2010-10-22 7 views
5

Podobno jest to prawie duplikat "Bad pipe filedescriptor when reading from stdin in python - Stack Overflow"; jednak uważam, że ta sprawa jest nieco bardziej skomplikowana (i nie jest specyficzna dla systemu Windows, ponieważ konkluzją tego wątku było).Linux: Pipe do skryptu Python (ncurses), stdin i termy

Obecnie próbuję eksperymentować z prostym skryptem w Pythonie: Chciałbym podać dane wejściowe do skryptu - za pomocą argumentów wiersza poleceń; lub przez 'pipe'-ing string do tego skryptu - i niech skrypt wyświetli ten ciąg wejściowy za pomocą interfejsu terminala curses.

Pełny skrypt, nazywany tutaj testcurses.py, podano poniżej. Problem polega na tym, że za każdym razem, gdy próbuję rzeczywistej instalacji, wydaje się, że zepsuło się stdin, a okno curses nigdy się nie wyświetla. Oto wyjściowy zacisk:

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

O ile widzę, że problem jest: - Zawsze, kiedy ciągi rur do skryptu Pythona skrypt Pythona traci odniesienie do terminalajak stdin i uwagi że zastąpiony stdin nie jest już strukturą termios - a ponieważ stdin nie jest już terminalem, curses.initscr() wychodzi natychmiast bez renderowania czegokolwiek.

Więc moje pytaniejest - w skrócie: Mogę jakoś osiągnąć, że składnia echo "blabla" | ./testcurses.py - kończy się pokazując rurami ciąg w curses? Dokładniej: czy można uzyskać odwołanie do stdin wywołującego terminala ze skryptu Python, nawet jeśli ten skrypt jest "podłączony" do?

Z góry dziękuję za wszelkie wskazówki,

Pozdrawiam!

 

 

PS: testcurses.py scenariusz:

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

Odpowiedz

1

To nie może być wykonane bez uzyskania procesu nadrzędnego zaangażowana. Na szczęście istnieje sposób, aby uzyskać bash związanego z użyciem I/O redirection:

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

To będzie rura foo do pipe.py w podpowłoce z stdin duplikowane w pliku deskryptora 3. Teraz musimy zrobić jest użycie dodatkowej pomocy z naszej rodzica proces w skrypcie Pythona (ponieważ będziemy dziedziczyć 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() 

Wreszcie, można obejść brzydkiej składni w wierszu polecenia przez pierwsze uruchomienie powłoki w tle:

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

To rozwiązuje twój problem, jeśli masz bash.

+0

Dziękuję, proszę pana, za zwięzłą - i działającą - odpowiedź! :) Rzeczywiście używam 'bash', ponieważ jestem na Ubuntu Lucid. Mój przykład, zaktualizowany zmianami, można znaleźć jako [testcurses-stdin.py] (http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=75&content- type = text% 2Fplain i pathrev = 75); i powinno być wywoływane z '' (echo "blabla" | |/testcurses-stdin.py -) 3 <& 0'' ... – sdaau

+0

_PS: Muszę przyznać, że przyjrzałem się [przekierowaniu I/O] (http: //www.faqs.org/docs/abs/HTML/io-redirection.html) setki razy - także zanim to opublikowałem - i zawsze kończy się to myleniem; Naprawdę trudno byłoby mi znaleźć właściwe rozwiązanie. Ponadto, ponieważ bardzo lubię jednolinijki, "brzydka składnia w linii poleceń" jest w rzeczywistości ** najbardziej ** doceniana - jedną z rzeczy, których nie lubię, jest uruchamianie '' 'exec 3 < & 0' 'przed uruchomieniem czegoś, co jest zasadniczo jednym linerem_. Jeszcze raz dziękuję za odpowiedź, Frédéric - i okrzyki! – sdaau

8
Problem polega na tym, że za każdym razem, gdy próbuję rzeczywistego orurowania, które wydaje się zepsuć stdin, a okno curses nigdy nie pokazuje. [... snip ...] O ile widzę, problem jest następujący: - za każdym razem, gdy piszemy ciągi znaków do skryptu Python, skrypt Pythona traci odniesienie do terminala jako stdin i zauważa, że ​​zastąpiony stdin nie jest już strukturą termios - a ponieważ stdin nie jest już terminalem, curses.initscr() wychodzi natychmiast bez renderowania czegokolwiek.

W rzeczywistości okno przekleństw się pokazuje, ale ponieważ nie ma więcej danych wejściowych na nowym odważnym nowym stdin, myscreen.getch() natychmiast zwraca. Nie ma więc nic wspólnego z klątwami sprawdzającymi, czy terminal stdin jest terminalem.

Więc jeśli chcesz używać myscreen.getch() i innych funkcji wejściowych curses, będziesz musiał ponownie otworzyć terminal. W systemach Linux i * nix występuje zazwyczaj urządzenie o nazwie /dev/tty, które odnosi się do bieżącego terminala. Możesz więc zrobić coś takiego:

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

przed połączeniem z myscreen.getch().

+0

Dzięki, ninjalj, za miłe wyjaśnienie - pomaga mi to lepiej zrozumieć, jak działają przewody i standardowe operacje we/wy! Btw, nie jestem naprawdę zainteresowany użyciem '' myscreen.getch() ''- to, co chciałem zrobić, to" niesformatowane "dane typu" raw "w tym skrypcie, a skrypt powinien je przeanalizować i sformatować na ekranie używanie 'ncurses' jak w," w czasie rzeczywistym "(to jest cały zestaw różnych problemów - ale zrozumienie potrzeby duplikacji' stdin' było prawdziwym zakończeniem show_). Twoje zdrowie! – sdaau

+1

Zabawne jest to, że jeśli skrypt będzie działał przez czas nieokreślony i nie użyje 'myscreen.getch()', skrypt, który wysłałeś już działa, po prostu wychodzi zbyt szybko, aby go zauważyć. – ninjalj

+0

PS: Chciałem tylko powiedzieć, że zaktualizowałem [testcurses-stdin.py] (http://sdaaubckp.svn.sourceforge.net/viewvc/sdaaubckp/single-scripts/testcurses-stdin.py?revision=76&content-type= text% 2Fplain & pathrev = 75), więc powiela '/ dev/tty' zamiast' fd3' - a teraz skrypt może być wywołany po prostu za pomocą '' echo "blabla" | ./testcurses-stdin.py -' '. Jeszcze raz dziękuję, ninjalj - i okrzyki! – sdaau