Zsh 開發指南(第十五篇 程式與作業控制)

陌辭寒發表於2017-09-23

導讀

通常情況 zsh 指令碼是在一個程式中(並且單執行緒)執行的,但有時我們需要並行執行一些程式碼,因為現在的 CPU 基本都是多核的,這樣可以加快執行速度。這就涉及到程式與作業控制。這裡不講程式的概念。

在子程式中執行程式碼

之前我們提到過,小括號中的程式碼是在子程式中執行的:

% (sleep 1000 && echo good)

# 然後再另一個 zsh 裡檢視程式
% pstree | grep sleep
     `-tmux: server-+-zsh---zsh---sleep複製程式碼

裡邊有兩個 zsh 程式。如果不加小括號的話:

% sleep 1000 && echo good

# 然後再另一個 zsh 裡檢視程式
% pstree | grep sleep
     `-tmux: server-+-zsh---sleep複製程式碼

就只有一個 zsh 程式。這說明使用小括號時,裡邊的程式碼是在子程式(一個新的 zsh 程式)執行的。但需要注意的時,如果括號裡只有一個命令(比如 sleep 1000),那麼並不會再開一個子程式來執行了。

那麼在子程式裡執行程式碼有什麼意義呢?如果像上邊那樣放著前臺執行,是沒有什麼意義。但我們可以把它放後臺執行。

在後臺執行程式

首先我們先看下怎麼把單個程式放後臺執行。

% sleep 1000 &
[1] 850複製程式碼

在 sleep 1000 後邊加一個 &,就會把它放後臺執行。然後會輸出一行內容,[1] 是程式的作業(job)號,850 是程式號(PID)。我們可以繼續執行別的命令,不需要等待 sleep 結束了。

jobs 命令可以檢視當前在後臺執行的所有作業:

% jobs
[1]  + running    sleep 1000

# -l 會輸出程式號
% jobs -l
[1]  + 850 running    sleep 1000複製程式碼

fg 命令可以把後臺的作業切換回前臺:

# 然後會繼續等待 sleep 執行
% fg
[1]  + running    sleep 1000複製程式碼

如果程式已經執行起來了,我們想再把它放到後臺,可以這樣:

# 回車後按 ctrl + z
% sleep 1000
^Z
zsh: suspended  sleep 1000
# 這時可以執行 jobs 看一下,sleep 是處於掛起狀態的
% jobs
[1]  + suspended  sleep 1000
# 可以用 bg 讓 sleep 恢復執行
% bg
[1]  + continued  sleep 1000
# 這樣 sleep 就執行在後臺了
% jobs
[1]  + running    sleep 1000複製程式碼

其實 jobs、fg、bg 這些命令並不常用,大概瞭解下用法即可。比如現在在用 vim 編輯檔案,檔案還沒有儲存,但我想退到終端執行個命令,然後再回到 vim。可以按 ctrl + z 讓 vim 掛起,然後執行命令,最後再執行 fg 讓 vim 恢復。但通常我們可以啟動多個終端模擬器,或者開一個新終端模擬器標籤,或者用 tmux,沒必要在一個 shell 裡這麼折騰。

在指令碼中使用後臺程式執行程式碼

那麼回答之前的場景,要在後臺程式裡執行 sleep 1000 && echo good:

% {sleep 1000 && echo aa} &複製程式碼

這樣大括號裡的程式碼都會在後臺程式裡執行,指令碼里可以繼續寫別的。如果做完了後需要再等大括號裡邊的程式碼執行。

#!/bin/zsh

{sleep 5 && echo p1} &
# $! 是上一個執行的後臺程式的程式號
pid=$!
{sleep 10 && echo p2} &
echo aaa
# 要做的其他事情先做完
sleep 2
echo bbb
# wait 加程式號用來等待程式結束,類似 fg,但指令碼中不能用 fg
wait $pid
echo ccc複製程式碼

結果:

% ./test.zsh
aaa
bbb
p1
ccc
# p2 是指令碼執行完過幾秒才輸出的
% p2複製程式碼

這樣我們就可以同時操作多個程式來為自己服務了。而程式之間的通訊,可以用命名管道或者普通檔案來做,也可以使用 socket 檔案(Zsh 中有 zsh/net/socket 模組,使用它可以通過 socket 檔案來通訊。管道是單向的,而 socket 雙向的,更靈活一些,後續我們會了解它的用法),或者使用網路通訊(如果指令碼分佈在不同的機器,zsh 中有 zsh/net/tcp 模組,這樣無需外部命令就可進行 tcp 通訊,後續也會講到它)。

訊號

執行中的程式可以接受訊號然後對訊號做出響應。kill 命令用來給程式傳送訊號。

15(SIGTERM)是最常用的訊號,也是 kill 不加引數的預設訊號,用於終止一個程式。kill num 即可終止程式號是 num 的程式。但 15 訊號可以被程式捕獲,然後並不退出。如果要強行殺掉一個程式,可以用 9 訊號(SIGKILL),它是程式無法捕獲的,但這樣的話程式正在做的事情會突然中斷,可能會有嚴重的影響,所以通常情況不要使用 9 訊號殺程式。

在指令碼中捕獲訊號:

#!/bin/zsh

# SIGINT 是 2 訊號,ctrl + c 會觸發
TRAPINT() {
    # 處理一些退出前的善後工作
    sleep 333
}

sleep 1000複製程式碼

然後執行這個指令碼,然後 ctrl + c,指令碼沒有退出,因為在執行 sleep 333,要再按一次才會退出。

在指令碼中使用訊號,通常是給其他程式發(主要是 15),而不是給自己發。在指令碼中也很少需要捕獲訊號處理。訊號相關的更多內容,以後可能會補充。

總結

本文大概講了程式與作業控制相關內容,主要用於在指令碼里使用多程式執行程式碼,而不是在終端裡進行作業控制(因為很少需要這樣做)。關於指令碼中的多個程式如何配合的內容還需要繼續完善。

全系列文章地址:github.com/goreliu/zsh…

付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活定價,歡迎諮詢,微信 ly50247。

相關文章