【问题标题】:How do I set the terminal foreground process group for a process I'm running under a pty?如何为我在 pty 下运行的进程设置终端前台进程组?
【发布时间】:2013-03-04 11:40:02
【问题描述】:

我编写了一个简单的包装脚本,用于在命令失败时重复命令,称为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,但会导致 pty 的 -EINVAL。如果可能的话,我宁愿继续使用 Python 子进程机制,还是需要手动 fork/execve'ing?

【问题讨论】:

    标签: python posix subprocess pty posix-api


    【解决方案1】:

    如果您不需要为子流程提供全新的 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) 将是 true(假设它从 retry.py 继承的 stdout 实际上是一个tty)。您不需要代理子进程和 tty 之间的流量,这样您就可以放弃所有 select 事件处理和 fcntl-nonblocking-setting。

    【讨论】:

    • 我试了一下,没有任何效果: >retry.py -v -- ~/mysrc/retry.git/tty_tests command is ['/home/ajb/mysrc/ retry.git/tty_tests'] isatty 报告 1 pgrps 是 28268 和 -1 子完成:rc = 0 Ran 命令 1 次
    • 哦!我刚刚注意到您在问题中提供了指向 retry.py 的链接。我原以为这只是 stackoverflow 试图提供帮助并用看起来像主机名的东西制作链接。我去看看。
    • os.tcsetgrp 和 os.setpgrp 都返回 None 所以看起来它失败了:-/
    • 是否要求 retry.py 为子进程提供一个 tty,即使它本身没有在 tty 下运行?
    • become_tty_fg 函数可以通过在其末尾添加os.close(tty) 来稍微改进。否则文件描述符tty 保持打开状态,尽管在当前代码中,该函数可能在其他地方使用并不重要。
    猜你喜欢
    • 1970-01-01
    • 2020-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-21
    • 1970-01-01
    • 1970-01-01
    • 2020-10-13
    相关资源
    最近更新 更多