2013-03-04 16 views
6

Ich habe ein einfaches Wrapper-Skript geschrieben, um Befehle zu wiederholen, wenn sie fehlschlagen: retry.py. Da ich jedoch die Ausgabe des Kinderbefehls sehen möchte, musste ich ein paar Tricks machen. Dies funktioniert in Ordnung für Programme wie rsync, aber andere wie scp wenden einen zusätzlichen Test an, um Dinge wie ihre Fortschrittsanzeige anzuzeigen.Wie lege ich die Terminal-Vordergrund-Prozessgruppe für einen Prozess fest, den ich unter einem Pty laufen lasse?

Der scp-Code hat einen Test, der im Großen und Ganzen ist:

getpgrp() == tcgetpgrp(STDOUT_FILENO); 

, die, wenn ich aber das Wrapper-Skript ausführen fehlschlägt. Wie können Sie mit meiner einfachen tty_test.c Testfall sehen:

./tty_tests 
isatty reports 1 
pgrps are 13619 and 13619 

und:

./retry.py -v -- ./tty_tests 
command is ['./tty_tests'] 
isatty reports 1 
pgrps are 13614 and -1 
child finished: rc = 0 
Ran command 1 times 

Ich habe versucht, die tcsetpgrp() verwendet, die auf den pty fd die als IOCTL endet aber, dass ergibt ein -EINVAL für ptys. Ich würde es vorziehen, die Python-Subprozess-Maschinerie weiter zu verwenden, wenn es überhaupt möglich ist, oder wird manuell fork/execve'ing dafür erforderlich sein?

Antwort

9

Ich glaube, Sie Ihr Programm nach unten auf diese pare können, wenn Sie nicht eine ganz neue pty zum subprocess bieten müssen:

from argparse import ArgumentParser 
import os 
import signal 
import subprocess 
import itertools 

# your argumentparser stuff goes here 

def become_tty_fg(): 
    os.setpgrp() 
    hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN) 
    tty = os.open('/dev/tty', os.O_RDWR) 
    os.tcsetpgrp(tty, os.getpgrp()) 
    signal.signal(signal.SIGTTOU, hdlr) 

if __name__ == "__main__": 
    args = parser.parse_args() 

    if args.verbose: print "command is %s" % (args.command) 
    if args.invert and args.limit==None: 
     sys.exit("You must define a limit if you have inverted the return code test") 

    for run_count in itertools.count(): 
     return_code = subprocess.call(args.command, close_fds=True, 
             preexec_fn=become_tty_fg) 
     if args.test == True: break 
     if run_count >= args.limit: break 
     if args.invert and return_code != 0: break 
     elif not args.invert and return_code == 0: break 

    print "Ran command %d times" % (run_count) 

Der setpgrp() Aufruf erstellt eine neue Prozessgruppe in der gleichen Sitzung , so dass der neue Prozess eine beliebige ctrl-c/ctrl-z/etc vom Benutzer erhält und das Wiederholungsskript nicht. Dann macht die tcsetpgrp() die neue Prozessgruppe zum Vordergrund auf der steuernden tty. Der neue Prozess erhält eine SIGTTOU, wenn das passiert (weil seit der setpgrp(), war es in einer Hintergrundprozessgruppe), die normalerweise den Prozess stoppen würde, so dass der Grund für das Ignorieren SIGTTOU ist. Wir setzen den SIGTTOU-Handler zurück auf das, was er vorher war, um die Wahrscheinlichkeit zu minimieren, dass der Unterprozess durch eine unerwartete Signaltabelle verwirrt wird.

Da der Unterprozess jetzt in der Vordergrundgruppe für das tty ist, sind seine tcgetpgrp() und getpgrp() identisch, und isatty (1) wird wahr sein (vorausgesetzt, das stdout erbt von retry.py ist eigentlich) ein tty). Sie müssen den Datenverkehr zwischen dem Subprozess und dem tty nicht per Proxy übertragen, wodurch Sie alle Einstellungen für die Ereignisbehandlung select und die Einstellung fcntl-nonblocking löschen können.

+0

Ich habe es versucht und es hat keine Wirkung: > retry.py -v - ~/mysrc/retry.git/tty_tests Befehl ist ['/home/ajb/mysrc/retry.git/tty_tests] isatty reports 1 pgrps sind 28268 und -1 Kind fertig: rc = 0 Ran Befehl 1 mal – stsquad

+0

Könnten Sie etwas vollständigen Code einfügen? –

+0

OH! Ich habe gerade festgestellt, dass Sie in Ihrer Frage einen Link zu retry.py angegeben haben. Ich dachte, das wäre nur Stackoverflow, der versucht, hilfreich zu sein und aus etwas herauszukommen, das wie ein Hostname aussieht. Ich werde es mir ansehen. –