Усечение стандартного вывода подпроцесса.run() без оболочки = True

#python #subprocess

#python #подпроцесс

Вопрос:

Я запускаю двоичный исполняемый файл из Python с помощью subprocess.run() команды, и команда выдает около 20 МБ текстовых данных в стандартный вывод. Меня интересуют только первые несколько строк вывода, и загрузка всего вывода команды в память занимает очень много времени (около 10 секунд).

Я хотел бы прочитать стандартный вывод команды до 10-й строки, а затем обрезать все остальные выходные данные. Чего я хотел бы достичь, так это эквивалентности running command | head (что очень быстро), но у меня есть shell=False набор, который не позволяет использовать каналы.

Есть ли какой-либо способ, которым я могу усечь вывод стандартного вывода всего до нескольких строк / байтов, не загружая все это в память? Я уже пробовал bufsize= параметр, но он не имел никакого эффекта.

Комментарии:

1. Все head , что нужно, это прочитать заданное количество строк из стандартного вывода процесса, а затем выйти (после чего программа в левой части конвейера получает SIGPIPE при следующей попытке записи в свой стандартный вывод и, следовательно, обычно также завершает работу). Вы можете, конечно, сделать это самостоятельно — в своем коде на Python.

2. …то есть: Мне нужно было бы просмотреть ваш конкретный код subprocess , чтобы узнать, почему он загружает весь стандартный вывод в память. Это не безусловное / автоматическое / поведение по умолчанию, если только вы не используете что-то вроде communicate() , которое явно определено для этого.

3. Спасибо, что объяснили head мне поведение. Я думаю, что в моем случае проблема не в том, что все это находится в памяти, а в том, что head удается уничтожить команду после того, как она выдает 10 строк, но подпроцесс будет ждать, пока команда не завершится полностью.

4. Хотелось бы, чтобы был способ воссоздать то, что head вы объяснили выше

5. head на самом деле не завершает программу; все, что он делает, это закрывает выходную часть FIFO (неявно, путем выхода), программа убивает себя, когда ее следующая попытка записать что-либо в стандартный вывод завершается неудачей. Отправка SIGPIPE выполняется операционной системой автоматически, когда есть read системный вызов после того, как автор закрыл свой конец, или write системный вызов после того, как читатель закрыл свой конец.

Ответ №1:

При использовании subprocess.Popen вы можете получить доступ к стандартному выводу подпроцесса и прочитать столько строк, сколько захотите

 p = subprocess.Popen(cmd, stdout=subprocess.PIPE)

lines_to_read = 10

for i in range(lines_to_read):
    print(p.stdout.readline())
  

Комментарии:

1. Это по-прежнему загружает все это в память и занимает около 10 секунд для моей команды, в отличие от command | head команды, которая занимает ~ 100 мс

2. 10 секунд для достижения какой точки? Конец цикла или вы ждете, что после завершения цикла произойдет что-то еще? (Если вы поместите p.wait() туда a , f / e, это вполне может закончиться зависанием на неопределенный срок, поскольку дочерний процесс все равно будет висеть в ожидании чтения его выходных данных).

3. @TadejMagajna, … кстати, вы приняли это после подтверждения того, что ввод p.stdout.close() после цикла работает? (Если это так, это может стоить отметить, чтобы к ответу можно было добавить явное закрытие).

4. Нет, я принял это после фактического запуска кода (изначально я был убежден, что описанный выше подход не будет работать до его фактического запуска). Отлично работает — спасибо вам обоим!