2010-10-22 81 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