.readline() 之后的子进程不会读取文件描述符



今天我写了下面的脚本,它不起作用:

#!/usr/bin/env python3
'''
hsort.py
sort, but keeping the header at the top
'''
import sys
from subprocess import Popen
from contextlib import suppress
def main() -> None:
print(sys.stdin.readline(), end='')
with suppress(EOFError, KeyboardInterrupt):
Popen(['sort', *sys.argv[1:]], stdin=sys.stdin, stdout=sys.stdout)
if __name__ == '__main__':
main()

经过大量的调试,我找到了这个子进程。如果之前调用了fd.readline(), Popen将无法从文件对象(在本例中为sys.stdin)中读取任何剩余的行。

根据我对python(以及C文件指针)的了解,我无法解释为什么会发生这种情况,也无法解释为什么在subprocess模块中故意以这种方式编码。

这是预期的行为吗?是否有一种简单的方法来解释为什么会发生这种情况?

两个问题:

  1. 您没有对刚刚创建的Popen()对象做任何有意义的事情。您需要在其上调用.communicate().wait()才能使子进程实际运行到完成,就像您需要为使用fork(2)创建的子进程调用wait(2)一样。如果不这样做,那么子进程很可能会在试图从标准输入中读取任何内容时出错,因为脚本过早终止并且文件描述符无效。

  2. 在执行sort之前执行的sys.stdin.readline()调用最有可能消耗比单行输入更多的。如果Python的标准输入是缓冲的(很可能是缓冲的),那么.readline()只是读取任意大的数据块(在我的系统上高达8KiB),然后处理它以找到第一个换行符。

    在第一次读取之后,sort将被启动,但是没有其他的东西可以读取,因为所有的东西都已经被初始化的.readline()读取并存储在一些Python的内部缓冲区中。

    要解决这个问题,您应该避免读取第一行,或者以无缓冲的方式读取。这有点烦人,你需要从open()的文件描述符中输入buffering=0来获得一个新的"文件对象"。这是未缓冲的,然后手动读取输入的每个字节。

总之,你要做的是:

#!/usr/bin/env python3
import sys
from subprocess import Popen
def main() -> None:
first_line = b''
with open(sys.stdin.fileno(), 'rb', closefd=False, buffering=0) as f:
while not first_line.endswith(b'n'):
first_line += f.read(1)
print(first_line.decode(), end='')
p = Popen(['sort', *sys.argv[1:]], stdin=sys.stdin, stdout=sys.stdout)
p.wait()
if __name__ == '__main__':
main()

最新更新