National Taiwan University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

2

Local AI

--

Uedu Code

--

台灣大學 AQI 70 24°C PM2.5 17
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 11

I/O 重導與管線

輸出重導 > >>、輸入導向 < <<、管線 | 與 ||
Interaction 20 minute(s)

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 裡不是「強制覆寫」的意思,不要照抄。

互動練習:把輸出與錯誤訊息存進檔案

按鈕都是真的指令。先用 > 與 >> 寫入 output.log,再觀察 ls -l dir(dir 不存在)的錯誤訊息:只用 > 時錯誤訊息仍出現在畫面上,加上 2>&1 或改用 2> 才會進檔案。最後對應投影片的 Lab01、Lab02 建立 test.log。左邊的資料夾樹會標出剛剛寫入的檔案。

< 與 <<:改變輸入的來源

< 讓指令從檔案讀取標準輸入,而不是等鍵盤:

$ 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。

互動練習:here-doc、管線與 ||

對應投影片的 Lab03~Lab05。先用 < 讓 cat 讀 test.log,再用 here-doc 輸入多行文字、寫進檔案;接著用管線串接指令,右側會畫出管線每一段的輸出;最後用 || 與 && 依前一個指令的結果決定下一步。多行的按鈕代表在終端機上依序輸入好幾行。

想一想:重導輸出與錯誤

1.

notes.txt 原本有 10 行。執行 date > notes.txt 之後,notes.txt 會剩下什麼?

2.

dir 不存在。執行 ls -l dir > out.log 之後,會發生什麼事?

3.

想讓指令的正常輸出與錯誤訊息都寫進 all.log,下列哪一個寫法在 bash 裡不能做到?

第 1 次作答

想一想:輸入導向、管線與 || &&

1.

執行 wc -l < notes.txt,輸出會是下列哪一種樣子?

2.

輸入 cat > memo.txt << EOF 之後,什麼時候才會結束輸入?

3.

backup 目錄已經存在。執行 mkdir backup && echo OK || echo FAIL 會在畫面上看到什麼?

第 1 次作答

重點精華回顧

  • 三條資料流:0 標準輸入(鍵盤)、1 標準輸出(螢幕)、2 標準錯誤(螢幕)。
  • > 檔案 覆寫、>> 檔案 附加;> 會先清空檔案,set -o noclobber 可以防止誤蓋,強制覆寫用 >|。
  • 2> 檔案 只存錯誤訊息;> 檔案 2>&1 或 &> 檔案 兩種都存,順序要「先 > 後 2>&1」;2> /dev/null 把錯誤訊息丟掉。
  • 投影片的 >&、>!、>>&! 是 csh/tcsh 的寫法;bash 用 &>、>|、&>>。
  • < 檔案 讓指令從檔案讀輸入;<< 標記 是 here-doc,cat > 檔案 << end 可以直接寫出多行檔案。
  • | 把前一個指令的標準輸出接到下一個指令的標準輸入;$? 是結束狀態,&& 成功才執行、|| 失敗才執行、; 一律執行。
這一課完成了!
Next lesson:資料串流指令
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。