Linux

Linux中的“洩漏”管道

  • December 10, 2018

假設您有如下管道:

$ a | b

如果b停止處理標準輸入,一段時間後管道會填滿,並a從其標準輸出寫入,將阻塞(直到b再次開始處理或死亡)。

如果我想避免這種情況,我可能會想使用更大的管道(或者,更簡單地說,buffer(1)),如下所示:

$ a | buffer | b

這只會為我爭取更多時間,但最終a會停止。

我希望擁有的(對於我正在解決的一個非常具體的場景)是有一個“洩漏”管道,當它滿時,會從緩衝區中刪除一些數據(理想情況下,逐行)讓a繼續處理(正如您可能想像的那樣,在管道中流動的數據是可消耗的,即處理數據b不如a能夠在沒有阻塞的情況下執行重要)。

總而言之,我希望有一個有界的、洩漏的緩衝區:

$ a | leakybuffer | b

我可能可以很容易地用任何語言實現它,我只是想知道我是否缺少“準備使用”的東西(或類似 bash 單線的東西)。

注意:在範例中我使用的是正常管道,但這個問題同樣適用於命名管道


雖然我在下面給出了答案,但我也決定實現leakybuffer 命令,因為下面的簡單解決方案有一些限制:https ://github.com/CAFxX/leakybuffer

最簡單的方法是通過一些設置非阻塞輸出的程序進行管道傳輸。這是簡單的 perl oneliner(您可以將其保存為leakybuffer),它這樣做:

所以你a | b變成:

a | perl -MFcntl -e \
   'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

所做的是讀取輸入並寫入輸出(cat(1)與錯誤。程序是一種你想要的行緩衝,但請參閱下面的警告。

你可以案例如測試:

seq 1 500000 | perl -w -MFcntl -e \
   'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
   while read a; do echo $a; done > output

您將獲得output帶有失去行的文件(確切的輸出取決於您的 shell 的速度等),如下所示:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

您會看到 shell 在哪裡失去了之後的行12773,但也是一個異常 - perl 沒有足夠的緩衝區,12774\n但是這樣做了,1277所以它只寫了那個 - 所以下一個數字75610不會從行的開頭開始,所以它很少醜陋。

這可以通過讓 perl 檢測何時寫入沒有完全成功來改進,然後嘗試刷新剩餘的行,同時忽略新的行,但這會使 perl 腳本更加複雜,所以留作練習感興趣的讀者:)

更新(對於二進製文件): 如果您不處理換行符終止的行(如日誌文件或類似文件),則需要稍微更改命令,否則 perl 將消耗大量記憶體(取決於換行符在輸入中出現的頻率):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

它也適用於二進製文件(不消耗額外的記憶體)。

Update2 - 更好的文本文件輸出: 避免輸出緩衝區(syswrite而不是print):

seq 1 500000 | perl -w -MFcntl -e \
   'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
   while read a; do echo $a; done > output

似乎為我解決了“合併行”的問題:

12766
12767
12768
16384
16385
16386

(注意:可以驗證在哪些線路上輸出被切斷:perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)

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