【问题标题】:Detecting when a child process is waiting for stdin检测子进程何时等待标准输入
【发布时间】:2021-05-28 08:31:38
【问题描述】:

我正在制作一个能够运行任何可执行文件的终端程序(请忽略安全问题)。我需要检测子进程何时等待用户输入(来自stdin)。我使用以下方式启动子进程:

process = subprocess.Popen(command, close_fds=False, shell=True, **file_descriptors)

我可以想到两种方法来检测子进程是否正在等待stdin

  • 写入一个字符,然后退格并检查子级是否处理了这 2 个字节。但是here 它说“CMD 确实支持退格键”。所以我需要找到一个字符,当打印到屏幕上时会删除命令提示符中stdin 缓冲区中的内容。
  • 第二种方法是使用pywin32 libraryWaitForInputIdle 函数,如here 所述。我查看了subprocess 库的源代码,发现它使用pywin32 并保留了对进程句柄的引用。所以我尝试了这个:
win32event.WaitForInputIdle(proc._handle, 100)

但是我收到了这个错误:

(1471, 'WaitForInputIdle', 'Unable to finish the requested operation because the specified process is not a GUI process.')

同样在 windows api 文档here 中它说:“WaitForInputIdle 只等待一次进程变为空闲;随后的 WaitForInputIdle 调用立即返回,无论进程是空闲还是忙碌。”。我认为这意味着我不能多次使用该功能,这不能解决我的问题

编辑: 这只需要在 Windows 上工作,但稍后我可能会尝试让我的程序也可以在 Linux 上计算。我也在为stdin/stdout/stderr使用管道。

为什么我需要知道孩子是否在等待标准输入

目前,当用户按下回车键时,我将他们迄今为止写入的所有数据发送到stdin 并禁止用户更改它。问题是当子进程正在睡眠/计算并且用户写入一些输入并希望在进程再次开始从stdin 读取之前更改它。

基本上让我们来看看这个程序:

sleep(10)
input("Enter value:")

假设我输入了“abc\n”。使用 cmd 时,如果孩子还在睡觉,我可以按退格键并删除输入。目前我的程序会在检测到“\n”时将所有文本标记为只读并将其发送到stdin

【问题讨论】:

  • 这只需要在 Windows 上工作吗?
  • 我找到了类似你想要的东西。它回答了你的问题吗? stackoverflow.com/a/49603436/11502612
  • @JosephSible-ReinstateMonica 我编辑了我的答案
  • @milanbalazs 我使用的是管道而不是文件,我不想更改子进程的输入。这就是我尝试将"g\b" 写入stdin 的原因。 \b 字符应该删除另一个字符,但它只给了我"g" 和一个白框 - 就像用于未知字符的那个。

标签: python-3.x subprocess pipe pywin32


【解决方案1】:
class STDINHandle:
    def __init__(self, read_handle, write_handle):
        self.handled_write = False
        self.working = Lock()
        self.write_handle = write_handle
        self.read_handle = read_handle

    def check_child_reading(self):
        with self.working:
            # Reset the flag
            self.handled_write = True
            # Write a character that cmd will ignore
            self.write_handle.write("\r")
            thread = Thread(target=self.try_read)
            thread.start()
            sleep(0.1)
            # We need to stop the other thread by giving it data to read
            if self.handled_write:
                # Writing only 1 "\r" fails for some reason.
                # For good measure we write 10 "\r"s
                self.write_handle.write("\r"*10)
                return True
            return False

    def try_read(self):
        data = self.read_handle.read(1)
        self.handled_write = False

    def write(self, text):
        self.write_handle.write(text)

我做了一些测试,我认为 cmd 忽略了"\r" 字符。我找不到 cmd 将其解释为实际字符的情况(就像我做"\b" 时发生的那样)。发送"\r" 字符并测试它是否留在管道中。如果它确实留在管道中,则意味着孩子尚未处理它。如果我们无法从管道中读取它,则意味着孩子已经处理了它。但是我们有一个问题——如果我们不能从stdin 读取,我们需要停止读取,否则它会干扰下一次写入stdin。为此,我们将更多的"\r"s 写入管道。

注意:我可能需要更改 sleep(0.1) 行的时间。

【讨论】:

  • 对于阅读本文的任何人:请查看this。这是一种更好的方法。
【解决方案2】:

我不确定这是一个好的解决方案,但如果您有兴趣,可以尝试一下。我只是假设我们在给定 2 个输入 dataTIMEOUT 的情况下为其 output 执行子进程。

process = subprocess.Popen(command, close_fds=False, shell=True, **file_descriptors)

try:
    output, _ = process.communicate(data, TIMEOUT)
except subprocess.TimeoutExpired:
    print("Timeout expires while waiting for a child process.")
    # Do whatever you want here

    return None

cmd_output = output.decode()

您可以找到更多关于 TimeoutExpired here 的示例。

【讨论】:

  • 我不明白这应该如何告诉我子进程是否正在侦听标准输入上的用户输入。这不只是发送输入数据并读取 stdout/stderr 以获取结果(超时)吗?我已经可以读取标准输出/标准错误并在 tkinter 窗口中写入。问题是我无法检测到子进程是否正在等待输入。我将在 2 分钟内更新我的问题并提供更多详细信息。
  • 子进程在等待用户输入时如何获取输出?这对我来说仍然不清楚。
  • 我有 3 个线程正在运行 - 1 个用于获取 stdout,1 个用于获取 stderr,1 个(使用 tkinter)处理终端窗口/stdin。
猜你喜欢
  • 2013-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 1970-01-01
相关资源
最近更新 更多