Как мне использовать subprocess.Popen для подключения нескольких процессов по каналам?
Как мне выполнить следующую команду оболочки с помощью модуля Python subprocess
?
echo "input data" | awk -f script.awk | sort > outfile.txt
Входные данные будут поступать из строки, так что на самом деле мне это не нужно echo
. Я зашел так далеко, кто-нибудь может объяснить, как я могу передать их по каналу sort
тоже?
p_awk = subprocess.Popen(["awk","-f","script.awk"],
stdin=subprocess.PIPE,
stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )
ОБНОВЛЕНИЕ: Обратите внимание, что, хотя принятый ответ ниже на самом деле не отвечает на заданный вопрос, я считаю, что С. Лотт прав, и лучше вообще не решать эту проблему!
Переведено автоматически
Ответ 1
Вам было бы немного удобнее со следующим.
import subprocess
awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )
Делегируйте часть работы оболочке. Позвольте ей соединить два процесса конвейером.
Вам было бы намного приятнее переписать 'script.awk' на Python, исключив awk и конвейер.
Редактировать. Некоторые из причин, по которым awk не помогает.
[Слишком много причин для ответа в комментариях.]
Awk добавляет шаг, не имеющий существенного значения. В обработке awk нет ничего уникального, чего не обрабатывал бы Python.
Конвейерная обработка от awk до sort для больших наборов данных может увеличить время обработки. Для коротких наборов данных это не имеет существенного преимущества. Помогает быстрое измерение
awk >file ; sort file
иawk | sort
покажет параллелизм. В случае сортировки это редко помогает, потому что сортировка не является одноразовым фильтром.Простота обработки "Python для сортировки" (вместо "Python для awk для сортировки") не позволяет задавать здесь вопросы точного типа.
Python - хотя и более словоохотливый, чем awk, - также является явным, где awk имеет определенные неявные правила, которые непрозрачны для новичков и сбивают с толку неспециалистов.
Awk (как и сам сценарий оболочки) добавляет еще один язык программирования. Если все это можно сделать на одном языке (Python), устранение оболочки и программирования awk устраняет два языка программирования, позволяя кому-то сосредоточиться на тех частях задачи, которые создают ценность.
Итог: awk не может добавить значительной ценности. В данном случае awk - это чистая стоимость; это добавило достаточно сложности, чтобы было необходимо задать этот вопрос. Удаление awk будет чистым выигрышем.
Боковая панель Почему построить конвейер (a | b
) так сложно.
Когда оболочка сталкивается с a | b
, она должна выполнить следующее.
Разветвляйте дочерний процесс исходной оболочки. В конечном итоге это станет b .
Создайте канал операционной системы. (не подпроцесс Python.КАНАЛ), но вызов
os.pipe()
который возвращает два новых файловых дескриптора, соединенных через общий буфер. На данный момент процесс имеет stdin, stdout, stderr от своего родительского элемента, плюс файл, который будет "a's stdout" и "b's stdin".Разветвляйте дочерний процесс. Дочерний элемент заменяет свой стандартный вывод на стандартный вывод нового a. Запустите
a
процесс.Дочерний элемент b closes заменяет свой stdin на stdin нового b. Запустите процесс
b
.Дочерний элемент b ожидает завершения a.
Родительский файл ожидает завершения b.
Я думаю, что приведенное выше можно использовать рекурсивно для создания a | b | c
, но вы должны неявно заключать длинные конвейеры в скобки, рассматривая их так, как если бы они были a | (b | c)
.
Поскольку в Python есть os.pipe()
, os.exec()
и os.fork()
, и вы можете заменить sys.stdin
и sys.stdout
, есть способ сделать вышеописанное на чистом Python. Действительно, вы можете разработать некоторые быстрые пути, используя os.pipe()
и subprocess.Popen
.
Однако проще делегировать эту операцию командной оболочке.
Ответ 2
import subprocess
some_string = b'input_data'
sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in,
stdin=subprocess.PIPE).communicate(some_string)
Ответ 3
Для эмуляции конвейера оболочки:
from subprocess import check_call
check_call('echo "input data" | a | b > outfile.txt', shell=True)
без вызова командной оболочки (см. 17.1.4.2. Замена командной оболочки в конвейере):
#!/usr/bin/env python
from subprocess import Popen, PIPE
a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
with a.stdout, open("outfile.txt", "wb") as outfile:
b = Popen(["b"], stdin=a.stdout, stdout=outfile)
a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already
plumbum
предоставляет некоторый синтаксический сахар:
#!/usr/bin/env python
from plumbum.cmd import a, b # magic
(a << "input data" | b > "outfile.txt")()
Аналог:
#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt
это:
#!/usr/bin/env python
from plumbum.cmd import awk, sort
(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()
Ответ 4
Принятый ответ обходит актуальный вопрос. вот фрагмент, который связывает выходные данные нескольких процессов в цепочку: Обратите внимание, что он также выводит (несколько) эквивалентную команду оболочки, чтобы вы могли запустить ее и убедиться в правильности выходных данных.
#!/usr/bin/env python3
from subprocess import Popen, PIPE
# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")
p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)
print("Output from last process : " + (p3.communicate()[0]).decode())
# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)