Linux中的“洩漏”管道
假設您有如下管道:
$ 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
而不是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=$_' output
oneliner)