2010-10-22 8 views
5

Aparentemente, esto es casi un duplicado de "Bad pipe filedescriptor when reading from stdin in python - Stack Overflow"; sin embargo, creo que este caso es un poco más complicado (y no es específico de Windows, ya que la conclusión de ese hilo fue).Linux: ingrese a la secuencia de comandos de Python (ncurses), stdin y termios

Actualmente estoy tratando de experimentar con un script simple en Python: me gustaría proporcionar entradas para el script, ya sea a través de argumentos de línea de comando; o "pipeando" una cadena a este script - y haga que el script muestre esta cadena de entrada usando una interfaz de terminal curses.

El guión completo, aquí llamado testcurses.py, se da a continuación. El problema es que cada vez que pruebo la tubería real, eso parece arruinar stdin, y la ventana curses nunca se muestra. Aquí es un terminal de salida:

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

Por lo que yo puedo ver, la cuestión es: - Cada vez que las cadenas de tubería en la secuencia de comandos de Python, la secuencia de comandos de Python pierde la referencia al terminalcomo stdin, y las comunicaciones que el stdin reemplazado ya no es una estructura termios - y dado que stdin ya no es un terminal, curses.initscr() sale inmediatamente sin procesar nada.

lo tanto, mi pregunta es - en pocas palabras: ¿hay algún modo de lograr, que la sintaxis echo "blabla" | ./testcurses.py - termina mostrando la cadena canalizado en curses? Más específicamente: ¿es posible recuperar una referencia al stdin del terminal que realiza la llamada desde un script de Python, incluso si este script está siendo "canalizado"?

Gracias de antemano por cualquier punteros,

Saludos!

 

 

PD: el guión 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 ..) 

Respuesta

1

Esto no se puede hacer sin que el proceso padre implicado. Afortunadamente, hay una manera de conseguir bash implicó el uso I/O redirection:

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

Eso será tubería foo a pipe.py en un subnivel con stdin duplicado en el descriptor de archivo 3. Ahora todo lo que tenemos que hacer es usar que ayuda adicional de nuestros padres proceso en el script en Python (ya que vamos a heredar 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() 

por último, se puede evitar la sintaxis feo en la línea de comandos ejecutando el subnivel primera:

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

Eso resuelve su problema, si tiene bash.

+0

Gracias, señor, por la respuesta concisa y operativa. :) De hecho, uso 'bash', ya que estoy en Ubuntu Lucid. Mi ejemplo, actualizado con sus cambios, se puede encontrar como [testcurses-stdin.py] (http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=75&content- type = text% 2Fplain & pathrev = 75); y debería llamarse con ''(echo" blabla "| ./testcurses-stdin.py -) 3 <& 0'' ... – sdaau

+0

_PS: Debo admitir que he visto [redirección de E/S] (http: //www.faqs.org/docs/abs/HTML/io-redirection.html) cientos de veces, también antes de publicar esto, y siempre termina confundiéndome; Realmente me hubiera costado encontrar la solución adecuada. Además, como soy muy aficionado a los one-liners, la "' sintaxis fea en la línea de comando' "es en realidad ** más ** apreciada, una de las cosas que no me gusta es ejecutar '' 'exec 3 < & 0' 'antes de ejecutar algo que es, esencialmente, un trazador de líneas_. Gracias de nuevo por la respuesta, Frédéric, ¡y aplausos! – sdaau

8
El problema es que cada vez que pruebo la tubería real, eso parece arruinar stdin, y la ventana de maldiciones nunca se muestra. [... snip ...] Por lo que puedo ver, el problema es: cada vez que canalizamos cadenas en el script de Python, el script de Python pierde la referencia al terminal como stdin y nota que el stdin reemplazado ya no es una estructura termios y como stdin ya no es un terminal, curses.initscr() sale inmediatamente sin representar nada.

En realidad, la ventana maldiciones hace espectáculo, pero ya que no hay más valiente de entrada de su nueva entrada estándar , myscreen.getch() vuelve inmediatamente. Por lo tanto, no tiene nada que ver con la prueba de maldiciones, ya sea que stdin es un terminal.

Entonces, si desea utilizar myscreen.getch() y otras funciones de entrada de cursivas, deberá volver a abrir su terminal. En los sistemas Linux y * nix generalmente hay un dispositivo llamado /dev/tty que se refiere a la terminal actual. Así que usted puede hacer algo como:

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

antes de su llamada a myscreen.getch().

+0

Gracias, ninjalj, por la buena explicación. Me ayuda mucho a comprender mejor cómo funcionan las tuberías y las E/S estándar. Por cierto, no estoy realmente interesado en usar '' myscreen.getch() ''- lo que quería hacer, es obtener datos' sin formato 'sin procesar en este script, y hacer que el script analice los datos y darles formato en la pantalla usando 'ncurses' como en," en tiempo real "(que es un conjunto de problemas diferentes) pero comprender la necesidad de duplicar' stdin' era un verdadero show stopper_). ¡Aclamaciones! – sdaau

+1

Lo curioso es que si va a tener la secuencia de comandos ejecutándose indefinidamente y no usar 'myscreen.getch()', la secuencia de comandos que publicó ya funciona, simplemente sale demasiado rápido para darse cuenta. – ninjalj

+0

PD: Solo quería decir que actualicé [testcurses-stdin.py] (http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=76&content-type= text% 2Fplain & pathrev = 75) por lo que se duplica '/ dev/tty' en lugar de' fd3' - y ahora el script se puede llamar claramente con '' echo 'blabla' | ./testcurses-stdin.py -' '. Gracias de nuevo, ninjalj - ¡y aplausos! – sdaau

Cuestiones relacionadas