獲取管道後台程序PID的可靠方法
我需要檢索通過管道傳輸到另一個程序的程序的 PID,這些程序一起作為 bash 中的後台作業生成。以前我只是依靠
pgrep
,但事實證明在pgrep
能夠找到該過程之前可能會有> 2s的延遲:#!/bin/bash cmd1 | cmd2 & pid=$(pgrep cmd1) # emtpy in about 1/10
我發現針對這個問題的一些常見建議是使用程序替換而不是簡單的管道 (
cmd1 >(cmd2) & pid=$!
) 或使用jobs
內置函式。程序替換執行整個子shell(對於整個執行時),所以我現在寧願使用jobs
,但我想避免兩次犯同樣的錯誤……
jobs
如果我在生成它們後立即執行查找,我可以 100% 依賴於了解這兩個程序嗎?#!/bin/bash cmd1 | cmd2 & pid=$(jobs -p %cmd1) # 10/10?
這可能是由於在後台執行作業,或者可能是一個怪癖
set -x
,但以下範例通常以任何順序列出執行的命令。到目前為止,輸出似乎是正確的,但我想完全排除在jobs
作業開始之前jobs
可以執行的可能性(或者至少不會列出兩個程序)!?jobs
#!/bin/bash set -x tail -f /dev/null | cat & jobs -l kill %tail
例子:
+ jobs -l [1]+ 2802325 Running tail -f /dev/null 2802326 | cat & + tail -f /dev/null + kill %tail
同樣,在流程替換的情況下,我可以依靠
pid=$!
始終工作嗎?它到底是為了“擴展到最近執行的後台(非同步)命令的程序ID”而設計的?
當後台作業是表單的管道時
cmd1 | cmd2
,它仍然是單個後台作業。沒有辦法知道什麼時候cmd1
開始。每個
&
創建一個後台作業。一旦cmd &
返回,shell 就會知道該後台作業:cmd & jobs
listscmd
。cmd & pid=$!
設置pid
為執行的程序 IDcmd
。管道
cmd1 | cmd2
創建了另外兩個子流程:一個要執行cmd1
,一個要執行cmd2
。這兩個程序都是執行後台作業的子程序的子程序。以下是程序樹的樣子bash -c '{ sleep 123 | sleep 456; } & jobs -p; sleep 789'
:PID PPID CMD 268 265 | \_ bash -c { sleep 123 | sleep 456; } & sleep 789 269 268 | \_ bash -c { sleep 123 | sleep 456; } & sleep 789 270 269 | | \_ sleep 123 271 269 | | \_ sleep 456 272 268 | \_ sleep 789
268 是原始的 bash 程序。269 是
jobs -p
列印的後台作業。270 和 271 是管道的左側和右側,它們都是後台作業 (269) 主程序的子程序。我測試的 bash 版本(Linux 上為 5.0.17)優化了
cmd1 | cmd2 &
沒有大括號。在這種情況下,管道的左側與後台作業在同一程序中執行:PID PPID CMD 392 389 | \_ bash -c sleep 123 | sleep 456 & jobs -p; sleep 789 393 392 | \_ sleep 123 394 392 | \_ sleep 456 395 392 | \_ sleep 789
您不能依靠這種行為在 bash 版本之間保持穩定,甚至可能跨平台、發行版、libc 版本等。
jobs -p %cmd1
查找程式碼以 . 開頭的作業cmd1
。它發現的是cmd1 | cmd2
.jobs -p %?cmd2
找到相同的工作¹。無法通過 bash 的內置功能訪問正在執行的程序 IDcmd1
。cmd2
如果您需要確定
cmd1
已經開始,請使用程序替換。cmd1 >(cmd2)
你不知道什麼時候
cmd2
開始和結束。如果您需要知道何時
cmd1
、cmd2
開始和結束,您需要讓它們同時工作,並讓它們通過命名管道進行通信。tmp=$(mktemp -d) # Remove this in cleanup code mkfifo "$tmp/pipe" cmd1 >"$tmp/pipe" & pid1=$! cmd2 <"$tmp/pipe" & pid2=$! …
該
jobs
命令在腳本中不是很有用。用於$!
記住後台作業的 PID。¹或者至少應該如此。我的版本抱怨一個模棱兩可的工作規範,這必須是一個錯誤,因為它說儘管只有一個工作。