03 - Operators

Piping

[!Note] 在了解 piping 之前,你會須要先了解 stdin, stdout & stderr

{COMMAND_A} | {COMMAND_B}

| 稱為 pipe,用來連接兩個指令。會先執行左側指令,並將左側指令的 stdout 作為右側指令的 stdin,然後執行右側指令。

e.g.

echo "hello world" | grep "h"
# hello world

Output Redirection

{COMMAND} > {FILENAME}

> 會將左側 {COMMAND} 的 stdout 導向(寫入)右側的 {FILE}

e.g.

echo hello > myfile.txt

以上例而言,若直接執行 echo hello,Shell 會將 hello 印在 terminal 上;加上 > 後,hello 會被導向並寫入 myfile.txt 這個檔案,所以此時 terminal 不會印出文字。

Append Mode

若 redirect output 時不想要將檔案內容覆蓋,而是想將 stdout 加在原檔案內容的後面,則應使用 >> 取代 >

touch myfile.txt

echo hello > myfile.txt
echo world >> myfile.txt

cat myfile.txt
# hello
# world

將 stdout 導向 stderr

使用 >& 搭配 file descriptor 可以將 stdout 導向 stderr,比如:

echo hello 1>&2

也可以反過來將 stderr 導向 stdout,比如:

cat non-existing-file 2>&1 | tee -a test.txt

在上例中,如果只寫 cat non-existing-file | tee -a test.txt,在「cat 因找不到 non-existing-file 而 stderr」後,就不會繼續執行後面 | tee -a test.txt 的部分了。但若有 2>&1,Shell 就會將 cat 的 stderr (cat: non-existing-file: No such file or directory) 寫入 test.txt 中。

將 stderr 導向「黑洞」

{COMMAND} 2> /dev/null
  • /dev/null 是一個檔案,任何寫入這個檔案的東西都會直接消失

  • 2> /dev/null 的意思是只將 stderr 導向 /dev/null,stdout 仍然會印在 terminal 上

Input Redirection

{COMMAND} < {FILE}

< 會把右側 {FILE} 的內容讀入 stdin,並把 stdin 導向左側的 {COMMAND}

e.g.

pbcopy < myfile.txt

{COMMAND} < {FILE} 的效果與 cat + piping (cat {FILE} | {COMMAND}) 是一樣的,唯一的差別是後者多一個尋找並執行 cat 的時間,所以使用 input redirection 通常會比較有效率。但若 {COMMAND} 是一個不接收 file input 的指令,那就還是得使用 cat + piping。

Process Substitution

{COMMAND_A} <({COMMAND_B})

前面介紹的 input redirection 可以將 file 傳遞給 command,而這裡的 process substitution 是把一個 command 的 output 當作檔案,然後傳遞給另一個 command。

你可能會想,更前面介紹的 Piping 不就是在做這件事嗎?確實,不過兩者還是有一些細微的差異,下表是 Piping ({COMMAND_A} | {COMMAND_B}) 與 Process Substitution ({COMMAND_A} <({COMMAND_B})) 的差異:

Piping
Process Substitution

Execution

Both commands run ==concurrently==, ==streaming data==.

{COMMAND_B} runs in the ==background==, {COMMAND_A} reads from a ==pseudo-file==.

Data Flow

Directly from stdout to stdin.

{COMMAND_B}'s output is treated as a file.

Efficiency

More efficient for large data (streaming).

Can be slower if {COMMAND_A} expects file input instead of streaming.

Use Case

When {COMMAND_B} works with streamed input.

When {COMMAND_A} needs a filename as an argument (e.g., diff, cmp).

[!Note]sh 不支援 process substitution。

把指令丟到背景執行 - &

如果一個指令在執行時會佔用 process 一段時間,但你不想讓 terminal 因此一直被佔用著,那可以在指令後方加上 & 來把指令丟到背景執行:

{COMMAND} &

將指令丟到背景執行的好處就是 user 可以持續擁有 terminal 的控制權。

把背景的指令拉回前景

可以用 fg 將在背景運行的 job 拉回前景,前提是 ==user 必須仍然在當初使用 & 的那個 Shell session 中==:

fg [%{JOB_ID}]

如果當前的 Shell session 的背景中只有一個 job 則可以不用寫 {JOB_ID},可以用 jobs 指令查看所有「當前 Shell session 中」的 jobs 以及它們的 job ID。

[!Note] 想知道更多關於 fgjobs 指令以及 job control 的詳細介紹,可以看這篇

Chaining Commands

有條件地接連執行指令 - &&

若將若干個指令用 && 串連,則這些指令會「一個接著一個」被執行,只有前面的指令成功執行完畢才會執行後面的,否則就中斷。

e.g.

echo "hello" && echo "world"
# hello
# world

這麼做的效果等同於在 Shell script 中使用 set -e 讓 script 在遇到錯誤時就終止:

set -e
echo "hello"
# some other scripts
echo "world"

[!Note]&& 也是條件式中的 AND operator。

有條件地接連執行指令 - ||

只有 || 前面的指令執行失敗時(產生一個非 0 的 exit code),才會執行 || 後面的指令。

e.g.

echo "hello" || echo "world"
# hello

因為有成功執行 echo "hello",所以就不執行 echo "world"

[!Note]|| 也是條件式中的 OR operator。

無條件地接連執行指令 - ;

無論前方的指令發生什麼事,都會從頭到尾執行所有指令。

e.g.

echo "hello"; echo "world"
# hello
# world

false; echo "hi"
# hi

# keep listening incoming TCP packages on port 8000 
while true; do nc -l localhost 8000; done

其實使用 ; 的效果就和「不在開頭寫 set -e 的前提下」一行一行寫是一樣的,所以可以把 ; 當作換行符號。

參考資料

Last updated