Learning Objectives
- 用 > 把指令的輸出存成檔案,用 >> 附加在檔案後面
- 分辨標準輸入、標準輸出、標準錯誤(0、1、2),用 2>、2>&1、/dev/null 處理錯誤訊息
- 用 < 讓指令從檔案讀入資料,用 << 一次輸入多行文字(here-doc)
- 用管線 | 把指令串起來,用 && 與 || 依前一個指令成功或失敗決定要不要執行下一個
指令的三條資料流
每個指令執行時,都有三條預先接好的資料流,各有一個編號(檔案描述元,file descriptor):
| 編號 | 名稱 | 預設接到 | 例子 |
|---|---|---|---|
| 0 | 標準輸入(stdin) | 鍵盤 | cat 沒有給檔名時,等你從鍵盤打字 |
| 1 | 標準輸出(stdout) | 螢幕 | ls 列出的檔名 |
| 2 | 標準錯誤(stderr) | 螢幕 | ls: cannot access 'dir': No such file or directory |
正常結果與錯誤訊息平常都顯示在螢幕上,看起來混在一起,其實走的是兩條不同的路。 「重導」(redirection)就是改變這些資料流的去向:輸出存進檔案、輸入改從檔案讀、錯誤訊息另外存或直接丟掉。 「管線」(pipe)則是把前一個指令的輸出直接接到下一個指令的輸入。
這一課的寫法都是 bash 的語法。Ubuntu 的登入 shell 預設是 bash(
echo $SHELL會顯示/bin/bash)。
> 與 >>:把輸出存成檔案
指令 > 檔案 # 覆寫:檔案不存在就建立;已經存在就先清空再寫入
指令 >> 檔案 # 附加:寫在原本內容的後面
投影片的例子(test.txt 是上一課的 7 行文字檔):
$ ls -l test.txt > output.log
$ cat output.log
-rw-r--r-- 1 student student 36 Oct 29 14:30 test.txt
$ wc test.txt >> output.log
$ cat output.log
-rw-r--r-- 1 student student 36 Oct 29 14:30 test.txt
7 12 36 test.txt
執行 > output.log 時畫面上什麼都沒有出現,因為輸出全部進了檔案。
>會先清空檔案,就算指令本身失敗也一樣。想保留原本的內容一定要用>>。 擔心手誤覆寫時,可以執行set -o noclobber:之後>遇到已經存在的檔案會拒絕寫入, 真的要覆寫時改用>|。
標準錯誤:2>、2>&1 與 /dev/null
> 只會導向標準輸出(1),錯誤訊息(2)仍然顯示在螢幕上,檔案裡什麼都沒有:
$ ls -l dir > output2.log
ls: cannot access 'dir': No such file or directory
$ cat output2.log
$
| 寫法 | 作用 |
|---|---|
指令 2> 檔案 |
只把錯誤訊息存進檔案,正常輸出照樣顯示在螢幕 |
指令 > 檔案 2>&1 |
正常輸出與錯誤訊息都存進同一個檔案(2>&1=把 2 號接到 1 號目前去的地方) |
指令 &> 檔案 |
和上一行相同,是 bash 的簡寫 |
指令 >> 檔案 2>&1、指令 &>> 檔案 |
兩種都附加到檔案後面 |
指令 2> /dev/null |
把錯誤訊息丟掉;/dev/null 是一個特殊裝置,寫進去的資料都會消失 |
順序有關係:ls > dirlist 2>&1 會把兩種輸出都寫進 dirlist;ls 2>&1 > dirlist 只有標準輸出進檔案,
因為 2>&1 執行的當下,1 號還指向螢幕。記住「先寫 > 檔案,再寫 2>&1」即可。
每個指令結束時會留下一個結束狀態(exit status),存在變數 $?:0 代表成功,其他數字代表失敗。
ls 找不到檔案時的結束狀態是 2。
投影片的 >&、>! 是 csh/tcsh 的語法
投影片「特殊輸出符號」那張表(>&、>>&、>!、>>!、>&!、>>&!,以及 noclobber 變數)來自 csh/tcsh 這一系的 shell,
不是 bash。在 Ubuntu 的 bash 裡請改用右欄的寫法:
| csh/tcsh | 意思 | bash 的寫法 |
|---|---|---|
>& 檔案 |
標準輸出與標準錯誤寫入檔案 | &> 檔案 或 > 檔案 2>&1(bash 也接受 >&,但手冊建議用 &>) |
>>& 檔案 |
標準輸出與標準錯誤附加到檔案後面 | &>> 檔案 或 >> 檔案 2>&1 |
>! 檔案 |
開啟 noclobber 時仍強制覆寫 | >| 檔案 |
>&! 檔案 |
開啟 noclobber 時仍強制覆寫,兩種輸出都寫入 | >| 檔案 2>&1 |
set noclobber |
禁止 > 覆寫既有檔案 |
set -o noclobber(關閉:set +o noclobber) |
>! 在 bash 裡不是「強制覆寫」的意思,不要照抄。
< 與 <<:改變輸入的來源
< 讓指令從檔案讀取標準輸入,而不是等鍵盤:
$ cat < output.log
-rw-r--r-- 1 student student 36 Oct 29 14:30 test.txt
7 12 36 test.txt
$ wc -l < output.log
2
cat output.log 與 cat < output.log 看起來一樣,差別在於前者是 cat 自己開檔案,後者是 shell 開好檔案再交給 cat。
從 wc -l < output.log 只印出 2、沒有檔名,就看得出 wc 根本不知道資料來自哪個檔案。
有些程式只會從標準輸入讀資料(例如下一課的 tr),或會一題一題詢問使用者,就可以用 < 把準備好的答案檔交給它。
<< 叫做 here-doc(here document):在指令後面寫一個結束標記,接下來輸入的每一行都會成為標準輸入,
直到出現只有結束標記的一行為止。終端機會用 > 提示你繼續輸入:
$ cat << end
> 123
> 345
> end
123
345
和輸出重導合在一起,就能不開編輯器直接寫一個多行的檔案:
cat > notes.txt << end # 寫入(覆寫)notes.txt
cat << end >> notes.txt # 附加到 notes.txt 後面
結束標記可以是任何字(常見的是 end 或 EOF)。把標記加上引號(<< 'EOF'),內容裡的 $HOME 這類變數就不會被替換,原樣寫入。
管線 |,以及 && 與 ||
管線 | 把前一個指令的標準輸出,接成下一個指令的標準輸入,中間不必存成檔案:
指令1 ──標準輸出──▶ | ──標準輸入──▶ 指令2 ──標準輸出──▶ | ──標準輸入──▶ 指令3
$ cat /etc/hosts | wc
5 17 132
$ ls -l /etc | less
管線只傳遞標準輸出;錯誤訊息仍然顯示在畫面上,要一起傳下去得先寫 2>&1,例如 指令 2>&1 | less。
&&、|| 與 ; 則是依序執行多個指令,差別在於看不看前一個指令的結束狀態:
| 寫法 | 意思 |
|---|---|
指令1 ; 指令2 |
不管指令1 成功或失敗,都接著執行指令2 |
指令1 && 指令2 |
指令1 成功($? 是 0)才執行指令2 |
指令1 || 指令2 |
指令1 失敗($? 不是 0)才執行指令2 |
投影片的例子:「dir 不存在才建立」
$ ls -ld dir || mkdir dir
ls: cannot access 'dir': No such file or directory
$ ls -ld dir || mkdir dir
drwxrwxr-x 2 student student 4096 Oct 28 14:30 dir
第一次 ls 失敗,所以執行了 mkdir;第二次 ls 成功,mkdir 就不會執行。
管線和 || 也能一起用:整條管線的結束狀態是最後一個指令的結束狀態。例如
cat notes.txt | grep "line 9" || echo "沒有找到 line 9":grep(第 14 課介紹,作用是找出含有某段文字的行)找不到時結束狀態是 1,就會執行 echo。
想一想:重導輸出與錯誤
notes.txt 原本有 10 行。執行 date > notes.txt 之後,notes.txt 會剩下什麼?
dir 不存在。執行 ls -l dir > out.log 之後,會發生什麼事?
想讓指令的正常輸出與錯誤訊息都寫進 all.log,下列哪一個寫法在 bash 裡不能做到?
想一想:輸入導向、管線與 || &&
執行 wc -l < notes.txt,輸出會是下列哪一種樣子?
輸入 cat > memo.txt << EOF 之後,什麼時候才會結束輸入?
backup 目錄已經存在。執行 mkdir backup && echo OK || echo FAIL 會在畫面上看到什麼?
重點精華回顧
- 三條資料流:0 標準輸入(鍵盤)、1 標準輸出(螢幕)、2 標準錯誤(螢幕)。
> 檔案覆寫、>> 檔案附加;>會先清空檔案,set -o noclobber可以防止誤蓋,強制覆寫用>|。2> 檔案只存錯誤訊息;> 檔案 2>&1或&> 檔案兩種都存,順序要「先>後2>&1」;2> /dev/null把錯誤訊息丟掉。- 投影片的
>&、>!、>>&!是 csh/tcsh 的寫法;bash 用&>、>|、&>>。 < 檔案讓指令從檔案讀輸入;<< 標記是 here-doc,cat > 檔案 << end可以直接寫出多行檔案。|把前一個指令的標準輸出接到下一個指令的標準輸入;$?是結束狀態,&&成功才執行、||失敗才執行、;一律執行。