Вопрос-Ответ

Running shell command and capturing the output

Запуск команды оболочки и захват выходных данных

Я хочу написать функцию, которая выполнит команду оболочки и вернет ее выходные данные в виде строки, независимо от того, является ли это сообщением об ошибке или успешном завершении. Я просто хочу получить тот же результат, который я получил бы с помощью командной строки.

Каким был бы пример кода, который бы делал такую вещь?

Например:

def run_command(cmd):
# ??????

print run_command('mysqladmin create test -uroot -pmysqladmin12')
# Should output something like:
# mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'
Переведено автоматически
Ответ 1

Во всех официально поддерживаемых версиях Python простейшим подходом является использование subprocess.check_output функции:

>>> subprocess.check_output(['ls', '-l'])
b'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'

check_output запускает единственную программу, которая принимает в качестве входных данных только аргументы.1 Возвращает результат в точности таким, в каком он был напечатан в stdout. Если вам нужно записать входные данные в stdin, переходите к разделам run или Popen. Если вы хотите выполнять сложные команды оболочки, смотрите Примечание к shell=True в конце этого ответа.

Функция check_output работает во всех официально поддерживаемых версиях Python. Но для более поздних версий доступен более гибкий подход.

Современные версии Python (3.5 или выше): run

Если вы используете Python 3.5+ и вам не нужна обратная совместимость, новая run функция рекомендована официальной документацией для большинства задач. Он предоставляет очень общий высокоуровневый API для subprocess модуля. Чтобы получить выходные данные программы, передайте subprocess.PIPE флаг в stdout аргумент ключевого слова. Затем получите доступ к stdout атрибуту возвращаемого CompletedProcess объекта:

>>> import subprocess
>>> result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE)
>>> result.stdout
b'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'

Возвращаемое значение является bytes объектом, поэтому, если вам нужна соответствующая строка, вам понадобится decode она. Предполагая, что вызываемый процесс возвращает строку в кодировке UTF-8.:

>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'

При желании все это можно сжать до однострочного:

>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'

Если вы хотите передать входные данные процессу stdin, вы можете передать bytes объект в input аргумент ключевого слова:

>>> cmd = ['awk', 'length($0) > 5']
>>> ip = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=ip)
>>> result.stdout.decode('utf-8')
'foofoo\n'

Вы можете фиксировать ошибки, передавая stderr=subprocess.PIPE (захват в result.stderr) или stderr=subprocess.STDOUT (захват в result.stdout вместе с обычным выводом). Если вы хотите run выдавать исключение, когда процесс возвращает ненулевой код выхода, вы можете передать его check=True. (Или вы можете проверить returncode атрибут result выше.) Когда безопасность не вызывает беспокойства, вы также можете запускать более сложные команды оболочки, передавая shell=True как описано в конце этого ответа.

Более поздние версии Python еще больше упростили вышесказанное. В Python 3.7+ приведенная выше однострочная строка может быть написана следующим образом:

>>> subprocess.run(['ls', '-l'], capture_output=True, text=True).stdout
'total 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n'

Использование run этого способа добавляет немного сложности по сравнению со старым способом выполнения задач. Но теперь вы можете делать практически все, что вам нужно, с помощью одной только run функции.

Более старые версии Python (3-3.4): подробнее check_output

If you are using an older version of Python, or need modest backwards compatibility, you can use the check_output function as briefly described above. It has been available since Python 2.7.

subprocess.check_output(*popenargs, **kwargs)  

It takes takes the same arguments as Popen (see below), and returns a string containing the program's output. The beginning of this answer has a more detailed usage example. In Python 3.5+, check_output is equivalent to executing run with check=True and stdout=PIPE, and returning just the stdout attribute.

You can pass stderr=subprocess.STDOUT to ensure that error messages are included in the returned output. When security is not a concern, you can also run more complex shell commands by passing shell=True as described at the end of this answer.

