Linux:通过管道输入 Python (ncurses) 脚本、stdin 和 termios

发布于 2024-09-28 19:05:04 字数 6794 浏览 4 评论 0 原文

显然,这几乎是“读取时错误的管道文件描述符”的重复python 中的标准输入 - 堆栈内存溢出”;但是,我认为这种情况稍微复杂一些(并且它不是 Windows 特定的,因为该线程的结论是)。

我目前正在尝试用 Python 尝试一个简单的脚本:我想为脚本提供输入 - 通过命令行参数;或者通过“管道”将字符串传递到此脚本 - 并让脚本使用 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 ..)

Apparently this is almost a duplicate of "Bad pipe filedescriptor when reading from stdin in python - Stack Overflow"; however, I believe this case is slightly more complicated (and it is not Windows specific, as the conclusion of that thread was).

I'm currently trying to experiment with a simple script in Python: I'd like to supply input to the script - either through command line arguments; or by 'pipe'-ing a string to this script - and have the script show this input string using a curses terminal interface.

The full script, here called testcurses.py, is given below. The problem is that whenever I try the actual piping, that seems to mess up stdin, and the curses window never shows. Here is a terminal output:

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

As far as I can see, the issue is: - whenever we pipe strings into the Python script, the Python script loses the reference to the terminal as stdin, and notices that the replaced stdin is not a termios structure anymore - and since stdin is no longer a terminal, curses.initscr() exits immediately without rendering anything.

So, my question is - in brief: can I somehow achieve, that the syntax echo "blabla" | ./testcurses.py - ends up showing the piped string in curses? More specifically: is it possible to retrieve a reference to the calling terminal's stdin from a Python script, even if this script is being "piped" to?

Thanks in advance for any pointers,

Cheers!

 

 

PS: the testcurses.py script:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷…雨湿花 2024-10-05 19:05:04

问题是,每当我尝试实际的管道时,这似乎都会弄乱标准输入,并且诅咒窗口永远不会显示。

[...剪...]

据我所知,问题是: - 每当我们将字符串通过管道传输到 Python 脚本中时,Python 脚本都会丢失对终端作为 stdin 的引用,并注意到替换的 stdin 不再是 termios 结构 - 并且因为 stdin 是不再是终端,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() 之前。

The problem is that whenever I try the actual piping, that seems to mess up stdin, and the curses window never shows.

[...snip...]

As far as I can see, the issue is: - whenever we pipe strings into the Python script, the Python script loses the reference to the terminal as stdin, and notices that the replaced stdin is not a termios structure anymore - and since stdin is no longer a terminal, curses.initscr() exits immediately without rendering anything.

Actually, the curses window does show, but since there is no more input on your brave new stdin, myscreen.getch() returns immediately. So it has nothing to do with curses testing whether stdin is a terminal.

So if you want to use myscreen.getch() and other curses input functions, you'll have to reopen your terminal. On Linux and *nix systems there is usually a device called /dev/tty that refers to the current terminal. So you can do something like:

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

before your call to myscreen.getch().

陌伤ぢ 2024-10-05 19:05:04

如果没有父进程的参与,这是无法完成的。幸运的是,有一种方法可以使用 bash 参与进来http://www.faqs.org/docs/abs/HTML/io-redirection.html" rel="nofollow">I/O 重定向 :

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

这会将 foo 传送到 < code>pipe.py 位于子 shell 中,并将 stdin 复制到文件描述符 3 中。现在我们需要做的就是在 python 脚本中使用来自父进程的额外帮助(因为我们' ll继承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,这可以解决您的问题。

This can't be done without getting the parent process involved. Fortunately, there's a way to get bash involved using I/O redirection :

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

That will pipe foo to pipe.py in a subshell with stdin duplicated into file descriptor 3. Now all we need to do is use that extra help from our parent process in the python script (since we'll inherit 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()

Finally, you can work around the ugly syntax on the command line by running the subshell first:

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

That solves your problem, if you have bash.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文