腳本中的多個後台程序
假設如果我遇到需要複製一些文件並且需要很長時間的情況,那麼我將不得不去並行處理文件副本。例如,它可能看起來像這樣
for i in ipaddresslist do cp x y & //running in back ground or some other process in background done wait //will cause till all the files are copied or whatever process if all are finished
現在我已經使用
wait
了,以便完成所有背景流程,但其中有一些案例1)一些文件複製可能會更早發生,所以如果我必須對這些文件進行一些處理,我必須等到所有文件都被複製。
2)如果複製程序(或在後台執行的任何其他程序)寫入日誌文件,則日誌文件可能會出現亂碼,每個後台程序都試圖同時寫入文件。
是否有解決此類問題的方法,1)我的意思是,如果我可以知道該過程已完成,那麼我可以開始對該過程的特定實例(例如文件副本)進行其餘處理(如果已完成)。此外,寫入日誌文件可能是有序的。
請提出建議
如果在複製某些文件後需要啟動某些作業,只需將其作為後台作業的一部分:
(cp this /there && start job that needs this in /there) & (cp that /here && start job that needs that in /here) & wait
(最後一個
&
不是必需的)。現在對於更複雜的依賴項,您可以使用 GNU
make -j
。make -j2 -f /dev/fd/3 3<< 'EOF' all: j1 j2 j3 .PHONY: cp1 cp2 cp3 j1 j2 j3 all cp1: cp this /there cp2: cp that /here cp3: cp this /here j1: cp1 start job that needs this in /there j2: cp2 start job that needs that in /here j3: cp1 cp3 start job that needs this in /here and /there EOF
-j2
在任何給定時間最多可以執行 2 個作業,並且會尊重依賴關係。現在為避免日誌文件出現亂碼,您有兩個主要選項
- 不要將它們交錯,即一個接一個地附加每個作業的內容。
- 嘗試確保它們很好地交錯,可能標記每個作業的每一行,以便更容易查看哪一行屬於哪個作業。
對於 1,最簡單的方法是將每個作業輸出儲存在單獨的文件中,然後將它們合併:
(cp this /there && start job that needs this in /there) > j1.log 2>&1 & (cp that /here && start job that needs that in /here) > j2.log 2>&1 & wait cat j1.log j2.log > jobs.log
另一種選擇是使用管道收集每個作業的輸出並
cat
合併它們。中可用的 Shell 程序替換ksh
,zsh
或者bash
可以幫助我們解決這個問題,甚至負責後台處理:j1() { cp this /there && start job that needs this in /there; } j2() { cp that /here && start job that needs that in /here; } cat <(j1 2>&1) <(j2 2>&1) > jobs.log
j1
,將同時啟動並與管道相互連接j2
。cat
但是請注意,只有在完成後
cat
才會開始從第二個管道(由 寫入j2
)開始讀取j1
。這意味著如果j2
寫入的日誌記錄超過管道的大小(例如,在 Linux 上,通常為 64kiB),j2
將被阻止直到j1
完成。這可以通過使用
sponge
from來避免moreutils
,例如:cat <(j1 2>&1) <(j2 2>&1 | sponge) > jobs.log
雖然這意味著 的所有輸出
j2
都將儲存在記憶體中,並且 cat 只會在完成後開始寫入j2
in的輸出,在這種情況下,使用example 可能更可取:jobs.log``j2``pv -qB 100M
cat <(j1 2>&1) <(j2 2>&1 | pv -qB 100M) > jobs.log
這種方式
j2
只會在(加上兩個管道內容)輸出日誌j1
後暫停(如果尚未完成) ,並且不會等待完成後再輸出到標準輸出。100M``pv``j2
請注意,對於上述所有內容,您需要注意,一旦將大多數命令的輸出重定向到文件或管道(除了 tty 之外的任何內容),行為就會受到影響。命令,或者更確切地說是它們呼叫的
stdio
API ( , , …) 檢測到輸出不會發送到終端,並通過大塊輸出(幾千字節)來執行優化,而他們不這樣做那是標準錯誤。這意味著輸出和錯誤消息的順序將受到影響。如果這是一個問題,在 GNU 系統或 FreeBSD(至少)上以及對於動態連結的命令,您可以使用以下命令:libc``printf``fputs``fwrite``stdbuf
stdbuf -oL j1 > j1.log 2>&1
代替:
j1 > j1.log 2>&1
確保 stdio 輸出是行緩衝的(每行輸出將在完成後立即單獨寫入)。
對於選項 2,寫入小於
PIPE_BUF
字節的管道,在 Linux 上是 4096 字節,比您的平均日誌行大得多,保證是原子的,也就是說,如果兩個程序同時寫入同一個管道,它們的保證 2 次寫入不會交織在一起。正常文件沒有這樣的保證,但我嚴重懷疑小於 4kiB 的 2 次寫入最終會在任何作業系統或文件系統上交織在一起。因此,如果不是用於上述緩衝,並且如果日誌行單獨作為一個整體單獨輸出,則您可以保證輸出的行不會包含該作業的一行和一行其他工作的行。
但是,沒有什麼可以阻止命令在正在寫入的行的兩個部分(如
printf("foo"); fflush(stdout); printf("bar\n");
)之間進行刷新,並且 stderr 上沒有緩衝。另一個問題是,一旦所有作業的行交錯,就很難分辨哪一行是針對哪個作業的。
您可以通過執行以下操作來解決這兩個問題:
tag() { stdbuf -oL sed "s%^%$1: %"; } { j1 2>&1 | tag j1 & j2 2>&1 | tag j2 } | cat > jobs.log
(請注意,我們不需要
wait
(並且它在大多數 shell 中無論如何都不會工作),因為cat
直到沒有人再寫入管道才會完成,所以直到j1
並且j2
已經終止)。上面我們使用
| cat
具有原子性保證的管道。我們將每個命令的輸出通過管道傳輸到一個命令,該命令用作業名稱標記每一行。j1
並且j2
可以隨心所欲地編寫他們的輸出,sed
(因為stdbuf -oL
)將作為一個整體和單獨的行(帶有標籤前綴)輸出,這將保證輸出不會被破壞。與上面相同的註釋仍然適用:我們沒有應用
stdbuf -oL
到命令中j1
,j2
因此它們很可能會緩衝它們的輸出,因此可能會在生成後很久才寫入。這比前一種情況更糟,因為如果你看到:j1: doing A j1: doing B j2: doing C
這確實意味著
j1
在執行 B 之前執行了 A,但並不是說它在j2
執行 C 之前執行了其中的任何一個。因此,再一次,如果出現問題,您可能需要應用stdbuf -oL
更多命令。請注意,您不能應用於
stdbuf
類似j1
或j2
以上的 shell 函式,但至少對於 GNU 和 FreeBSD stdbuf,您可以使用它來stdbuf
全域設置或基於每個子 shell:stdbuf_LD_PRELOAD=$(stdbuf sh -c 'export -p LD_PRELOAD') line_buffered_output() { eval "$stdbuf_LD_PRELOAD" export _STDBUF_O=L } j1() (line_buffered_output; cp this /there && start...)