2013-03-04 59 views
6

Napisałem prosty skrypt do powtarzania poleceń, gdy nie zadzwonił pod numer retry.py. Jednak ponieważ chcę zobaczyć wynik polecenia potomnego, musiałem wyciągnąć trochę sztuczek. Działa to dobrze w przypadku programów takich jak rsync, ale inne, takie jak scp, stosują dodatkowy test do pokazywania rzeczy takich jak ich miernik postępu.Jak ustawić grupę procesów na pierwszym planie terminów dla procesu, w którym działam pod pty?

Kod SCP ma testu, który jest szeroko:

getpgrp() == tcgetpgrp(STDOUT_FILENO); 

która nie kiedy biegnę choć skryptu otoki. Jak widać z mojego prostego przypadku testowego tty_test.c:

./tty_tests 
isatty reports 1 
pgrps are 13619 and 13619 

oraz:

./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 

próbowałem za pomocą tcsetpgrp(), który kończy się jako IOCTL na FD pty, ale że daje wynik -EINVAL dla ptys. Wolałbym nadal używać maszyny podprocesowej Python, jeśli jest to w ogóle możliwe, czy też wymagane jest ręczne wykonanie fork/execveing?

Odpowiedz

9

wierzę można pare swój program do tego, jeśli nie muszą zapewnić zupełnie nowy pty do podproces:

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) 

setpgrp() wezwanie tworzy nową grupę procesów w tej samej sesji , aby nowy proces otrzymywał od użytkownika ctrl-c/ctrl-z/etc, a twój skrypt nie będzie. Następnie tcsetpgrp() powoduje, że nowa grupa procesów będzie na pierwszym planie na teście sterującym. Nowy proces uzyskuje SIGTTOU, gdy tak się dzieje (ponieważ od setpgrp(), był w grupie procesów w tle), co normalnie spowodowałoby zatrzymanie procesu, dlatego jest to powodem ignorowania SIGTTOU. Przywracamy sterownik SIGTTOU z powrotem do tego, co było wcześniej, aby zminimalizować ryzyko, że podproces zostanie zdezorientowany przez nieoczekiwaną tabelę sygnałów.

Ponieważ podproces jest teraz w grupie pierwszoplanowej dla tty, jego tcgetpgrp() i getpgrp() będą takie same, a isatty (1) będzie prawdą (zakładając, że standardowe wyjście z pliku retry.py faktycznie jest tty). Nie ma potrzeby wykonywania pośredniczącego ruchu między podprocesami i tty, dzięki czemu można pozbyć się całej obsługi zdarzeń i ustawienia fcntl-nonblocking.

+0

Poddałem próbę i nie ma żadnego efektu: > Retry.py -v - ~/mysrc/retry.git/tty_tests Komenda jest ['/home/ajb/mysrc/retry.git/tty_tests '] Raporty isatty 1 pgrps to 28268 i -1 ukończone dziecko: rc = 0 Polecenie Ran 1 razy – stsquad

+0

Czy możesz wkleić kompletny kod? –

+0

OH! Właśnie zauważyłem, że w swoim pytaniu podałeś link do retry.py. Myślałem, że to właśnie stackoverflow próbuje być pomocny i tworzy link z czegoś, co wyglądało jak nazwa hosta. Spojrzę na to. –