繁体   English   中英

如果在等待`read -s`时被中断,则在子进程中运行bash会中断td的stdout?

[英]Running bash in subprocess breaks stdout of tty if interrupted while waiting on `read -s`?

正如@Bakuriu在评论中指出的那样,这与BASH中的问题基本相同:输入时Ctrl + C打破了当前终端但是,我只能在bash作为另一个可执行文件的子进程运行时重现问题,而不是直接来自bash ,它似乎处理终端清理罚款。 关于为什么bash在这方面似乎被打破了,我会感兴趣。

我有一个Python脚本,用于记录由该脚本启动的子进程的输出。 如果子进程碰巧是一个bash脚本,在某些时候通过调用内置的read -s读取用户输入( -s ,它可以防止回输输入的字符,成为键),并且用户中断脚本(即Ctrl-C),然后bash无法将输出恢复到tty,即使它继续接受输入。

我把它简化为一个简单的例子:

$ cat test.py
#!/usr/bin/python
import subprocess as sp
p = sp.Popen(['bash', '-c', 'read -s foo; echo $foo'])
p.wait()

运行./test.py ,它将等待一些输入。 如果您键入一些输入并按Enter键,脚本将返回并按预期回显您的输入,并且没有问题。 但是,如果您立即点击“Ctrl-C”,Python会显示KeyboardInterrupt的回溯,然后返回到bash提示符。 但是,您输入的任何内容都不会显示在终端上。 但是,键入reset<enter>成功重置终端。

我有点不知道到底发生了什么。

更新:我设法在没有Python的情况下重现这一点。 我试图在strace中运行bash,看看我是否可以收集任何正在发生的事情。 使用以下bash脚本:

$ cat read.sh
#!/bin/bash
read -s foo
echo $foo

运行strace ./read.sh并立即按Ctrl-C会产生:

...
ioctl(0, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon -echo ...}) = 0
brk(0x1a93000)                          = 0x1a93000
read(0, Process 25487 detached
 <detached ...>

其中PID 25487被read.sh 这使终端处于相同的破坏状态。 但是, strace -I1 ./read.sh只是中断./read.sh进程并返回正常的非破坏终端。

看起来这与bash -c启动非交互式 shell的事实有关。 这可能会阻止它恢复终端状态。

要显式启动交互式shell,您只需将-i选项传递给bash即可。

$ cat test_read.py 
#!/usr/bin/python3
from subprocess import Popen
p = Popen(['bash', '-c', 'read -s foo; echo $foo'])
p.wait()
$ diff test_read.py test_read_i.py 
3c3
< p = Popen(['bash', '-c', 'read -s foo; echo $foo'])
---
> p = Popen(['bash', '-ic', 'read -s foo; echo $foo'])

当我运行并按Ctrl + C时

$ ./test_read.py

我获得:

Traceback (most recent call last):
  File "./test_read.py", line 4, in <module>
    p.wait()
  File "/usr/lib/python3.5/subprocess.py", line 1648, in wait
    (pid, sts) = self._try_wait(0)
  File "/usr/lib/python3.5/subprocess.py", line 1598, in _try_wait
    (pid, sts) = os.waitpid(self.pid, wait_flags)
KeyboardInterrupt

并且终端未正确恢复。

如果我以与我得到的相同的方式运行test_read_i.py文件:

$ ./test_read_i.py 

$ echo hi
hi

没有错误,终端工作。

正如我在对我的问题的评论中所写,当运行read -s时,bash保存当前的tty属性,并安装add_unwind_protect处理程序,以便在read的堆栈帧退出时恢复先前的tty属性。

通常, bash在启动时为SIGINT安装一个处理程序,除其他外,它调用堆栈的完全展开,包括运行所有unwind_protect处理程序,例如read添加的处理程序。 但是,通常在bash以交互模式运行时才安装此SIGINT处理程序。 根据源代码,仅在以下条件下启用交互模式:

 /* First, let the outside world know about our interactive status.
     A shell is interactive if the `-i' flag was given, or if all of
     the following conditions are met:
    no -c command
    no arguments remaining or the -s flag given
    standard input is a terminal
    standard error is a terminal
     Refer to Posix.2, the description of the `sh' utility. */

我认为这也应该解释为什么我不能简单地通过在bash中运行bash来重现问题。 但是当我在strace运行它,或者从Python启动子进程时,我要么使用-c ,要么程序的stderr不是终端等。

正如@Baikuriu在他们的回答中发现的那样,就像我在编写这个过程中一样, -i将强制bash使用“交互模式”,并且它将在自身之后正确清理。

就我而言,我认为这是一个错误。 在手册页,如果证明stdin不是一个TTY,则-s来选择read被忽略。 但是,在我的例子stdin仍然一个TTY,但庆典是不是在交互模式,否则技术上,尽管仍然调用交互行为。 在这种情况下,它仍应从SIGINT中正确清理。

对于它的价值,这里是一个特定于Python(但很容易概括)的解决方法。 首先,我确保将SIGINT (和SIGTERM for good measure)传递给子进程。 然后我在一个小的上下文管理器中包装整个subprocess.Popen调用终端设置:

import contextlib
import os
import signal
import subprocess as sp
import sys
import termios

@contextlib.contextmanager
def restore_tty(fd=sys.stdin.fileno()):
    if os.isatty(fd):
        save_tty_attr = termios.tcgetattr(fd)
        yield
        termios.tcsetattr(fd, termios.TCSAFLUSH, save_tty_attr)
    else:
        yield

@contextlib.contextmanager
def send_signals(proc, *sigs):
    def handle_signal(signum, frame):
        try:
            proc.send_signal(signum)
        except OSError:
            # process has already exited, most likely
            pass

    prev_handlers = []

    for sig in sigs:
        prev_handlers.append(signal.signal(sig, handle_signal))

    yield

    for sig, handler in zip(sigs, prev_handlers):
        signal.signal(sig, handler)


with restore_tty():
    p = sp.Popen(['bash', '-c', 'read -s test; echo $test'])
    with send_signals(p, signal.SIGINT, signal.SIGTERM):
        p.wait()

我仍然感兴趣的答案解释了为什么这是必要的 - 为什么不能更好地清理自己呢?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM