Linux: скрипт в Python (ncurses), стандартный ввод и termios - PullRequest
5 голосов
/ 22 октября 2010

Видимо, это почти копия " Файловый дескриптор Bad pipe при чтении из stdin в python - Stack Overflow ";тем не менее, я считаю, что этот случай немного сложнее ( и он не специфичен для 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 теряет ссылку на терминал as stdin и замечает, что замененный stdin больше не является структурой termios - и поскольку stdin больше не является терминалом, curses.initscr() завершается немедленно, ничего не рендерив.

Итак, мой вопрос - вкратце: могу ли я как-то добиться, чтобы синтаксис echo "blabla" | ./testcurses.py - заканчивался тем, что строка в канале curses отображается?Более конкретно: возможно ли получить ссылку на stdin вызывающего терминала из сценария Python, даже если этот сценарий «передается по каналу»?

Заранее спасибо за любые указатели,

Приветствия!

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

Ответы [ 2 ]

10 голосов
/ 23 октября 2010
Проблема в том, что всякий раз, когда я пробую реальный трубопровод, это, кажется, портит стандартный ввод, и окно проклятий никогда не отображается.[... snip ...] Насколько я вижу, проблема заключается в следующем: - всякий раз, когда мы передаем строки в скрипт Python, скрипт Python теряет ссылку на терминал как stdin и замечает, что замененный stdin не являетсяструктура termios - и так как stdin больше не является терминалом, curses.initscr () немедленно завершает работу, ничего не рендерив. stdin , myscreen.getch() возвращается немедленно.Так что это не имеет ничего общего с проверкой curses, является ли stdin терминалом.

Так что, если вы хотите использовать myscreen.getch() и другие функции ввода curses, вам придется снова открыть терминал,В системах Linux и * nix обычно существует устройство под названием /dev/tty, которое относится к текущему терминалу.Таким образом, вы можете сделать что-то вроде:

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

до вашего звонка на myscreen.getch().

1 голос
/ 22 октября 2010

Этого нельзя сделать без вовлечения родительского процесса. К счастью, есть способ привлечь bash , используя перенаправление ввода / вывода :

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

Это перенаправит foo в pipe.py в подоболочке с 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()

Наконец, вы можете обойти уродливый синтаксис в командной строке, запустив сначала подоболочку:

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

Это решит вашу проблему, если у вас есть bash.

...