for line in sys.stdin
糾結一個下午和一個晚上了,我想在syslog-ng里面添加一個program destination,程序是用Python寫的,結果發現file destination總是在第一時間就能收到消息,而program則沒有什么動靜,反復測試了好多遍都是如此。man Python,發現了Python有個’-u’參數,man是這樣說的。
-u Force stdin, stdout and stderr to be totally unbuffered. On systems where it matters, also put stdin, std-out and stderr in binary mode. Note that there is internal buffering in xreadlines(), readlines() and file-object iterators (“for line in sys.stdin”) which is not influenced by this option. To work around this, you will want to use “sys.stdin.readline()” inside a “while 1:” loop.
加上’-u’之后,標准輸出打印的內容很快能進入日志文件中,標准輸入還是沒有動靜。當時沒有仔細看這段說明,里面已經指出了for line in sys.stdin並不受影響,而我的代碼偏偏是這樣從標准輸入里面讀數據的。后來無意中在stackoverflow發現有一個人說這樣迭代方式需要等到EOF出現才會開始執行,如果使用sys.stdin.readline()就不會有問題,測試了下發現果然是好用的。
下面兩個例子可以說明問題。在終端中分別運行兩個程序,第一種遍歷方式會等到敲入CRTL+D才會打印輸入的內容。第二種方式輸入一行,回車之后就會打印這行。
010203040506070809101112#!/bin/env
import
sys
for
line
in
sys.stdin:
line,
line
=
sys.stdin.readline()
while
line:
line,
line
=
sys.stdin.readline()
奇怪的是,我寫Hadoop Streaming Job時,一直都用for line in sys.stdin這種方式遍歷,也沒有出過問題。Hadoop Streaming官方文檔里面的例子用的是readline這種方法。我猜這個應該是Hadoop的數據都保存在本地了,等於用cat的方式給腳本送數據,所以沒有問題。
在網上查資料的時候還發現有人反饋Python使用for line in sys.stdin的一個bug:Issue1633941,就是需要輸入兩次CRTL+D程序才會退出。Ralph Corderoy指出這個和Python 2.6用fread導致的問題,大概的意思是fread讀到的數據長度為0時,它才認為獲取到了EOF。如果沒有得到指定長度的數據,即使數據后面存在EOF,它也會忽略。解決辦法是在循環內使用feof對stdin進行一次判斷,如果結束了就立即退出。