【问题标题】:Get user input while continually reading stdin from a pipe in Python?在 Python 中不断从管道中读取标准输入时获取用户输入?
【发布时间】:2020-05-24 03:46:59
【问题描述】:

给定一个名为 interactive 的程序,它的开头如下:

% tail -f logfile | interactive

在 Python 中,我们可以通过覆盖引用将 stdin 重置为 stdin 以外的其他值:

import sys 

text = sys.stdin.read() 

# rebind sys.stdin to tty 
sys.stdin = open("/dev/tty") 
user_in = input("Selection? : ") 

但是,如果我们需要继续从我们通过管道输入的文件中继续读取,这将不起作用,因为原始的 stdin 管道不会放开。有没有办法从我们的管道中获取输入并将其移动到单独的管道中,以便我们仍然可以使用stdin 来获取用户输入?

【问题讨论】:

  • 我认为可以分两步完成 - 假设一个命名管道在 /tmp/my_pipe 处打开,您可以将 tail 输出到该管道,然后独立使用 python打开并从同一管道读取 - 从而释放标准输入以供用户输入。做类似tail -f logfile > /tmp/my_pipe && python interactive.py
  • 也就是说,您是否考虑过尝试在单个 python 程序中完成整个事情?您实际上可以将管道/文件描述符附加到您自己的 subprocess 调用中,这将简化问题,因为您不需要创建这样的代理。
  • 我们的目标是让这个程序以任意方式运行;我可以将它称为interactive -e 'tail -f logfile' 并使用该命令生成一个子进程。这一切都行得通,但我真的希望能够通过管道传递给它,因为这种语法感觉更自然。好的调用 RE tail 到一个单独的管道,但你是对的,subprocess 方法要简单得多。

标签: python stdin


【解决方案1】:

就它们作为文件流的状态而言,stdin/stdout/stderr1没有特别之处。程序启动后,您可以将它们视为任何常规文件描述符。所以重新绑定stdin 的想法是没有必要的。相反,这似乎更像是一个关于如何设置并发输入流的问题。

我的第一个倾向是使用几个线程,但我确信这可以使用async/await 以及可能的其他方式来完成。在这种情况下线程应该没问题,因为两个线程最终都会绑定 IO 并应该释放 GIL(每个人都喜欢对 GIL 感到兴奋)。这非常接近(不完美,但在大致范围内......):

#!/usr/bin/env python3

import sys
import threading
from time import sleep

user_input = ""

def tty_reader():
    global user_input
    read_fd = open("/dev/tty", 'r')
    while 1:
        print("interactive-% ", end="", flush=True)
        user_input = read_fd.readline() # block for input
        print("\nUser Input: " + str(user_input), end="", flush=True)

def pipe_reader():
    while 1:
        stdin_text = sys.stdin.readline() # block for input
        if stdin_text != user_input:
            print("\nPipe Message: " + str(stdin_text), end="", flush=True)

t_tty_reader = threading.Thread(target=tty_reader)
t_pipe_reader = threading.Thread(target=pipe_reader)
t_pipe_reader.start()
sleep(3) # to let the existing 'tail' lines finish printing
t_tty_reader.start()

将它保存到一个文件中,chmod 755 它,然后像这样运行:

tail -f <some file> | ./<python script>

这会启动 2 个线程。一个从/dev/tty 读取,另一个从原始stdin 读取,应该附加到tail -f 命令的stdout。两个线程大部分时间都在阻塞输入。正如我所说,这可能会更好,但它显示了总体思路。


1:尽管如此,它们仍然有点特别:您可以免费获得它们;它们预先安装在某种标准化配置中;他们在/dev 中获得了一些特殊待遇;子进程继承它们。

【讨论】:

    最近更新 更多