2010-10-22 3 views
5

По-видимому, это почти дубликат «Bad pipe filedescriptor when reading from stdin in python - Stack Overflow»; однако, я считаю, что этот случай немного сложнее (и он не зависит от Windows, так как вывод этой темы был).Linux: Протокол Python (ncurses), stdin и termios

В настоящее время я пытаюсь поэкспериментировать с простым скриптом на Python: я хотел бы предоставить входные данные сценарию - либо через аргументы командной строки; или «pipe» - строка в этом скрипте - и сценарий показывает эту строку ввода с использованием терминального интерфейса curses.

Полный сценарий, который называется testcurses.py, приведен ниже. Проблема в том, что всякий раз, когда я пытаюсь использовать фактический трубопровод, кажется, что он запутывает stdin, а окно 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? Более конкретно: можно ли получить ссылку на вызывающий терминал 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 ..) 

ответ

1

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

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

Это будет труба foo к pipe.py в субоболочке с stdin дублируется в дескрипторе файла 3. Теперь все, что нам нужно сделать, это использовать, что дополнительная помощь от нашего родителя процесс в сценарии питона (так как мы будем наследовать дескриптор 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.

+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

+0

_PS: Должен признаться, я просмотрел [перенаправление ввода/вывода] (http: //www.faqs.org/docs/abs/HTML/io-redirection.html) сотни раз - также до того, как я разместил это - и это всегда меня пугает; Мне бы очень трудно было найти правильное решение. Кроме того, поскольку я очень люблю однострочники, «уродливый синтаксис в командной строке» на самом деле ** наиболее признателен - одна из вещей, которые мне не нравятся, работает «exec» < & 0' 'перед тем, как запустить что-то, что по существу является одним liner_. Еще раз спасибо за ответ, Фредерик - и ура! – sdaau

8
Проблема в том, что всякий раз, когда я пытаюсь использовать фактический трубопровод, кажется, что он запутывает stdin, а окно проклятий никогда не отображается. [... 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, за приятное объяснение - это помогает мне с пониманием лучшего, как работают трубопроводы и стандартные операции ввода-вывода! Кстати, я не очень-то заинтересован в использовании '' myscreen.getch() ''- то, что я хотел сделать, - это« необработанные »данные канала в этот скрипт, и сценарий анализирует данные и форматирует их на экране используя «ncurses» как в «реальном времени» (который является целым рядом различных проблем, но понимание необходимости дублирования «stdin» было настоящим остановом show stop_). Ура! – sdaau

+1

Самое смешное, что если вы собираетесь использовать скрипт неограниченно и не использовать 'myscreen.getch()', скрипт, который вы опубликовали, уже работает, он просто выходит слишком быстро, чтобы заметить его. – ninjalj

+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 -'. Еще раз спасибо, нинжал - и ура! – sdaau

Смежные вопросы