位元詩人 [Shell Scripting] 教學:標準輸出入

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

透過標準輸出入,電腦程式可以和外部環境傳遞資料。標準輸出入將輸出入的功能抽象化,電腦程式可在不知道輸出入實際的來源和去處即可傳遞資料。

標準輸出入分為標準輸入、標準輸出、標準錯誤三種。這是電腦系統所提供的功能,而非 shell script 獨有的特性。本文介紹 shell script 處理標準輸出入的方式。

標準輸出 (Standard Output)

標準輸出由程式傳出,通常會傳向螢幕,也可能導向其他裝置。標準輸出用來傳輸一般的文字資料。錯誤訊息則不建議使用標準輸出來傳遞。

以下 shell script 使用 echo(1) 指令將字串 "Hello World" 傳到標準輸出:

greet="Hello";
target="World";

echo "$greet $target";

echo 僅適合傳輸簡單的文字資料。若需要對文字資料做細微的調整,則可改用 printf(1),如下例:

greet="Hello";
target="World";

printf "%s %s\n" greet target;

對於跨行的文字資料則可改用 cat(1) 傳遞:

#--------1---------2---------3---------4---------5---------6---------7---------8
#2345678901234567890123456789012345678901234567890123456789012345678901234567890
cat << EOF
Unix (trademarked as UNIX) is a family of multitasking, multiuser computer
operating systems that derive from the original AT&T Unix, development starting
in the 1970s at the Bell Labs research center by Ken Thompson, Dennis Ritchie,
and others
EOF

EOF 不是 shell 指令,而是程式設計者自訂的識別字,用來界定多行文字資料結束的位置。

標準錯誤 (Standard Error)

標準錯誤由程式傳出。如同標準輸出,標準錯誤常會傳到螢幕,或是導向其他裝置。標準錯誤僅用來傳遞錯誤訊息,不建議傳遞一般的文字資料。

Shell 沒有為標準錯誤設計額外的指令,而是藉由指定輸出代號的方式將文字資料導向標準錯誤。以下的 echo 指令將錯誤訊息傳向標準錯誤:

echo "Some error message" >&2

一般來說,標準輸出的代號為 1,標準錯誤的代號為 2。傳出文字訊息時,預設會傳到標準輸出。要將輸出改到標準錯誤,要寫額外的程式碼,像是上述程式碼片段的做法。

標準輸入 (Standard Input)

標準輸入則是由外部程式傳入的文字資料。對於電腦程式來說,不需要知道標準輸入的實際來源,只要留好開口即可。

以下 shell script 會偵測命令列參數是否存在,若命令列參數為空,則改從標準輸入取得文字資料:

#!/bin/sh

if [ -z "$@" ];
then
    tr '[:lower:]' '[:upper:]' < /dev/stdin;
else
    tr '[:lower:]' '[:upper:]' $@;
fi

exit 0;

tr(1) 指令會將字元進行一對一轉換,本範例程式是將小寫字母轉大寫字母。

重導 (Redirecting)

在預設情境下,標準輸入的來源是鍵盤,標準輸出和標準錯誤的標的是螢幕。但系統使用者可隨需求更改。

nohup(1) 用於執行長時間的指令。該指令在使用者離線後仍會繼續運行,常用於執行遠端指令。以下指令用 make 編譯程式:

$ nohup make </dev/null 1>build.log 2>build.err &

& 表示在背景執行指令。指令丟到背景後,會將主動權選給使用者。由於不需要標準輸入,故從 /dev/null 導入即可。標準輸出和標準錯誤則導向兩個不同的檔案,便於後續觀察。

管線 (Pipe)

管線的功能是將前一個程式的輸出重導為下一個程式的輸入。由於管線可在同一行內重覆使用多次,使用管線可以串連出緊湊且複雜的指令。

以下是編譯程式時常用的組合指令:

$ make | tee build.log

make(1) 需搭配 Makefile (或 GNUmakefile) 使用,通常用來編譯 C 或 C++ 應用程式。由於編譯程式的訊息很長,往往會超出螢幕捲動的上限,所以將 make 指令所得的訊息用管線重導緥 tee。然後 tee 將這些訊息另存在 build.log 文字檔案中。這時系統使用者仍可以同時觀看編譯的訊息。

以下 shell script 碼片段用來檢查特定應用程式的 PID (process id):

if [ -n "$1" ];
then
    ps aux | grep "$1" | grep -v grep | tr -s ' ' | cut -d' ' -f2;
fi

用管線組合的指令都比較複雜。一步步拆開來看就會理解了:

  • ps aux 以 BSD 風格輸出目前所有的行程
  • grep "$1" 從前一個指令過濾出目標指令
  • grep -v grep 用來去掉 grep 本身
  • tr -s ' ' 將多個空白壓縮成一個空白
  • cut -d' ' -f2 將輸出以空白切開後取第二個欄位

如果還是無法理解的話,建議實際上機操作,逐一輸入指令,就可以理解了。

以下 shell script 碼片段用來計算重覆輸入的文字的數量:

if [ -n "$@" ];
then
    grep -oP "\w+" "$@" | tr '[:upper:]' '[:lower:]' | sort | uniq -c | sort -nr
fi

這裡也用到管線。同樣將其拆開來看:

  • grep -oP "\w+" "$@" 抓出所有輸入的文字
  • tr '[:upper:]' '[:lower:]' 將文字正規化 (大寫轉小寫)
  • sort 將文字排序
  • uniq -c 取出不重覆文字並對其計數
  • sort -nr 以數量排序,由小排到大

Unix 命令列工具的哲學

由本文可知,Unix 命令列工具除了單獨使用外,還可以組合出複雜的功能。在這樣的思維下,Unix 命令列工具的設計方式如下:

  • 每個工具專注在單一任務上
  • 僅輸出最少量的可用文字訊息
  • 以「行」為單位來輸出文字訊息
  • 不寫死輸出入,讓使用者自由選擇
  • 不限制互動模式或批次模式
  • 儘量不重覆實作已有的命令列工具

當然,還是有一些例外。像是 awk(1)perl(1) 就是兼具程式語言的命令列工具。這些工具可以應付多種情境,而不限於單一任務。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。