Linux

腳本中的多個後台程序

  • March 10, 2013

假設如果我遇到需要複製一些文件並且需要很長時間的情況,那麼我將不得不去並行處理文件副本。例如,它可能看起來像這樣

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. 不要將它們交錯,即一個接一個地附加每個作業的內容。
  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 程序替換kshzsh或者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,將同時啟動並與管道相互連接j2cat

但是請注意,只有在完成後cat才會開始從第二個管道(由 寫入j2)開始讀取j1。這意味著如果j2寫入的日誌記錄超過管道的大小(例如,在 Linux 上,通常為 64kiB),j2將被阻止直到j1完成。

這可以通過使用spongefrom來避免moreutils,例如:

cat <(j1 2>&1) <(j2 2>&1 | sponge) > jobs.log

雖然這意味著 的所有輸出j2都將儲存在記憶體中,並且 cat 只會在完成後開始寫入j2in的輸出,在這種情況下,使用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 之外的任何內容),行為就會受到影響。命令,或者更確切地說是它們呼叫的stdioAPI ( , , …) 檢測到輸出不會發送到終端,並通過大塊輸出(幾千字節)來執行優化,而他們不這樣做那是標準錯誤。這意味著輸出和錯誤消息的順序將受到影響。如果這是一個問題,在 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到命令中j1j2因此它們很可能會緩衝它們的輸出,因此可能會在生成後很久才寫入。這比前一種情況更糟,因為如果你看到:

j1: doing A
j1: doing B
j2: doing C

這確實意味著j1在執行 B 之前執行了 A,但並不是說它在j2執行 C 之前執行了其中的任何一個。因此,再一次,如果出現問題,您可能需要應用stdbuf -oL更多命令。

請注意,您不能應用於stdbuf類似j1j2以上的 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...)

引用自:https://unix.stackexchange.com/questions/67392