2010-10-22 14 views
5

Anscheinend ist dies fast ein Duplikat von "Bad pipe filedescriptor when reading from stdin in python - Stack Overflow"; Ich glaube jedoch, dass dieser Fall etwas komplizierter ist (und es ist nicht Windows-spezifisch, da die Schlussfolgerung dieses Threads war).Linux: Pipe in Python (ncurses) Skript, stdin und termios

Ich versuche gerade mit einem einfachen Skript in Python zu experimentieren: Ich würde gerne eine Eingabe für das Skript liefern - entweder über Befehlszeilenargumente; oder indem Sie eine Zeichenfolge an dieses Skript "pipen" - und das Skript diese Eingabezeichenfolge mit einer curses Terminalschnittstelle anzeigen lässt.

Das vollständige Skript, hier testcurses.py genannt, ist unten angegeben. Das Problem ist, dass, wenn ich die tatsächliche Rohrleitung versuche, das stdin zu versauen scheint, und das curses Fenster nie zeigt. Hier ist eine Terminal-Ausgabe:

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

Soweit ich sehen kann, ist die Frage: - wenn wir Rohrstränge in den Python-Skript, verlieren der Python-Skript den Verweis auf den Terminal als stdin und Bekanntmachungen dass die ersetzte stdin ist keine termios Struktur mehr - und seit stdin ist nicht mehr ein Terminal, curses.initscr() beendet sofort ohne Rendering nichts.

Also, meine Frage ist - kurz: kann ich irgendwie erreichen, dass die Syntax echo "blabla" | ./testcurses.py - in curses die verrohrt Zeichenfolge zeigt endet? Genauer gesagt: Ist es möglich, einen Verweis auf die stdin des aufrufenden Terminals aus einem Python-Skript abzurufen, selbst wenn dieses Skript "piped" wird?

Vielen Dank im Voraus für alle Hinweise,

Prost!

 

 

PS: das testcurses.py Skript:

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

Antwort

1

Dies kann nicht ohne sich den übergeordneten Prozess beteiligt erfolgen. Glücklicherweise gibt es eine Möglichkeit, bash beteiligt mit I/O redirection zu bekommen:

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

Das wird Rohr foo-pipe.py in einer Subshell mit stdin in Dateideskriptors dupliziert 3. Jetzt müssen wir tun müssen, ist die Verwendung, die zusätzliche Hilfe von unserer Mutter Prozess in dem python-Skript (da wir fd 3 erben werden):

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

Schließlich können Sie rund um die hässliche Syntax in der Befehlszeile arbeiten, indem sie die Subshell läuft zuerst:

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

Das löst Ihr Problem, wenn Sie bash haben.

+0

Danke, mein Herr, für die prägnante - und funktionierende - Antwort! :) Ich benutze tatsächlich 'bash', da ich auf Ubuntu Lucid bin. Mein Beispiel, das mit Ihren Änderungen aktualisiert wurde, finden Sie unter [testcurses-stdin.py] (http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=75&content- type = text% 2Fplain & pathrev = 75); und es sollte mit ''(echo" blabla "| ./testcurses-stdin.py -) aufgerufen werden 3 <& 0'' ... – sdaau

+0

_PS: Ich muss zugeben, ich habe [I/O Umleitung] (http: //www.faqs.org/docs/abs/HTML/io-redirection.html) hunderte Male - auch bevor ich das gepostet habe - und es endet immer damit, mich zu verwirren; Ich hätte es wirklich schwer gehabt, die richtige Lösung herauszufinden. Da ich One-Liners sehr mag, ist die "hässliche Syntax auf der Kommandozeile" tatsächlich ** am meisten geschätzt - eines der Dinge, die ich nicht mag, ist die Ausführung von "exec 3" & 0 "vor dem Ausführen von etwas, das im Wesentlichen ein Einstrich ist. Nochmals vielen Dank für die Antwort, Frédéric - und Prost! – sdaau

8
Das Problem ist, dass, wann immer ich die tatsächliche Rohrleitung versuche, das stdin zu versauen scheint, und das Flursfenster wird nie angezeigt. [... schnippen ...] So weit ich sehe, ist das Problem: - Immer wenn wir Strings in das Python-Skript pipettieren, verliert das Python-Skript den Verweis auf das Terminal als stdin und bemerkt, dass die ersetzte stdin keine termios-Struktur mehr ist - Da stdin kein Terminal mehr ist, wird curses.initscr() sofort beendet, ohne etwas zu rendern.

Eigentlich zeigt das Flurs-Fenster, aber da es keine Eingabe mehr auf Ihrer mutigen neuen stdin gibt, kehrt myscreen.getch() sofort zurück. Es hat also nichts mit Flüchen zu tun, ob stdin ein Terminal ist.

Wenn Sie also myscreen.getch() und andere Curses-Eingabefunktionen verwenden möchten, müssen Sie Ihr Terminal erneut öffnen. Auf Linux- und * nix-Systemen gibt es normalerweise ein Gerät namens /dev/tty, das sich auf das aktuelle Terminal bezieht. So können Sie so etwas wie:

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

vor Ihrem Anruf myscreen.getch().

+0

Danke, ninjalj, für die nette Erklärung - es hilft mir sehr, besser zu verstehen, wie Piping und Standard I/O funktionieren! Übrigens, ich bin nicht wirklich daran interessiert, '' myscreen.getch() ''zu verwenden - was ich tun wollte, ist rohe' unformatierte 'Daten in dieses Skript, und das Skript analysiert die Daten und formatiert sie auf dem Bildschirm "ncurses" wie in "real-time" zu verwenden (_ das ist eine ganze Reihe von verschiedenen Problemen - aber die Notwendigkeit der Duplizierung von 'stdin' zu verstehen war ein echter Show-Stopper_). Prost! – sdaau

+1

Die lustige Sache ist, dass, wenn Sie das Skript auf unbestimmte Zeit laufen lassen und nicht 'myscreen.getch()' verwenden, das Skript, das Sie bereits veröffentlicht haben, es viel zu schnell beendet, um es zu bemerken. – ninjalj

+0

PS: Ich wollte nur sagen, dass ich [testcurses-stdin.py] (http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-sstdin.py?revision=76&content-type=) aktualisiert habe text% 2Fplain & pathrev = 75) so dupliziert es '/ dev/tty' anstelle von' fd3' - und nun kann das Skript einfach mit 'echo' blabla "| aufgerufen werden ./testcurses-stdin.py -''. Nochmals vielen Dank, Ninjalj - und Prost! – sdaau

Verwandte Themen