今天我写了下面的脚本,它不起作用:
#!/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模块中故意以这种方式编码。
这是预期的行为吗?是否有一种简单的方法来解释为什么会发生这种情况?
两个问题:
-
您没有对刚刚创建的
Popen()
对象做任何有意义的事情。您需要在其上调用.communicate()
或.wait()
才能使子进程实际运行到完成,就像您需要为使用fork(2)
创建的子进程调用wait(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()