subprocess.Popen stdin读取文件

14

我想在读取文件的一部分后调用该文件的某个进程。例如:

with open('in.txt', 'r') as a, open('out.txt', 'w') as b:
  header = a.readline()
  subprocess.call(['sort'], stdin=a, stdout=b)

如果在运行subprocess.call之前不从a中读取任何内容,这将正常工作,但是如果我从a中读取任何内容,子进程就看不到任何东西。这是使用Python 2.7.3时出现的情况。我在文档中找不到任何解释这种行为的内容,并且对subprocess源代码进行了(非常)简短的浏览,也没有发现原因。


我猜"header = a.readline()"会使Python解释器的stdio为了性能而缓冲in.txt的部分内容,从而消除了一些系统调用的需求。如果in.txt不是非常庞大,最好使用Python的list_.sort()。 - dstromberg
3个回答

14

如果您以无缓冲的方式打开文件,则可以运行:

import subprocess

with open('in.txt', 'rb', 0) as a, open('out.txt', 'w') as b:
    header = a.readline()
    rc = subprocess.call(['sort'], stdin=a, stdout=b)

subprocess模块基于文件描述符级别工作(操作系统的低级别无缓冲I/O)。它可以与os.pipe()socket.socket()pty.openpty()等任何具有有效的.fileno()方法的对象一起使用,如果操作系统支持。

不建议在同一文件上混合使用缓冲和无缓冲I/O。

在Python 2中,file.flush()会导致输出出现,例如:

import subprocess
# 2nd
with open(__file__) as file:
    header = file.readline()
    file.seek(file.tell()) # synchronize (for io.open and Python 3)
    file.flush()           # synchronize (for C stdio-based file on Python 2)
    rc = subprocess.call(['cat'], stdin=file)

使用 os.read() 无需 subprocess 模块即可重现此问题:

#!/usr/bin/env python
# 2nd
import os

with open(__file__) as file: #XXX fully buffered text file EATS INPUT
    file.readline() # ignore header line
    os.write(1, os.read(file.fileno(), 1<<20))
如果缓冲区大小较小,则会打印文件的其余部分。
#!/usr/bin/env python
# 2nd
import os

bufsize = 2 #XXX MAY EAT INPUT
with open(__file__, 'rb', bufsize) as file:
    file.readline() # ignore header line
    os.write(2, os.read(file.fileno(), 1<<20))

如果第一行大小不能被bufsize整除,则会更多地消耗输入。

在我的机器上,默认的bufsizebufsize=1(行缓冲)的行为类似:文件开头消失了-大约4KB。

对于所有缓冲区大小,file.tell()报告的位置都在第二行开头。在Python 2中,使用next(file)而不是file.readline()会导致file.tell()大约为5K,这是由于读取前缓冲区错误io.open()给出了预期的第二行位置)。

在子进程调用之前尝试file.seek(file.tell())对于默认基于stdio的文件对象的Python 2无效。它可以在Python 2的io_pyio模块中使用open()函数,并且可以在Python 3的默认open(也是基于io的)中使用。

在Python 2和Python 3中尝试使用io_pyio模块,有无file.flush()会产生不同的结果。这证实了在同一文件描述符上混合缓冲和非缓冲I/O不是一个好主意


4

感谢您对这种行为的解释。虽然我会接受您的回答,但 J.F. 比您更快地给出了答案。 - DRayX

1

正如 @jfs 提到的那样, 当使用 popen 时,它会将文件描述符传递给进程, 同时 Python 以块的形式读取(例如 4096 字节), 结果是 fd 级别上的位置与您期望的不同。

我在 Python 2.7 中通过对齐文件描述符位置来解决了这个问题。

_file = open(some_path)
_file.read(codecs.BOM_UTF8)
os.lseek(_file.fileno(), _file.tell(), os.SEEK_SET)
truncate_null_cmd = ['tr','-d', '\\000']
subprocess.Popen(truncate_null_cmd, stdin=_file, stdout=subprocess.PIPE)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接