如果您不需要为子流程提供全新的 pty,我相信您可以将您的程序缩减到此:
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()
调用在同一会话中创建一个新进程组,以便新进程将接收来自用户的任何 ctrl-c/ctrl-z/etc,而您的重试脚本不会。然后tcsetpgrp()
使新进程组成为控制 tty 上的前台。新进程在SIGTTOU
发生这种情况时得到一个(因为从 开始setpgrp()
,它一直在后台进程组中),这通常会使进程停止,所以这就是忽略SIGTTOU
. 我们将SIGTTOU
处理程序设置回原来的状态,以尽量减少子进程被意外信号表混淆的机会。
由于子进程现在位于 tty 的前台组中,因此它的 tcgetpgrp() 和 getpgrp() 将是相同的,并且 isatty(1) 将为真(假设它从 retry.py 继承的 stdout 实际上是一个 tty)。您不需要代理子进程和 tty 之间的流量,这样您就可以放弃所有的select
事件处理和 fcntl-nonblocking-setting。