异步向Python脚本管道输入的问题排查求助
我需要给后台运行的Python脚本异步管道输入,要求脚本完成长时间初始化后等待输入,处理完输入后回到等待状态(非无头服务器场景)。
我的Python脚本如下:
import sys # do some long functions sys.stdin.flush() data="" while(1): data = sys.stdin.read() if data != "": print("input: " + data) sys.stdin.flush() data="" exit()
在Ubuntu 22.04上,我在一个终端执行命令:
python3 rx.py</tmp/py-srv.input & echo $!>/tmp/py-srv.pid
创建PID文件用于后续终止进程,同时指定输入文件。
测试情况:
- 第一次在另一个终端执行
echo "test">/tmp/py-srv.input,第一个终端正常显示input: test,首次输入有效。 - 再次执行
echo "YAY!">/tmp/py-srv.input时无输出;若执行echo "testYAY!">/tmp/py-srv.input,则显示input: AY!。
观察到的现象:.input文件首次读取后被清空,但后续读取后未清空,添加sys.stdin.flush()也未解决问题。尝试使用input()函数时出现EOFError: EOF when reading a line错误,推测和后台运行脚本有关。
请问我在Python sys.stdin的使用上遗漏了什么?为何后续写入输入文件时,stdin.read()无法识别,除非内容以首次输入的字符串开头?
核心问题
你当前的方式是将普通文件作为stdin的数据源,但sys.stdin.read()在读取到文件末尾(EOF)后会持续返回空字符串,不会再监听文件内容的更新。
第一次写入后,文件被读取至EOF,后续即使往文件写入内容,Python的stdin已处于EOF状态,无法读取新内容。而你看到的AY!是因为第二次写入testYAY!时,文件指针停留在第一次读取后的位置(即test之后),仅读取到后续的AY!,这是文件指针偏移导致的,并非脚本识别了开头字符串。
另外,input()函数报错是因为它尝试读取一行,但stdin已触发EOF,直接抛出异常。
解决方案
要实现“持续监听输入、处理后回到等待状态”的需求,不能直接绑定普通文件作为stdin,需通过以下两种方式实现:
方案1:使用命名管道(FIFO)
这是适配异步管道输入的最优方案,步骤如下:
- 创建命名管道:
mkfifo /tmp/py-srv.fifo
- 修改Python脚本,监听FIFO输入:
import os import time # 执行长时间初始化操作 # do some long functions fifo_path = "/tmp/py-srv.fifo" # 以只读+非阻塞模式打开FIFO fd = os.open(fifo_path, os.O_RDONLY | os.O_NONBLOCK) while True: try: # 读取FIFO内容,最大读取1024字节 data = os.read(fd, 1024).decode().strip() if data: print(f"input: {data}") except BlockingIOError: # 无内容时休眠0.1秒,降低CPU占用 time.sleep(0.1)
- 后台启动脚本:
python3 rx.py & echo $!>/tmp/py-srv.pid
- 向FIFO写入内容:
echo "test" > /tmp/py-srv.fifo echo "YAY!" > /tmp/py-srv.fifo
每次写入FIFO的内容都会被脚本读取并处理,处理完成后继续等待新输入。
方案2:监控普通文件的新增内容
若必须使用普通文件,可通过追踪文件偏移量读取新增内容:
import time # 执行长时间初始化操作 # do some long functions input_file = "/tmp/py-srv.input" file_offset = 0 while True: try: with open(input_file, "r") as f: # 移动文件指针到上次读取的位置 f.seek(file_offset) data = f.read().strip() if data: print(f"input: {data}") # 更新偏移量到当前文件末尾 file_offset = f.tell() except Exception: pass # 休眠0.1秒,减少文件IO频率 time.sleep(0.1)
启动脚本时无需绑定stdin,直接后台运行:
python3 rx.py & echo $!>/tmp/py-srv.pid
每次写入文件后,脚本会读取新增内容,不会再出现EOF问题。
关键注意事项
- 禁止将普通文件直接绑定为stdin,因为stdin读取到EOF后会停止响应。
- 命名管道是更符合“管道输入”场景的方案,无需手动管理文件偏移量。
- 两种方案都需添加短暂休眠,避免脚本持续占用过高CPU资源。
内容的提问来源于stack exchange,提问作者BrettOC