Если вам нужно выполнить передачу данных из stderr или передать входные данные процессу, check_output это не будет соответствовать задаче. В этом случае смотрите Popen Примеры ниже.

Сложные приложения и устаревшие версии Python (2.6 и ниже): Popen

Если вам нужна глубокая обратная совместимость или если вам нужна более сложная функциональность, чем предоставляет check_output or run, вам придется работать напрямую с Popen объектами, которые инкапсулируют низкоуровневый API для подпроцессов.

Popen Конструктор принимает либо единственную команду без аргументов, либо список, содержащий команду в качестве первого элемента, за которым следует любое количество аргументов, каждый как отдельный элемент в списке. shlex.split может помочь преобразовать строки в списки соответствующего формата. Popenобъекты также принимают множество различных аргументов для управления процессом ввода-вывода и низкоуровневой конфигурации.

Для отправки входных данных и захвата выходных данных communicate почти всегда является предпочтительным методом. Как в:

output = subprocess.Popen(["mycmd", "myarg"], 
stdout=subprocess.PIPE).communicate()[0]

Или

>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE,
... stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

Если вы установите stdin=PIPE, communicate также позволяет передавать данные процессу через stdin:

>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
... stderr=subprocess.PIPE,
... stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo

Обратите внимание на ответ Аарона Холла, который указывает, что в некоторых системах вам может потребоваться установить stdout, stderr и stdin all в PIPE (или DEVNULL), чтобы заставить communicate вообще работать.

В некоторых редких случаях вам может потребоваться сложный захват выходных данных в режиме реального времени. Ответ Vartec предлагает путь вперед, но методы, отличные от communicate, склонны к взаимоблокировкам, если не использовать их осторожно.

Как и во всех вышеперечисленных функциях, когда безопасность не вызывает беспокойства, вы можете запускать более сложные команды оболочки, передавая shell=True.

Примечания

1. Запуск команд оболочки: shell=True аргумент

Обычно каждый вызов run, check_output или Popen конструктора выполняет единственную программу,,,,,,,,,,,. Это означает отсутствие причудливых каналов в стиле bash. Если вы хотите запускать сложные команды оболочки, вы можете передать shell=True, который поддерживают все три функции. Например:

>>> subprocess.check_output('cat books/* | wc', shell=True, text=True)
' 1299377 17005208 101299376\n'

Однако при выполнении этого возникают проблемы безопасности. Если вы делаете что-то большее, чем простые сценарии, возможно, вам было бы лучше вызывать каждый процесс отдельно и передавать выходные данные от каждого в качестве входных данных следующему, через

run(cmd, [stdout=etc...], input=other_output)

Или

Popen(cmd, [stdout=etc...]).communicate(other_output)

Соблазн напрямую подключить каналы силен; сопротивляйтесь ему. В противном случае вы, вероятно, столкнетесь с взаимоблокировками или вам придется делать хакерские вещи, подобные этому.

Ответ 2

Это намного проще, но работает только в Unix (включая Cygwin) и Python2.7.

import commands
print commands.getstatusoutput('wc -l file')

Он возвращает кортеж с (return_value, output).

Для решения, которое работает как в Python2, так и в Python3, используйте вместо этого subprocess модуль:

from subprocess import Popen, PIPE
output = Popen(["date"],stdout=PIPE)
response = output.communicate()
print response
Ответ 3

python3 Предложенияsubprocess.getoutput():

import subprocess
output = subprocess.getoutput("ls -l")
print(output)
Ответ 4

Что-то вроде этого:

def runProcess(exe):    
p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while(True):
# returns None while subprocess is running
retcode = p.poll()
line = p.stdout.readline()
yield line
if retcode is not None:
break

Обратите внимание, что я перенаправляю stderr на stdout, возможно, это не совсем то, что вы хотите, но мне также нужны сообщения об ошибках.

Эта функция выдает строку за строкой по мере их поступления (обычно вам нужно дождаться завершения подпроцесса, чтобы получить выходные данные в целом).

В вашем случае использование будет:

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
print line,
python subprocess