Shell 管線(pipe)與重導向(redirect)完整解說

三個標準串流(Standard Streams)

在理解管線與重導向之前,必須先了解 Linux 的「三個標準串流」概念。每個 Linux 程式啟動時,都會自動連接三個資料流:

名稱編號說明預設連到
stdin
標準輸入
0 程式的資料輸入來源 鍵盤
stdout
標準輸出
1 程式的正常輸出 終端機螢幕
stderr
標準錯誤
2 程式的錯誤訊息輸出 終端機螢幕
  鍵盤 ─── stdin (0) ──→ [ 程式 ] ──→ stdout (1) ─── 螢幕
                                    └──→ stderr (2) ─── 螢幕

管線和重導向,就是改變這些串流的連接目標。

輸出重導向(>、>>)

把程式的標準輸出(stdout)從螢幕改寫到檔案:

單箭頭 >:覆寫模式

建立或覆蓋目標檔案,寫入程式的輸出:

$ ls /etc/ > filelist.txt
(不顯示,ls 的輸出被寫進 filelist.txt)
$ cat filelist.txt
apt
bash.bashrc
cron.d
hosts
nginx
passwd
...
⚠️ >覆蓋已存在的檔案內容,操作前確認檔案是否重要。若要保留原有內容,改用 >>

雙箭頭 >>:附加模式

將輸出附加到檔案末尾,不覆蓋原有內容:

$ echo "$(date): 腳本執行開始" >> deploy.log
$ echo "$(date): 腳本執行結束" >> deploy.log
$ cat deploy.log
Tue May 20 10:00:00 CST 2026: 腳本執行開始
Tue May 20 10:00:05 CST 2026: 腳本執行結束

輸入重導向(<)

把程式的標準輸入(stdin)從鍵盤改為讀取檔案:

$ sort < names.txt
Alice
Bob
Charlie
Dave

sortnames.txt 讀取輸入,而非等待鍵盤輸入。

Here Document(Here-doc):<<

將多行文字直接寫進腳本作為輸入,不需要另建立檔案:

$ cat << 'EOF'
這是第一行
這是第二行
變數 $HOME 不會被展開(有引號)
EOF
這是第一行
這是第二行
變數 $HOME 不會被展開(有引號)

Here-doc 在腳本中很常用,例如建立設定檔、傳送 SQL 等。

錯誤輸出重導向(2>、2>&1)

stderr(錯誤訊息)和 stdout(正常輸出)是兩個不同的串流,需要分別處理:

只重導向錯誤訊息

$ ls /nonexistent 2> errors.txt
(螢幕無輸出,錯誤訊息寫進 errors.txt)

stdout 和 stderr 都重導向(最常用)

2>&1 表示「把 stderr(2)合併到 stdout(1)去」:

$ ./long-script.sh > output.log 2>&1

這樣正常輸出和錯誤訊息都寫進同一個日誌檔,是腳本中最常見的寫法。

更簡潔的寫法(bash 4+ 支援):

$ ./long-script.sh &> output.log

stdout 和 stderr 分開記錄

$ ./app.sh > app.log 2> error.log

丟棄輸出:/dev/null

/dev/null 是一個特殊的虛擬裝置,寫進去的資料都被丟棄,從它讀取永遠得到空資料。常用於屏蔽不需要的輸出:

$ ping -c 1 google.com > /dev/null 2>&1

如果只想知道 ping 是否成功(用回傳碼),不想看輸出,就重導向到 /dev/null

$ if ping -c 1 -W 2 8.8.8.8 > /dev/null 2>&1; then
  echo "網路正常"
else
  echo "網路異常"
fi

管線(|):串聯指令

管線(pipe)用 | 符號,把前一個指令的 stdout 接到後一個指令的 stdin,讓多個指令協同工作:

指令A | 指令B | 指令C
  stdout↗   stdout↗
       stdin↗       stdin↗

最經典的範例:

$ cat /etc/passwd | grep "alice"

不過上面這個例子可以更簡潔地寫成:

$ grep "alice" /etc/passwd

管線真正的威力在於組合多個工具:

$ cat /etc/passwd | cut -d: -f1 | sort | head -5
_apt
alice
avahi
backup
bin

分解這個指令:

  1. cat /etc/passwd:輸出 passwd 檔的所有內容
  2. cut -d: -f1:以 : 分隔,取第一欄(使用者名稱)
  3. sort:排序
  4. head -5:只取前 5 行

tee:分流輸出到螢幕和檔案

tee 指令讓輸出同時顯示在螢幕上,又寫進檔案,就像 T 形管接頭:

$ ls -la | tee listing.txt
(在螢幕顯示 ls 的輸出,同時寫進 listing.txt)

常見的應用:安裝套件時同時記錄日誌

$ sudo apt upgrade | tee upgrade-$(date +%Y%m%d).log

需要 sudo 寫入系統檔案時:

$ echo "192.168.1.10 myserver" | sudo tee -a /etc/hosts

實戰組合範例

範例 1:找出佔磁碟最多的 10 個目錄
$ du -sh /var/* 2>/dev/null | sort -rh | head -10
2.3G    /var/log
1.2G    /var/cache
850M    /var/lib
範例 2:統計日誌中各 HTTP 狀態碼的數量
$ cat /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
  52340 200
   3241 304
    892 404
    127 500
範例 3:找出佔用最多 CPU 的前 5 個程序
$ ps aux --sort=-%cpu | head -6
USER       PID %CPU %MEM    VSZ   RSS COMMAND
mysql     1234  5.2 15.3 1024000 314000 mysqld
nginx     5678  0.8  1.2   51200  24600 nginx: worker
範例 4:搜尋所有 .php 檔案中含有密碼的行
$ grep -rn "password\|passwd\|secret" --include="*.php" ./src/ 2>/dev/null | grep -v "//.*password"
./src/config.php:15:$db_password = getenv('DB_PASS');
💡 管線的概念是 Linux 命令列哲學的精髓。多練習用小工具組合成一行指令,你會發現 Linux 命令列極其強大。