2010-10-22 82 views
5

顯然這幾乎是「Bad pipe filedescriptor when reading from stdin in python - Stack Overflow」的副本;然而,我相信這種情況稍微複雜一些(,它不是Windows特定的,因爲該線程的結論是)。我正在嘗試在Python中使用一個簡單的腳本:我想爲腳本提供輸入 - 通過命令行參數;或者通過'將字符串'輸入到該腳本中 - 並使腳本使用終端界面顯示此輸入字符串。Linux:管入Python(ncurses)腳本,stdin和termios

下面給出了完整的腳本,這裏叫做testcurses.py。問題是,每當我嘗試實際的管道時,這似乎搞糟標準輸入,並且窗口從不顯示。這裏是一個終端輸出:

## CASE 1: THROUGH COMMAND LINE ARGUMENT (arg being stdin): 
## 
$ ./testcurses.py - 
['-'] 1 
stdout/stdin (obj): <open file '<stdout>', mode 'w' at 0xb77dc078> <open file '<stdin>', mode 'r' at 0xb77dc020> 
stdout/stdin (fn): 1 0 
env(TERM): xterm xterm 
stdin_termios_attr [27906, 5, 1215, 35387, 15, 15, ['\x03', ... '\x00']] 
stdout_termios_attr [27906, 5, 1215, 35387, 15, 15, ['\x03', ... '\x00']] 
opening - 
obj <open file '<stdin>', mode 'r' at 0xb77dc020> 
TYPING blabla HERE 
wr TYPING blabla HERE 

at end 
before curses TYPING blabla HERE 
# 
# AT THIS POINT: 
# in this case, curses window is shown, with the text 'TYPING blabla HERE' 
# ################ 


