【问题标题】:Linux: Pipe into Python (ncurses) script, stdin and termiosLinux:通过管道输入 Python (ncurses) 脚本、标准输入和 termios
【发布时间】:2011-04-29 06:51:29
【问题描述】:

显然这几乎是“Bad pipe filedescriptor when reading from stdin in python - Stack Overflow”的复制品;但是,我相信这种情况稍微复杂一些(它不是特定于 Windows 的,因为该线程的结论是)。

我目前正在尝试使用 Python 中的一个简单脚本:我想为脚本提供输入 - 通过命令行参数;或者通过 'pipe'-ing 一个字符串到这个脚本 - 并让脚本使用 curses 终端接口显示这个输入字符串。

下面给出了完整的脚本,这里称为testcurses.py。问题是,每当我尝试实际管道时,这似乎会弄乱标准输入,并且curses 窗口永远不会显示。这是一个终端输出:

## 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 ..)

【问题讨论】:

    标签: python linux pipe ncurses termios


    【解决方案1】:
    问题是,每当我尝试实际管道时,这似乎会弄乱标准输入,并且永远不会显示诅咒窗口。 [……剪断……] 据我所知,问题是: - 每当我们将字符串导入 Python 脚本时,Python 脚本都会丢失对终端的引用作为标准输入,并注意到被替换的标准输入不再是 termios 结构 - 因为标准输入是不再是终端,curses.initscr() 立即退出而不渲染任何内容。

    实际上,curses 窗口确实显示了,但由于您勇敢的新 stdin 上没有更多输入,myscreen.getch() 立即返回。所以它与 curses 测试 stdin 是否是终端无关。

    所以如果你想使用myscreen.getch() 和其他curses 输入功能,你必须重新打开你的终端。在 Linux 和 *nix 系统上,通常有一个名为 /dev/tty 的设备指向当前终端。所以你可以这样做:

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

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

    【讨论】:

    • 感谢 ninjalj 的精彩解释 - 它对我更好地理解管道和标准 I/O 的工作方式有很大帮助!顺便说一句,我对使用“myscreen.getch()”并不感兴趣——我想做的是将原始“未格式化”数据通过管道传输到此脚本中,并让脚本解析数据,并使用ncurses 在屏幕上对其进行格式化例如,“实时”(这是一整套不同的问题 - 但了解重复 stdin 的必要性是真正的阻碍)。干杯!
    • 有趣的是,如果你要让脚本无限期地运行而不使用myscreen.getch(),那么你发布的脚本已经可以工作了,它退出的速度太快了,以至于无法注意到它。跨度>
    • PS:我只是想说我更新了testcurses-stdin.py,所以它复制了/dev/tty而不是fd3——现在脚本可以用'echo "blabla" | ./testcurses-stdin.py -'简单地调用。再次感谢 ninjalj - 干杯!
    • PSS:也感谢getch 的评论——我希望我可以调试自己以获得类似的理解——但我无法得到gdb 或pdb 或strace 到在使用管道运行时执行 :) 所以对getch 的洞察力是最有价值的(基本上,我什至没有注意到它的存在:) - 但仍然需要检测退出的按键,我猜?)!干杯!
    • strace 对我来说非常适合管道:echo "hell of world" | strace -f python testpipe.py -
    【解决方案2】:

    如果不涉及父进程,就无法做到这一点。幸运的是,有一种方法可以让 bash 参与其中,使用 I/O redirection:

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

    这会将foo 传递到pipe.py 在一个子shell 中,stdin 复制到文件描述符 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()
    

    最后,您可以通过先运行 subshel​​l 来解决命令行中丑陋的语法:

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

    如果您有bash,这可以解决您的问题。

    【讨论】:

    • 谢谢你,先生,你的简洁 - 和工作 - 回答! :) 我确实使用bash,因为我在 Ubuntu Lucid 上。我的示例(根据您的更改进行了更新)可以在 testcurses-stdin.py 中找到;它应该用'(echo "blabla" | ./testcurses-stdin.py -) 3&lt;&amp;0'调用......
    • PS:我必须承认我已经看过I/O redirection 数百次了——也是在我发布这个之前——它总是让我感到困惑;我真的很难找到合适的解决方案。此外,由于我非常喜欢单行,“ugly syntax on the command line”实际上是最赞赏 - 我不喜欢的一件事是在运行之前运行“exec 3&lt;&amp;0”本质上是单线的东西。再次感谢 Frédéric 的回复,欢呼!
    • 嗨,这是一个相当古老的问题,但答案对我的一半问题非常有帮助。我现在知道如何通过管道进入我的 python 诅咒应用程序。我知道它可以将输出重定向到这里看到github.com/liaonau/chooser/blob/master/chooser.c,但我不知道如何在 python 中这样做。是否有类似于 dup2 的方法将 curses 的输出重定向到 /dev/tty 以在 curses 模式下运行应用程序但将不同的输出 pip 到下一个应用程序?
    • 好的,我想出了一些我可以忍受的东西。我仍然对我打赌我的解决方案非常好奇
    猜你喜欢
    • 2015-01-18
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 2011-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多