Как перенаправить вывод с помощью подпроцесса в Python?

97

Что я делаю в командной строке:

cat file1 file2 file3 > myfile

Что я хочу делать с Python:

import subprocess, shlex
my_cmd = 'cat file1 file2 file3 > myfile'
args = shlex.split(my_cmd)
subprocess.call(args) # spits the output in the window i call my python program
catatemypythoncode
источник
Выполнение такой команды в подпроцессе ничего не даст. Может быть, вы хотите запустить его без > myfile, перенаправляющего вывод из cat file1 file2 file3 в python?
PoltoS
@PoltoS Я хочу объединить несколько файлов, а затем обработать полученный файл. Я думал, что использование cat было самой простой альтернативой. Есть ли лучший / питонический способ сделать это?
catatemypythoncode
os.sendfile()возможно решение на основе, см. Воспроизведение команды unix cat в python
jfs
1
Я думаю, что перенаправление вывода ('>' или '>>') не работает в subprocess.Popen (по крайней мере, в Python 2.7) (в режиме shell = True) В этом примере, как отмечают другие, вы можете обойти для этого не используется перенаправление, но в других случаях перенаправление полезно. Если перенаправление или конвейерная обработка не поддерживаются в subprocess.Popen, это должно быть задокументировано (и / или os.system () не должна быть устаревшей, пока это не будет исправлено)
Рибо,

Ответы:

20

ОБНОВЛЕНИЕ: os.system не рекомендуется, хотя она все еще доступна в Python 3.


Использование os.system:

os.system(my_cmd)

Если вы действительно хотите использовать подпроцесс, вот решение (в основном взято из документации для подпроцесса):

p = subprocess.Popen(my_cmd, shell=True)
os.waitpid(p.pid, 0)

OTOH, вы можете полностью избежать системных вызовов:

import shutil

with open('myfile', 'w') as outfile:
    for infile in ('file1', 'file2', 'file3'):
        shutil.copyfileobj(open(infile), outfile)
Марсело Кантос
источник
1
Это работает, но позвольте мне тогда спросить вас: в чем смысл библиотеки подпроцессов, если os.system уже выполняет свою работу? У меня такое чувство, что мне следовало использовать подпроцесс, поскольку это библиотека, предназначенная для этой задачи, хотя, поскольку я делаю это только для себя, на этот раз мне будет хорошо пользоваться os.system.
catatemypythoncode
Библиотека подпроцессов намного более гибкая os.systemи может os.systemточно моделировать , но с ней также сложнее работать.
Марсело Кантос,
13
os.systemприходил раньше subprocess. Первый - это устаревший API, который второй намеревается заменить.
Санта
5
@catatemypythoncode: вам не следует использовать os.system()или shell=True. Чтобы перенаправить вывод подпроцесса, используйте stdoutпараметр, как показано в ответе Райана Томпсона . Хотя вам не нужен subprocess ( cat) в вашем случае, вы можете объединить файлы, используя чистый Python.
jfs
4
OTOH = С другой стороны
Cephlin 09
272

В Python 3.5+ для перенаправления вывода просто передайте дескриптор открытого файла для stdoutаргумента subprocess.run:

# Use a list of args instead of a string
input_files = ['file1', 'file2', 'file3']
my_cmd = ['cat'] + input_files
with open('myfile', "w") as outfile:
    subprocess.run(my_cmd, stdout=outfile)

Как отмечали другие, использование внешней команды, подобной catэтой, совершенно неуместно.

Райан С. Томпсон
источник
9
Это должен быть ответ на общий вопрос о конвейере при использовании оболочки из Python
Kaushik Ghose
47
Это правильный ответ, а не тот, который отмечен как правильный.
Джастин Блейк
7
Для использования Python 3.5+, subprocess.run(my_cmd, stdout=outfile)который заменяетsubprocess.call(...)
Остин Йейтс
1
Следует отметить, что это не работает с пользовательскими файловыми объектами, если у них нет поля fileno (если они не являются настоящим файлом.)
Элиэзер Мирон
1
Поскольку Python <3.5 на данный момент устарел, я обновил ответ вашим комментарием @AustinYates.
Грег Дубицки,
5

@PoltoS Я хочу объединить несколько файлов, а затем обработать полученный файл. Я думал, что использование cat было самой простой альтернативой. Есть ли лучший / питонический способ сделать это?

Конечно:

with open('myfile', 'w') as outfile:
    for infilename in ['file1', 'file2', 'file3']:
        with open(infilename) as infile:
            outfile.write(infile.read())
SingleNegationElimination
источник
1
size = 'ffprobe -v error -show_entries format=size -of default=noprint_wrappers=1:nokey=1 dump.mp4 > file'
proc = subprocess.Popen(shlex.split(size), shell=True)
time.sleep(1)
proc.terminate() #proc.kill() modify it by a suggestion
size = ""
with open('file', 'r') as infile:
    for line in infile.readlines():
        size += line.strip()

print(size)
os.remove('file')

Когда вы используете подпроцесс , процесс должен быть убит. Это пример. Если вы не убиваете процесс, файл будет пустым и вы ничего не сможете прочитать. Он может работать в Windows. Я не могу убедиться, что он может запустить в Unix.

Wyx
источник
1
Это плохой пример кода (он не будет работать на Unix, она демонстрирует плохие практики for line in .readlines():, s +=) и proc.kill()может привести к потере информации в целом (это не позволяет подпроцесс прекратить корректно (на Unix) - промываться содержание теряется ). В любом случае замечание о буферизации уместнее в качестве комментария.
jfs
Я запускаю его в Windows, все в порядке (потому что kill равносильно завершению в Windows). В Unix, возможно, вам следует использовать proc.terminate (). @ JF Sebastian У меня нет системы Unix на моем компьютере.
wyx
Если вы на Windows , то падение shlex.split(), падение shell=True, падение >file, падение open()и т.д. и использование stdout=PIPE, Timer(1, proc.terminate).start(); output = proc.communicate()[0]вместо. Вот полный пример . Другие решения: прекратить чтение вывода процесса в Python без зависания? Примечание: в вопросе нет требования, что вам нужно завершить дочерний процесс вручную - вы можете решить другие проблемы, например, процесс может вести себя иначе, если его стандартный вывод является tty, но не по теме.
jfs
0

Один интересный случай - обновить файл, добавив к нему аналогичный файл. Тогда не нужно было бы создавать новый файл в процессе. Это особенно полезно в случае, когда нужно добавить большой файл. Вот одна из возможностей использования командной строки teminal непосредственно из python.

import subprocess32 as sub

with open("A.csv","a") as f:
    f.flush()
    sub.Popen(["cat","temp.csv"],stdout=f)
DJJ
источник