## CASE 2: THROUGH PIPE 
## 
## NOTE I get the same output, even if I try syntax as in SO1057638, like: 
## python -c "print 'TYPING blabla HERE'" | python testcurses.py - 
## 
$ echo "TYPING blabla HERE" | ./testcurses.py - 
['-'] 1 
stdout/stdin (obj): <open file '<stdout>', mode 'w' at 0xb774a078> <open file '<stdin>', mode 'r' at 0xb774a020> 
stdout/stdin (fn): 1 0 
env(TERM): xterm xterm 
stdin_termios_attr <class 'termios.error'>::(22, 'Invalid argument') 
stdout_termios_attr [27906, 5, 1215, 35387, 15, 15, ['\x03', '\x1c', '\x7f', '\x15', '\x04', '\x00', '\x01', '\xff', '\x11', '\x13', '\x1a', '\xff', '\x12', '\x0f', '\x17', '\x16', '\xff', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00']] 
opening - 
obj <open file '<stdin>', mode 'r' at 0xb774a020> 
wr TYPING blabla HERE 

at end 
before curses TYPING blabla HERE 
# 
# AT THIS POINT: 
# script simply exits, nothing is shown 
# ################ 

據我所看到的,問題是: - 每當我們管柱到Python腳本,Python的腳本失去了參考終端stdin和通告替換的stdin不再是termios結構 - 並且由於stdin不再是終端,所以curses.initscr()立即退出而不呈現任何內容。

所以,我問題是 - 簡言之:我能以某種方式實現,即語法echo "blabla" | ./testcurses.py -最終顯示curses的管道串?更具體地說:是否可以從Python腳本中檢索對主叫終端的stdin的引用,即使此腳本正在「傳送」到?

在此先感謝您的指點,

乾杯!

 

 

PS:在testcurses.py腳本:

#!/usr/bin/env python 
# http://www.tuxradar.com/content/code-project-build-ncurses-ui-python 
# http://diveintopython.net/scripts_and_streams/stdin_stdout_stderr.html 
# http://bytes.com/topic/python/answers/42283-curses-disable-readline-replace-stdin 
# 
# NOTE: press 'q' to exit curses - Ctrl-C will screw up yer terminal 

# ./testcurses.py "blabla"     # works fine (curseswin shows) 
# ./testcurses.py -      # works fine, (type, enter, curseswins shows): 
# echo "blabla" | ./testcurses.py "sdsd"  # fails to raise curses window 
# 
# NOTE: when without pipe: termios.tcgetattr(sys.__stdin__.fileno()): [27906, 5, 1215, 35387, 15, 15, ['\x03', 
# NOTE: when with pipe | : termios.tcgetattr(sys.__stdin__.fileno()): termios.error: (22, 'Invalid argument') 

import curses 
import sys 
import os 
import atexit 
import termios 

def openAnything(source):    
    """URI, filename, or string --> stream 

    http://diveintopython.net/xml_processing/index.html#kgp.divein 

    This function lets you define parsers that take any input source 
    (URL, pathname to local or network file, or actual data as a string) 
    and deal with it in a uniform manner. Returned object is guaranteed 
    to have all the basic stdio read methods (read, readline, readlines). 
    Just .close() the object when you're done with it. 
    """ 
    if hasattr(source, "read"): 
     return source 

    if source == '-': 
     import sys 
     return sys.stdin 

    # try to open with urllib (if source is http, ftp, or file URL) 
    import urllib       
    try:         
     return urllib.urlopen(source)  
    except (IOError, OSError):    
     pass        

    # try to open with native open function (if source is pathname) 
    try:         
     return open(source)    
    except (IOError, OSError):    
     pass        

    # treat source as string 
    import StringIO      
    return StringIO.StringIO(str(source)) 



def main(argv): 

    print argv, len(argv) 
    print "stdout/stdin (obj):", sys.__stdout__, sys.__stdin__ 
    print "stdout/stdin (fn):", sys.__stdout__.fileno(), sys.__stdin__.fileno() 
    print "env(TERM):", os.environ.get('TERM'), os.environ.get("TERM", "unknown") 

    stdin_term_attr = 0 
    stdout_term_attr = 0 
    try: 
     stdin_term_attr = termios.tcgetattr(sys.__stdin__.fileno()) 
    except: 
     stdin_term_attr = "%s::%s" % (sys.exc_info()[0], sys.exc_info()[1]) 
    try: 
     stdout_term_attr = termios.tcgetattr(sys.__stdout__.fileno()) 
    except: 
     stdout_term_attr = `sys.exc_info()[0]` + "::" + `sys.exc_info()[1]` 
    print "stdin_termios_attr", stdin_term_attr 
    print "stdout_termios_attr", stdout_term_attr 


    fname = "" 
    if len(argv): 
     fname = argv[0] 

    writetxt = "Python curses in action!" 
    if fname != "": 
     print "opening", fname 
     fobj = openAnything(fname) 
     print "obj", fobj 
     writetxt = fobj.readline(100) # max 100 chars read 
     print "wr", writetxt 
     fobj.close() 
     print "at end" 

    sys.stderr.write("before ") 
    print "curses", writetxt 
    try: 
     myscreen = curses.initscr() 
     #~ atexit.register(curses.endwin) 
    except: 
     print "Unexpected error:", sys.exc_info()[0] 

    sys.stderr.write("after initscr") # this won't show, even if curseswin runs fine 

    myscreen.border(0) 
    myscreen.addstr(12, 25, writetxt) 
    myscreen.refresh() 
    myscreen.getch() 

    #~ curses.endwin() 
    atexit.register(curses.endwin) 

    sys.stderr.write("after end") # this won't show, even if curseswin runs fine 


# run the main function - with arguments passed to script: 
if __name__ == "__main__": 
    main(sys.argv[1:]) 
    sys.stderr.write("after main1") # these won't show either, 
sys.stderr.write("after main2")  # (.. even if curseswin runs fine ..) 

回答

1

這不能沒有得到參與父進程完成。幸運的是,有一種方式來獲得bash涉及使用I/O redirection

$ (echo "foo" | ./pipe.py) 3<&0 

這將管foopipe.pystdin子shell複製到文件描述符3,現在我們需要做的是使用從父項額外的幫助在python腳本程序(因爲我們會繼承FD 3):

#!/usr/bin/env python 

import sys, os 
import curses 

output = sys.stdin.readline(100) 

# We're finished with stdin. Duplicate inherited fd 3, 
# which contains a duplicate of the parent process' stdin, 
# into our stdin, at the OS level (assigning os.fdopen(3) 
# to sys.stdin or sys.__stdin__ does not work). 
os.dup2(3, 0) 

# Now curses can initialize. 
screen = curses.initscr() 
screen.border(0) 
screen.addstr(12, 25, output) 
screen.refresh() 
screen.getch() 
curses.endwin() 

最後,可以解決在命令行上醜陋的語法先運行子shell:

$ exec 3<&0 # spawn subshell 
$ echo "foo" | ./pipe.py # works 
$ echo "bar" | ./pipe.py # still works 

解決了您的問題,如果您有bash

+0

謝謝,先生,簡明扼要 - 工作 - 答案! :)我確實使用'bash',因爲我在Ubuntu Lucid上。我的例子,更新了您的更改,可以找到[testcurses-stdin.py](http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=75&content-類型=文本%2Fplain&pathrev = 75);它應該用''(echo「blabla」| ./testcurses-stdin.py - )3 <'0''... – sdaau 2010-10-22 21:17:11

+0

_PS:我必須承認我已經看過[I/O重定向](http: //www.faqs.org/docs/abs/HTML/io-redirection.html)數百次 - 在我發佈之前 - 並且總是讓我困惑;我真的很難找出適當的解決方案。另外,因爲我非常喜歡單行程序,所以「命令行中的醜陋語法」實際上是**最受讚賞的 - 我不喜歡的其中一項是運行''exec 3 <在運行某件事情之前,本質上是一個班輪_。再次感謝Frédéric的回覆 - 歡呼! – sdaau 2010-10-22 21:17:37

8
問題是,每當我嘗試實際的管道時,這似乎搞砸了標準輸入,並且詛咒窗口從不顯示。 [... snip ...] 據我所見,問題是: - 每當我們將字符串輸入到Python腳本中時,Python腳本就會丟失對stdin的引用,並且發現被替換的stdin不再是termios結構 - 由於stdin不再是終端,curses.initscr()立即退出而不呈現任何內容。

實際上,curses窗口確實顯示,但由於沒有更多的輸入對您的勇敢新stdin,myscreen.getch()立即返回。所以它與詛咒測試無關,是否stdin是一個終端。

所以,如果你想使用myscreen.getch()和其他詛咒輸入功能,你將不得不重新打開你的終端。在Linux和* nix系統上,通常會有一個稱爲/dev/tty的設備引用當前終端。所以你可以這樣做:

f=open("/dev/tty") 
os.dup2(f.fileno(), 0) 

在您致電myscreen.getch()之前。

+0

非常感謝ninjalj的解釋 - 它可以幫助我更好地理解管道和標準I/O如何工作!順便說一句,我對使用'myscreen.getch()''並不是很感興趣 - 我想做的是將原始'未格式化'的數據導入到這個腳本中,然後讓腳本解析數據並在屏幕上格式化使用'ncurses'作爲「實時」(這是一整套不同的問題 - 但理解複製stdin的需要是一個真正的展示瓶頸)。乾杯! – sdaau 2010-10-22 21:24:41

+1

有趣的是,如果您要讓腳本無限期地運行並且不使用'myscreen.getch()',那麼您發佈的腳本已經正常工作,它只是以太快的速度注意到它。 – ninjalj 2010-10-22 21:39:50

+0

PS:只是想說我更新了[testcurses-stdin.py](http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=76&content-type= text%2Fplain&pathrev = 75),所以它複製'/ dev/tty'而不是'fd3' - 現在腳本可以用''echo'blabla「| ./testcurses-stdin.py -''。再次感謝ninjalj - 歡呼! – sdaau 2010-10-22 21:49:21