Subprocess.Popen stdin read file

Я пытаюсь вызвать процесс в файле после того, как часть его была прочитана. Например:

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, но если я что-нибудь прочитаю от него, подпроцесс ничего не увидит. Это использует python 2.7.3. Я не могу найти ничего в документации, объясняющей это поведение, и (очень) краткий взгляд на источник подпроцесса не выявил причины.

Ответ 1

Если вы открываете файл без буферизации, он работает:

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 работает на уровне дескриптора файла (низкоуровневый небуферизованный ввод-вывод операционной системы). Он может работать с os.pipe(), socket.socket(), pty.openpty(), что угодно с допустимым методом .fileno(), если OS поддерживает его.

Не рекомендуется смешивать буферизованный и небуферизованный ввод-вывод в том же файле.

В 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)

Проблема может быть воспроизведена без модуля subprocess с os.read():

#!/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.

По умолчанию bufsize и bufsize=1 (строка-буферизация) ведут себя одинаково на моей машине: начало файла исчезает - около 4 КБ.

file.tell() сообщает для всех размеров буфера позицию в начале второй строки. Использование next(file) вместо file.readline() приводит к file.tell() около 5K на моей машине на Python 2 из-за ошибки ошибки чтения() io.open() дает ожидаемую позицию 2-й строки).

Попытка file.seek(file.tell()) до того, как вызов подпроцесса не поможет на Python 2 с файловыми объектами на основе stdio по умолчанию. Он работает с функциями open() из модулей io, _pyio на Python 2 и по умолчанию open (также io) на Python 3.

Попытка io, _pyio модулей на Python 2 и Python 3 с и без file.flush() дает различные результаты. Он подтверждает, что смешивание буферизованного и небуферизованного ввода-вывода в одном и том же дескрипторе файла не является хорошей идеей.

Ответ 2

Это происходит потому, что модуль subprocess извлекает дескриптор File из объекта File.

http://hg.python.org/releasing/2.7.6/file/ba31940588b6/Lib/subprocess.py

В строке 1126, начиная с 701.

Объект File использует буферы и уже много читал из дескриптора файла, когда подпроцесс извлекает его.