2013-03-04 32 views
6

Ho scritto un semplice script di wrapper per ripetere i comandi quando falliscono chiamati retry.py. Comunque, visto che voglio vedere l'output del comando figlio, ho dovuto fare alcuni trucchi. Funziona bene per programmi come rsync ma altri come scp applicano test aggiuntivi per mostrare cose come il loro indicatore di avanzamento.Come si imposta il gruppo di processi in primo piano del terminale per un processo eseguito con una pty?

Il codice SCP ha un test che è in linea di massima:

getpgrp() == tcgetpgrp(STDOUT_FILENO); 

che non riesce quando si esegue anche se lo script wrapper. Come si può vedere con il mio semplice caso di test tty_test.c:

./tty_tests 
isatty reports 1 
pgrps are 13619 and 13619 

e:

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

Ho provato con il tcsetpgrp(), che finisce per essere un IOCTL sulle FD pty, ma che risultati in -EINVAL per ptys. Preferirei continuare a utilizzare il macchinario di sottoprocesso Python se possibile o è necessario eseguire manualmente fork/execveing ​​per questo?

risposta

9

credo che si può sbucciare il programma riduce a questo, se non è necessario fornire una nuova PTY per il sottoprocesso:

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) 

La chiamata setpgrp() crea un nuovo gruppo di processi nella stessa sessione , in modo che il nuovo processo riceva qualsiasi ctrl-c/ctrl-z/etc dall'utente, e il tuo script non lo farà. Quindi lo tcsetpgrp() rende il nuovo gruppo di processi quello in primo piano sul tty di controllo. Il nuovo processo ottiene un SIGTTOU quando ciò accade (perché dal momento che il setpgrp(), è stato in un gruppo di processi in background), che normalmente farebbe fermare il processo, quindi questo è il motivo per ignorare SIGTTOU. Abbiamo impostato il gestore SIGTTOU su qualsiasi cosa fosse prima, per ridurre al minimo la possibilità che il sottoprocesso venga confuso da una tabella di segnali imprevisti.

Poiché il sottoprocesso è ora nel gruppo in primo piano per tty, il suo tcgetpgrp() e getpgrp() sarà lo stesso, e isatty (1) sarà vero (presupponendo che lo stdout ereditato da retry.py sia effettivamente un tty). Non è necessario il traffico proxy tra il sottoprocesso e il tty, che consente di abbandonare tutta la gestione degli eventi select e l'impostazione fcntl-nonblocking.

+0

Ho provato e non ha alcun effetto: > retry.py -v - ~/mysrc/retry.git/tty_tests comando è ['/home/ajb/mysrc/retry.git/tty_tests '] report isatty 1 pgrps sono 28268 e -1 figlio terminato: rc = 0 comando Ran 1 volte – stsquad

+0

Potresti incollare del codice completo? –

+0

OH! Ho appena notato che hai fornito un link a retry.py nella tua domanda. Avevo pensato che fosse semplicemente lo stackoverflow che cercava di essere utile e creare un collegamento con qualcosa che somigliava a un nome host. Darò un'occhiata. –