標籤:style http color io os ar sp 檔案 資料
1、問題:
下午有同學問了這麼一個問題:
tail -n +$(tail -n1 /root/tmp/n) -F /root/tmp/ip.txt 2>&1| awk ‘ARGIND==1{i=$0;next}{i++;if($0~/檔案已截斷/){i=0};print $1"---"i;print i >> "/root/tmp/n"}‘ /root/tmp/n - seq 10 > /root/tmp/ip.txt && tail -f /root/tmp/n
把這兩條語句分別在同一台機器的兩個終端上執行,你會發現第二條語句的 tail 跟蹤不到結果,而第一條語句明明是有結果輸出的。
往下細說之前,咱們先簡單介紹下第一個語句幹嘛的:
這個語句是即時 tail 一份日誌,並實現了兩個小功能:
當檔案被重寫的時候將檔案的行號置 0,並且當進程掛掉後,重啟進程時,能從上次掛掉的地方開始 tail 起,類似“斷點續傳”。
不熟悉 awk 的同學看起來估計比較費勁,沒關係,咱們簡化下情境,寫個簡單的 test case 類比上面的語句(1):
{ echo 21;sleep 10;echo 22; }|awk ‘{print >> "/root/tmp/n"}‘
你會發現確實是當螢幕輸出了 21 的時候, n 的值沒有變化,但是當整個 echo 執行完成時,n 的值卻一起變化了,輸出了 21、22。
對此,你很容易寫出另一個 test case:
while [[ $i -lt 10 ]]; do ((i++)); echo $i|awk ‘{print >> "/root/tmp/n"}‘; sleep 2; done
那為什麼這個 case 能即時看到 n 的值在變化呢?別急,讀完本文,你自會找到答案。^ _ ^
其實語句(1)的問題在於 shell 下的一個概念引發的:buffer
寫過程式的同學應該知道 磁碟與記憶體,記憶體與CPU 的 IO 互動速度都不在一個量級上,那麼為了提高資料的存取效率,一般都會在軟體程式、硬體設計中採用 buffer 的設計,當 buffer 滿了才會請求一次 IO 操作,而不是一個字元或者一個位元組的方式請求 IO 操作,具體說來一般是互動會無 buffer 或者小 buffer,非互動操作一般 buffer 都會比較大,因為對使用者來說“即時性”要求不是那麼高了嘛~
語句(1) 的重新導向就是一個典型的非互動式操作,會由於 buffer 的原因,使用者無法即時的看到日誌中資料的變化。
2、解決方案:
知道原因了,咱們可以有如下幾種方式,讓 awk 中的重新導向變得即時輸出無 buffer:
{ echo 21;sleep 10;echo 22; }|awk ‘{print >> "/root/tmp/n";fflush("")}‘{ echo 21;sleep 10;echo 22; }|awk ‘{print >> "/root/tmp/n"; system("")}‘{ echo 21;sleep 10;echo 22; }|awk ‘{print >> "/root/tmp/n";close("/root/tmp/n")}‘{ echo 21;sleep 10;echo 22; }|awk ‘{system("echo "$0" >> /root/tmp/n")}‘{ echo 21;sleep 10;echo 22; }|awk ‘{print |"cat - >> /root/tmp/n"}‘
關於 fflush 的說明如下:
fflush([file]) Flush any buffers associated with the open output file or pipe file. If file is missing, then standard output is flushed. If file is the null string, then all open output files and pipes have their buffers flushed.
說道這兒,有同學或許會有疑問:還有什麼辦法去驗證是 buffer 的原因呢?
其實你調大你的輸出就行了:
{ seq 5000;sleep 10;seq 1000; }|awk ‘{print >> "/root/tmp/n"}‘
3、推而廣之
其實 linux shell 下的眾多命令都採用了 buffer 的設計,例如 grep,比如就曾經有同學問過我:
tail -f logfile | grep ‘ooxx‘ 為什麼看不到結果呢?日誌中明明就有的呀? 等等。。。
那本文在此稍稍總結下常用命令的 buffer 問題以及應對措施:
grep (e.g. GNU version 2.5.1) |
--line-buffered |
sed (e.g. GNU version 4.0.6) |
-u,--unbuffered |
awk (GNU awk) |
use the fflush() function |
awk (mawk) |
-W interactive |
tcpdump, tethereal |
-l |
例如上文提到的 grep buffer 問題:
tail -f /var/log/foo | grep --line-buffered
也有專門的命令或者工具包來解決這個問題,比如 stdbuf、unbuffer、stdbuf,或者直接調用 c 語言庫禁用 buffer:
setvbuf(stdout, 0, _IONBF, 0);
4、Refer:
[1] 9.1.4 Input/Output Functions
https://www.gnu.org/software/gawk/manual/html_node/I_002fO-Functions.html
[2] What is buffering? Or, why does my command line produce no output: tail -f logfile | grep ‘foo bar‘ | awk ...
http://mywiki.wooledge.org/BashFAQ/009
[3] How to fix stdio buffering
http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html
[4] buffering in standard streams
http://www.pixelbeat.org/programming/stdio_buffering/
[5] 關於awk中通過管道執行shell後的管道關閉問題
http://hi.baidu.com/leejun_2005/item/26a5f8273e7e3555c28d5970
[6] Why does awk do full buffering when reading from a pipe
http://unix.stackexchange.com/questions/33650/why-does-awk-do-full-buffering-when-reading-from-a-pipe
玩轉 Shell 之:Shell 命令 Buffer 知多少?