6

我编写了一个简单的包装脚本,用于在命令失败时重复命令retry.py。但是,当我想查看子命令的输出时,我不得不使用一些 pty 技巧。这对于像 rsync 这样的程序可以正常工作,但是像 scp 这样的其他程序应用额外的测试来显示他们的进度表等东西。

scp 代码有一个广泛的测试:

getpgrp() == tcgetpgrp(STDOUT_FILENO);

当我通过包装脚本运行时失败了。正如您在我的简单 tty_test.c 测试用例中看到的那样:

./tty_tests
isatty reports 1
pgrps are 13619 and 13619

和:

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

我尝试使用 tcsetpgrp(),它最终作为 pty fd 上的 IOCTL,但这会导致 ptys 出现 -EINVAL。如果可能的话,我宁愿继续使用 Python 子进程机制,或者是否需要手动 fork/execve'ing?

4

1 回答 1

9

如果您不需要为子流程提供全新的 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。

于 2013-03-06T20:48:01.447 回答