Gold Holk
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags: shell, linux, trick, bash breaks: false --- # 突破盲點的 bash 使用技巧 bash 是 linux 下最通用的互動式 shell。 在有圖形界面以前,shell 就是 unix 使用者認知中電腦的全部。 本課程將會介紹多種冷門的 bash 使用技巧, 包含迴圈、多工、互動使用技巧、腳本撰寫; 帶領聽眾重新理解 shell 的設計哲學。 其中腳本會以 sh 為主,而互動式技巧則會以 bash 為主。 ---- ## 講者介紹: gholk * linux 使用者,興趣使然的 web 開發者。 * 宣稱只寫 vanilla js ,但其實只是懶得學框架。 * 自認為只是 linux 使用者,所以堅持能用 shell 解決的事情就不動到 c 、 js 等一般語言。 * 非本科生,未來應該不是靠程式過活,所以可以任性地把自己定位為使用者。 --- ## shell 與通用程式語言 * 一般完整的程式語言特性是,內部功能完整,但與外部其它程式溝通困難。 * shell 則是設計用來呼叫、組合所有其它程式的語言。 ---- ### 二者間的取捨 * 用通用程式語言設計的程式,應該行為單純,只專注在主要的功能。 * 在使用時再經由 shell 的包裝,配合 **萬用字元** 、**管道** 、 **迴圈** 等達成各式各樣的功能。 * (因為之前在 node.js 裡處理參數的時候寫得很煩。) --- ## shell 的理念 每個程序都有 stdin stdout stderr , 可以傳入 argv ,繼承環境變數。 shell 的核心功能就是呼叫、組合各程序,達到目的。 除了依賴外部程序, shell 中也能自定義 **函數** 、 **括號命令群組** , 函數也像程序一樣有 std stream argv 環境變數等功能, 而命令群組一樣有 std stream ,但缺少 argv 。 ---- ### 截取 stream 的一部份 ```shell cat some-stream | { head -c 8 >/dev/null # 丟掉開頭的 8 byte foo=$(head -c 4) # 讀取 4 byte # do something echo $(basename $foo) cat # 把剩下的輸入原封不動丟進輸出 } > result # 把結果存起來 ``` ---- ### 在行處理上不適用 以行來處理會漏資料,我不知道為什麼。 ```shell yes hello | nl | { head -n 1 >/dev/null # 丟掉開頭一行 foo=$(head -n 1) # 讀取一行 echo $foo } ``` ---- ### 只能回去用 read ```shell yes hello | nl | { read n for i in `seq 1 n` do read line done } ``` --- ## 管道的縮排應該怎麼寫? 管道符為最後一個字元時,可以省略跳脫結尾換行符的反斜線。 ```sh awk '{sum += $1; print sum}' < file | sed -n '/[13579]$/p' | cut -d ' ' -f 2-4 | sort -n ``` ---- 也有些人覺得應該要寫清楚, 顯式手動跳脫比較好 ~~,就像某些 js 開發者硬要加分號一樣~~ 。 ```sh cat file |\ awk '{sum += $1; print sum}' |\ sed -n '/[13579]$/p' ``` ---- *那就摻在一起作成撒尿牛丸啊!* 這好像是 gnu 風格的縮排? 不確定從哪看來的,但目前我覺得這種寫法最美觀。 ```sh cat file \ | awk '{sum += $1; print sum}' \ | sed -n '/[13579]$/p' ``` --- ## 為什麼要用 cat cat 的好處,統一格式,一律把輸入的檔案放在最開頭。 ```sh cat file \ | awk '{sum += $1; print sum}' \ | sed -n '/[13579]$/p' \ | cut -d ' ' -f 2-4 \ | sort -n ``` ---- 如果不用 cat , 重道向的檔案慣例是寫在第一個命令的結尾處, 後面跟著的命令同樣位置卻是空的,造成不協調感。 ```sh awk '{sum += $1; print sum}' <file \ | sed -n '/[13579]$/p' \ | cut -d ' ' -f 2-4 \ | sort -n ``` ---- ## 重導向符的位置 但其實重導向符的位置不一定要在結尾, 要擺開頭、中間、結尾都可以。 ```sh awk '{sum += $1; print sum}' < file \ | sed -n '/[13579]$/p' ``` ```sh <file awk '{sum += $1; print sum}' \ | sed >result -n '/[13579]$/p' ``` ```sh >df.log df ``` ---- 所以可以寫成這樣,夠反人類吧? 有沒有忽然覺得 cat 比較好看了? ```sh < file \ awk '{sum += $1; print sum}' | sed -n '/[13579]$/p' | cut -d ' ' -f 2-4 | sort -n ``` ---- 如果你記得命令群組的用法,那也可以用在這裡: ```sh { awk '{sum += $1; print sum}' | sed -n '/[13579]$/p' | cut -d ' ' -f 2-4 | sort -n } <file >result ``` ---- 有點像是沒有宣告成函數的寫法: ```sh do_something() { awk '{sum += $1; print sum}' | sed -n '/[13579]$/p' | cut -d ' ' -f 2-4 | sort -n } do_something <file >result ``` ---- ### 同一個檔案不能同時是管道的輸入與輸出 因為 `>` 會清空檔案內容,讓輸入讀不到資料。 ``` do_something <file >file ``` --- ## 組合出一對多的管道 在複雜的情況時,會需要組合多個輸入輸出; 所以為了模擬複雜的情況,介紹幾個程式: ---- ### paste 逐行合併二個檔案 ``` ~$ seq 0 2 8 > even ~$ seq 1 2 9 > odd ~$ paste even odd 0 1 2 3 4 5 6 7 8 9 ``` ---- ### 座標轉換程式 proj4 ```sh <lonlat.txt awk '{print $2,$3,$4}' \ | proj -f %.4f +proj=utm \ > utm.txt ``` ---- ### stdin 只有一個 有時,我們想把多個結果併在同一個檔案裡, 可以用 paste 把二個檔案的每一行串接起來。 ```sh <xyz.txt cs2cs -f %.4f +proj=cart +to +proj=utm \ | paste xyz.txt - >xyz-utm.txt ``` ---- 但如果想要一次併好幾個檔案呢? ```sh ( <xyz.txt cs2cs -f %.4f +proj=cart +to +proj=utm <xyz.txt cs2cs -f %.4f +proj=cart +to +proj=lonlat ) | paste - - # ????? ``` 很遺憾,你只有一個 stdin 可以用。 ---- ### process substitution 這個功能可以讓你把某個命令當作檔案寫入或讀取。 寫入是 `>( )` ,讀取是 `<( )` 。 ```sh diff <(tar -xOf v1.tar main.c) <(tar -xOf v2.tar main.c) ``` ---- 進程替換其實就是把該部份語法, 置換成連結到該命令的管道。 ```shell ~:$ printf 'process substitution: "%s"\n' <(true) process substitution: "/dev/fd/63" ~:$ ll >(true) l-wx------ 1 gholk gholk 64 8月 9 11:39 /dev/fd/63 -> pipe:[109154] ~:$ ll <(true) lr-x------ 1 gholk gholk 64 8月 9 11:39 /dev/fd/63 -> pipe:[109168] ``` ---- 那結合二者,就能把多個程序的輸出結合在一起了! ```sh paste \ <( <xyz.txt awk '{print $2,$3,$4}' \ | cs2cs -f %.4f +proj=cart +to +proj=utm ) \ <( <xyz.txt awk '{print $2,$3,$4}' \ | cs2cs -f %.4f +proj=cart +to +proj=lonlat ) ``` ---- 只是有個小問題,process substitution 是 bash 的專屬功能,sh 不支援。 ---- ### tee and fifo 所以如果要在 sh 中執行,那得再介紹 fifo。 fifo 搭配 tee 就能實現一到多的管道。 ---- ### fifo ```shell mkfifo my-fifo echo hey >my-fifo & cat my-fifo ``` ---- ```flow start=>start: start xyz=>parallel: file with name and xyz coordinate awk=>operation: awk extract xyz part tee=>parallel: tee dulpicate xyz pll=>operation: proj xyz to lonlat putm=>operation: proj xyz to utm paste=>operation: paste merge result=>end: file with name, xyz, utm, lonlat start(right)->xyz xyz(path1, bottom)->awk->tee tee(path3, left)->pll->paste tee(path2, bottom)->putm->paste xyz(path3, right)->paste paste->result ``` ---- ```sh fifo_list='xyz2lonlat xyz2utm lonlat utm' mkfifo $fifo_list # 對 fifo 的寫入在開始讀取前會阻塞所以要丟到背景 awk '{print $2,$3,$4}' <xyz.txt | tee xyz2lonlat > xyz2utm & cs2cs -f %.8f +proj=cart +to +proj=lonlat <xyz2lonlat >lonlat & cs2cs -f %.4f +proj=cart +to +proj=utm <xyz2utm >utm & paste xyz.txt lonlat utm > all.txt rm $fifo_list ``` ---- * fifo 其實是解決輸入輸出流沒有名字的問題。 因為 paste 需要多個輸入,但只用 stdin 只有一個, 所以用多個 fifo 來連接輸入輸出。 * 要實現一對多,最關鍵還是 tee; 其實 tee 也可以用 `>( )` pipe 給多個程式。 ---- ## tee 與 fifo 的阻塞 tee 與 fifo 都是會阻塞的操作。 對 fifo 讀取或寫入時, 如果沒有其它程式同時在寫入或讀取,就會阻塞。 ---- tee 也是會阻塞,如果 tee 寫入的任一個程序 或是檔案阻塞,那 tee 所有的輸出都會阻塞。 --- ## server 當自己家用 ssh 不一定是執行一個互動式的 shell , 也可以直接執行命令。 ```shell ssh user@my.lab.ml ls ``` ---- 如果有一台以上的電腦,但某些程式只裝在特定一台, 可以用 ssh 幾乎無縫接軌取用。 ```sh cat xyz.txt \ | ssh user@my.lab.ml cs2cs +proj=cart +to +proj=lonlat ``` ---- ### 善用別名 ssh 可以用別名代表一台伺服器, 不然每次都要打一長串帳號域名,一點都不像自己家。 ---- ``` # ~/.ssh/config # see `man ssh_config` Host lab HostName my.lab.ml User user ``` ```sh cat xyz.txt \ | ssh lab cs2cs +proj=cart +to +proj=lonlat ``` ---- ### 自動壓縮或手動壓縮 如果資料量比較大時,可以考慮壓縮加速: ```sh gzip --to-stdout xyz.txt \ | ssh lab zcat - \ \| cs2cs +proj=cart +to +proj=lonlat \ \| gzip - \ | zcat - ``` ---- 每次都手動壓也很累,不如直接在 ssh 層使用自動壓縮吧: ``` # ~/.ssh/config # global Compression yes # or only compression in host Host lab HostName my.lab.ml Compression yes ``` ---- ### 善用 stdin 但 ssh 執行遠端電腦上的程式的問題是,只有 stdin 能用。 如果真得要傳複數檔案,可以考慮用 tar 來打包, 但就得執行一長串命令來解開再打包回來了; 還要注意不要丟任何東西到 stdout,不然回來的 tar 會壞掉。 (只能靠 stderr 來 log 了。) ---- 傳送單個 tar 封存: ```shell tar -cf - file1 file2 | ssh lab tar -xf - ``` ---- 傳送 tar 封存並執行達端程式,再把結果用 tar 送回來: ```sh tar -cf - x y z | ssh lab ' tar -xf - paste x y z | cs2cs +proj=cart +to +proj=lonlat > lonlat tar -cf - x y z lonlat rm x y z lonlat ' > result.tar ``` ---- ### ssh key 只是你可能需要用 ssh-key 免密碼登入, 才能達到全家就是你家的方便等級。 如果怕安全問題,可以手動修改 server 上的 `~/.ssh/authorized_keys` ,用完就註解掉該行。 ---- ### ssh control master 或是用 ssh control master 登入一次後 就複用既有的 ssh 連線。 除了在連線存在期間不用重覆登入外, 還可以省下建 tcp 連線的時間。 ```term ~:$ time ssh lab true real 0m1.123s user 0m0.032s sys 0m0.032s ~:$ # using ssh control master ~:$ time ssh lab true real 0m0.030s user 0m0.004s sys 0m0.008s ``` ---- ``` # ~/.ssh/config Host lab HostName my.lab.ml User gholk ControlMaster auto ControlPath ~/.ssh/ssh-control-master-%r@%h:%p ControlPersist 300 ``` ---- 當然,第一次登入還是要密碼, 所以如果要完全自動化,還是得用 ssh key。 只是用 control master 之後, 不管同時執行了幾個 ssh , 都只會跑在同一個 ssh 連線上。 ---- ```sh ssh -N lab # login with password then C-z to background bg # run previous command in background for i in * do # do something with ssh cat $i | sed | ssh lab proj | awk >result-$i done kill %% # kill ssh -N process ``` --- ## 用 disown 讓程式在登出後繼續運行 ```sh ( for tar in *.tar do tar -xf $tar summary mv summary $(basename $tar .tar)-summary done ) >extract-tar.log 2>&1 & disown -h %% exit ``` ```shell tail -f extract-tar.log # or with modern command `less` less +F extract-tar.log ``` ---- ### 放棄難用的 nohup nohup 只能用在執行檔上。 但如果你要用 sh 就另當別論了, disown 只能在 bash 中使用。 ---- 其實在迴圈的 done 後面直接加 `&` 就能丟到背景了, ```sh while sleep 1s do echo dont sleep done & ``` ---- 關於 `&` 的意義,其實比較像 `;` 。 ```shell echo a & echo b echo a ; echo b ``` ---- 加括號是有時要一次執行好幾個命令, 要全部丟到背景就可以用括號包起來成群組, 再把群組丟到背景。 ```sh ( uncompress NCTU0010.19d.Z crx2rnx NCTU0010.19d > NCTU0010.19o xz NCTU0010.19o ) & ``` --- ## 迴圈寫法 有些命令適合放 while 後面。 ``` find -name '*.jpg' | while read file do cp $file /tmp # some other code done ``` ---- 比較醜,但比較好理解的寫法。 ``` find -name '*.jpg' | while true do read file if [ -z "$file" ] then break fi # some other code done ``` ---- 我也常用 sleep : ``` while sleep 1s # or use `true` while true do sleep 1s done ``` ---- ## 平行處理壓縮時間 ```shell for gzip in *.gz do gzip -d $gzip compute-some-thing ${gzip%.gz} done ``` ---- 同時處理二個 ```shell for gzip in *.gz do gzip -d $gzip wait compute-some-thing ${gzip%.gz} & done ``` ---- 我有一個大膽的想法 ```shell for gzip in *.gz do ( gzip -d $gzip compute-some-thing ${gzip%.gz} ) & done ``` ---- 比較保險的平行處理,直接跑二個迴圈。 ```shell for gzip in *[13579].gz do gzip -d $gzip compute-some-thing ${gzip%.gz} done & for gzip in *[24680].gz do gzip -d $gzip compute-some-thing ${gzip%.gz} done & ``` --- ## 編輯以前的命令並執行 很多人應該都知道可以用 ↑ ↓ 來執行以前的命令。 ---- ### 在歷史記錄中搜尋 另一個更好用的是 readline 的 C-r (reverse search) 可以搜尋以前的命令,不用 C-b 一個個找按半天。 1. `C-r` 2. 輸入要搜尋命令包含的字串 3. 每輸入一個字,會即時顯示搜尋到的結果 4. 按 enter 執行,按 ← → 編輯, 再按一次 `C-r` 換下一個匹配的, `C-c` 取消。 ---- ### fc: fix command 這個命令可以開啟編輯器編輯上一個執行的 shell 命令, 編輯完離開後就會執行。 可以用選項控制要編輯第 n 個命令,或是像 `C-r` 一樣搜尋。 但其實 fc 不好用,因為不太可能 *記住* 要改的命令是第幾個, 而且搜尋匹配的第一個結果不一定是你印象中的。 ---- ### 直接開啟編輯器 另一個 readline 快捷鍵是 `C-x C-e` (edit command in editor) , 是直接開啟編輯器編輯目前打到一半的 shell 命令。 主要用在命令打得很長的時候, 只靠 shell 基礎的功能編輯會很痛苦。 編輯器同 fc 預設都是 vi 或看 EDITOR 變數。 所以請至少有一個能在 shell 中使用的編輯器。 ---- ### 取代 fc 的功能 搭配 `C-r` 搜尋的話,就搜到了再按 `C-x C-e` 編輯即可, 互動搜尋的效果會比 fc 盲搜的結果好很多。 ---- ### 為什麼多行命令被壓成一行? 問題在 cmdhist 與 lithist 這二個選項。 ```sh shopt -s cmdhist # save multiple line command in single history entry # but join in single line with `;` shopt -s lithist # keep `\n` instead use `;` ``` ---- ### 多行命令 ``` ~:$ for i in `seq 2 6` > do > echo $i > done 2 3 4 5 6 ``` ---- ### only enable cmdhist ``` ~:$ for i in `seq 2 6`; do echo $i; done ``` ---- ### long multi-line command ` ~:$ for id in $(tail -n +2 csrs.id); do echo $id; curl-csrs-ppp get $id > $id.zip; basename=$(basename $(unzip -l $id.zip | awk '$4 ~ /mari.*pdf/ { print $4 }') .pdf); mv $id.zip $basename.19o.zip; unzip $basename.19o.zip $basename.csv; sleep 20s; done ` ---- ### enable lithist ``` ~:$ for id in $(tail -n +2 csrs.id) do echo $id curl-csrs-ppp get $id > $id.zip basename=$(basename $(unzip -l $id.zip | awk '$4 ~ /mari.*pdf/ { print $4 }') .pdf) mv $id.zip $basename.19o.zip unzip $basename.19o.zip $basename.csv sleep 20s done ``` ---- ### lithist 故障 lithist 有時候會壞掉,多行命令會被分開成一行一行, 可能是因為舊的歷史檔案 `~/.bash_history` 格式亂掉。 像如果歷史檔案的大小超過限制會被截斷,格式就會亂掉。 修正或直接刪掉就會正常。 ---- ### edit function, alias, script https://github.com/GHolk/loco/blob/master/bash_function#L78 --- ## 在腳本中啟動另一個子 shell 當你需要在腳本內在另一支程式內執行一系列命令, 一般是要寫到另一個檔案直接執行。 ``` sftp -b batch.sftp remote-server ``` 但有時候不希望多一個檔案,管理起來會很麻煩, 會想要都寫在同一個同案裡。 ---- ### here doc 比較直覺也比較保險的作法是用 heredoc, 但缺點是變數會被展開,可能需要跳脫。 (如果你的 IDE 有跳脫的快速鍵就不成問題。) ---- ```sh #!/bin/sh rinex=NCTU0010.19o docker exec --interactive --tty gxh bash <<GUEST . /usr/local/GipsyX/rc_GipsyX.sh rinex=inside-docker echo $rinex # NCTU0010.19o echo \$rinex # inside-docker gd2e.py -rnxFile $rinex GUEST ``` ---- ### 用 tail 抓出自己的內容 主要是用 `$0` 會指向檔案本身的技巧,事先算好行數, 但 `$0` 會存完整路徑是 bash 的擴充, sh 中 `$0` 只會存最終檔名。 ---- ```sh #!/bin/sh a=b tail +5 $0 | su -l guest -c sh exit whoami # guest a=c echo $a file=$(echo *.*) ``` ---- 這是 debian 裡 grub-mkconfig 的做法, 因為 grub 腳本中用到了大量的變數,如果一一跳脫可讀性會很差。 ``` #!/bin/sh exec tail -n +3 $0 # This file provides an easy way to add custom menu entries. Simply type the # menu entries you want to add after this comment. Be careful not to change # the 'exec tail' line above. menuentry '[system] shutdown' { halt } ``` ---- ### 用 sed 定位 exit 不用手動計算行數,但要注意不要匹配到奇怪的東西。 ```sh #!/bin/sh sed '1,/^exit$/d' $0 | dbus-run-session sh exit gvfs-mount ftp://my.lab.ml cp .gvfs/ftp/some-file.zip . ``` --- ## 冷門用法 摺疊 ```shell seq 10 | paste - - ``` ---- ### 格式化輸出類似的字串 ```shell seq -f "(%.0f)" 10 ``` ```shell printf "%s\0" * # similar to find -maxdepth 1 -print0 ``` ---- ### 重覆輸出 ```shell printf "yes\n%.0s" `seq 5` ``` ```shell yes "yes" | head -n 5 ``` ---- ### yes 的真正用途 當某些程式執行時會問很多 yes no 的時候, 用 yes 告訴他。 ```shell yes | sudo apt install the-world ``` --- ## ex 批次編輯 雖然 sed 也可以批次編輯, 但有些功能還是要用可以來回跳躍的真正的編輯器比較方便, 而且編輯器還是比較快。 後來發現其實差不多,當檔案太大時,都是卡在硬碟寫入瓶頸。 ---- ```sh for rinex in *.rnx do echo ' 1 /ANT # s/-Unknown-/TPSG3_A1/ w n ' done | ex *.rnx ``` ---- ### 為什麼不是 ed ? ``` ED(1) Unix Programmer's Manual ED(1) NAME ed - text editor SYNOPSIS ed [ - ] [ -x ] [ name ] DESCRIPTION Ed is the standard text editor. ``` ---- ### 有 vi 就有 ex ex 的好處是,太新的發行版不一定有裝 ed , 但一定有裝 vi ,有 vi 就有 ex 。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully