Я пытаюсь написать скрипт на Python, который запускает подпроцесс и выполняет запись в stdin подпроцесса. Я также хотел бы иметь возможность определять действие, которое необходимо предпринять в случае сбоя подпроцесса.
Процесс, который я пытаюсь запустить, называется программойnuke, которая имеет свою собственную встроенную версию Python, которой я хотел бы иметь возможность отправлять команды, а затем указывать ей завершить работу после выполнения команд. До сих пор я выяснял, что если я запускаю Python в командной строке, например, а затем запускаю nuke как подпроцесс, то я могу вводить команды в nuke, но я хотел бы иметь возможность поместить все это в скрипт, чтобы основная программа Python могла запускаться nuke, а затем записывать в свой стандартный ввод (и, следовательно, во встроенную версию Python) и указывать ему делать шикарные вещи, поэтому я написал скрипт, который начинается nuke следующим образом:
Тогда ничего не происходит, потому что nuke ожидает ввода пользователем. Как бы я теперь записал в стандартный ввод?
Я делаю это, потому что я запускаю плагин с nuke, который приводит к периодическому сбою при рендеринге нескольких кадров. Итак, я бы хотел, чтобы этот скрипт мог запускаться nuke, скажите ему, чтобы он что-то сделал, а затем, если произойдет сбой, попробуйте еще раз. Итак, если есть способ зафиксировать сбой и при этом все будет в порядке, то это было бы здорово.
from subprocess import Popen, PIPE, STDOUT p = Popen(['myapp'], stdout=PIPE, stdin=PIPE, stderr=PIPE, text=True) stdout_data = p.communicate(input='data_to_write')[0]
"Лучше" из-за этого предупреждения:
Используйте communicate() вместо .stdin.write, .stdout.read или .stderr.read, чтобы избежать взаимоблокировок из-за заполнения любого из других буферов канала ОС и блокирования дочернего процесса.
Ответ 2
Чтобы прояснить некоторые моменты:
Как jroупоминал, правильный способ - использовать subprocess.communicate.
Тем не менее, при подаче stdin using subprocess.communicate с помощью input вам необходимо инициировать подпроцесс с помощью stdin=subprocess.PIPE согласно docs.
Обратите внимание, что если вы хотите отправить данные в stdin процесса, вам нужно создать объект Popen с помощью stdin=PIPE . Аналогично, чтобы получить что-либо, кроме None, в результирующем кортеже, вам также нужно указать stdout=PIPE и / или stderr=PIPE .
Также qed упомянул в комментариях, что для Python 3.4 вам нужно закодировать строку, что означает, что вам нужно передавать байты в input, а не в string . Это не совсем верно. Согласно документации, если потоки были открыты в текстовом режиме, вводом должна быть строка (источник - та же страница).
Если потоки были открыты в текстовом режиме, ввод должен быть строкой. В противном случае это должны быть байты.
Итак, если потоки не были открыты явно в текстовом режиме, то должно сработать что-то вроде приведенного ниже:
Я намеренно оставил stderr значение выше в качестве STDOUT примера.
При этом иногда вам может потребоваться вывод другого процесса, а не создавать его с нуля. Допустим, вы хотите запустить эквивалент echo -n 'CATCH\nme' | grep -i catch | wc -m. Обычно это должно возвращать символы числа в 'CATCH' плюс символ новой строки, что приводит к 6. Смысл echo здесь в том, чтобы передать CATCH\nme данные в grep. Таким образом, мы можем передать данные в grep с помощью stdin в цепочке подпроцессов Python в качестве переменной, а затем передать стандартный вывод в виде КАНАЛА в wc процесс' stdin (тем временем, избавьтесь от лишнего символа перевода строки):
# We create the first subprocess, note that we need stdin=PIPE and stdout=PIPE p1 = subprocess.Popen(['grep', '-i', what_to_catch], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
# We immediately run the first subprocess and get the result # Note that we encode the data, otherwise we'd get a TypeError p1_out = p1.communicate(input=what_to_feed.encode())[0]
# Well the result includes an '\n' at the end, # if we want to get rid of it in a VERY hacky way p1_out = p1_out.decode().strip().encode()
# We create the second subprocess, note that we need stdin=PIPE p2 = subprocess.Popen(['wc', '-m'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
# We run the second subprocess feeding it with the first subprocess' output. # We decode the output to convert to a string # We still have a '\n', so we strip that out output = p2.communicate(input=p1_out)[0].decode().strip()
Это несколько отличается от ответа здесь, где вы передаете два процесса напрямую, не добавляя данные непосредственно в Python.
Надеюсь, это кому-нибудь поможет.
Ответ 3
Начиная с subprocess 3.5, существует subprocess.run() функция, которая предоставляет удобный способ инициализации и взаимодействия с Popen() объектами. run() принимает необязательный input аргумент, через который вы можете передавать данные stdin (как вы бы использовали Popen.communicate(), но все за один раз).
Адаптация примера jro для использования run() будет выглядеть следующим образом:
import subprocess p = subprocess.run(['myapp'], input='data_to_write', capture_output=True, text=True)
После выполнения p будет CompletedProcess объект. Устанавливая capture_output значение True, мы делаем доступным p.stdout атрибут, который дает нам доступ к выходным данным, если мы заботимся об этом. text=True сообщает ему, что он должен работать с обычными строками, а не с байтами. Если вы хотите, вы также можете добавить аргумент check=True, чтобы он выдавал ошибку, если состояние выхода (доступное независимо через p.returncode) не равно 0.
Это "современный" / быстрый и простой способ сделать это.
Ответ 4
хотя .communicate() это рекомендуемый способ, он решает не все варианты использования. Для тех, кто хочет записать поток строк и прочитать его преобразование обратно из подпроцесса, вот пример кода.
defcopy_to_stdin(proc, src_file: Path, mt_file: Path): """Example task: Write data to subproc stdin. Note: run this on another thread to avoid deadlocks This function reads two parallel files (src_file and mt_file), and write them as TSV record to the stdin of the sub process. :param proc: subprocess object to write to :param src_file: path to source file :param mt_file: path to MT file """
with src_file.open() as src_lines, mt_file.open() as mt_lines: for src_line, mt_line in itertools.zip_longest(src_lines, mt_lines): if src_line isNoneor mt_line isNone: log.error(f'Input files have different number of lines') raise ValueError('Input files have different number of lines') line = src_line.rstrip('\n') + '\t' + mt_line.rstrip('\n') + '\n' proc.stdin.write(line) proc.stdin.flush() proc.stdin.close() # close stdin to signal end of input
cmd_line = ['yourcmd', 'arg1'] # fill your args src_file, mt_file = ... # your files
proc = subprocess.Popen(cmd_line, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=sys.stderr, text=True, encoding='utf8', errors='replace') try: copy_thread = threading.Thread(target=copy_to_stdin, args=(proc, src_file, mt_file)) copy_thread.start() # demonstration of reading data from stdout. for line in proc.stdout: line = line.rstrip() print(line)
copy_thread.join() returncode = proc.wait() if returncode != 0: raise RuntimeError(f'Process exited with code {returncode}') finally: if proc.returncode isNone: log.warning(f'Killing process {proc.pid}') proc.kill()