Запуск команды оболочки и захват вывода
Я хочу написать функцию, которая выполнит команду оболочки и вернет ее вывод в виде строки, независимо от того, является ли это сообщение об ошибке или успехе. Я просто хочу получить тот же результат, что и в командной строке.
Каким будет пример кода, который бы сделал такую вещь?
Например:
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, все еще широко используемых (2. 7+). 2 Но для более поздних версий это уже не рекомендуемый подход.
Современные версии 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']
>>> input = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
>>> result.stdout.decode('utf-8')
'foofoo\n'
Вы можете result.stderr
ошибки, передав stderr=subprocess.PIPE
(result.stderr
в result.stderr
) или stderr=subprocess.STDOUT
(result.stdout
в result.stdout
вместе с обычным выводом). Когда безопасность не имеет значения, вы также можете запускать более сложные команды оболочки, передавая shell=True
как описано в примечаниях ниже.
Это добавляет немного сложности по сравнению со старым способом ведения дел. Но я думаю, что это того стоит: теперь вы можете делать практически все, что вам нужно, только с помощью функции run
.
Старые версии Python (2.7-3.4): check_output
Если вы используете более старую версию Python или вам нужна скромная обратная совместимость, вы, вероятно, можете использовать функцию check_output
как кратко описано выше. Он был доступен с Python 2.7.
subprocess.check_output(*popenargs, **kwargs)
Он принимает те же аргументы, что и Popen
(см. Ниже), и возвращает строку, содержащую выходные данные программы. В начале этого ответа приведен более подробный пример использования. В Python 3.5 и выше, check_output
эквивалентно выполнению run
с check=True
и stdout=PIPE
и возвращению только атрибута stdout
.
Вы можете передать stderr=subprocess.STDOUT
чтобы убедиться, что сообщения об ошибках включены в возвращаемый вывод, но в некоторых версиях Python передача stderr=subprocess.PIPE
в check_output
может вызвать взаимоблокировки. Когда безопасность не имеет значения, вы также можете запускать более сложные команды оболочки, передавая shell=True
как описано в примечаниях ниже.
Если вам нужно передать данные из stderr
или передать входные данные процессу, check_output
не будет соответствовать задаче. Смотрите примеры Popen
ниже в этом случае.
Сложные приложения и устаревшие версии Python (2.6 и ниже): Popen
Если вам нужна глубокая обратная совместимость или если вам нужна более сложная функциональность, чем обеспечивает check_output
, вам придется работать напрямую с объектами Popen
, которые инкапсулируют низкоуровневый API для подпроцессов.
Конструктор Popen
принимает либо одну команду без аргументов, либо список, содержащий команду в качестве первого элемента, за которым следует любое количество аргументов, каждый из которых является отдельным элементом в списке. shlex.split
может помочь разобрать строки в соответствующим образом отформатированных списках. Popen
объекты также принимают множество различных аргументов для управления 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
), чтобы DEVNULL
установить communicate
для работы.
В некоторых редких случаях вам может потребоваться сложный захват выходных данных в режиме реального времени. Ответ Vartec предлагает дальнейший путь, но методы, отличные от communicate
, подвержены тупикам, если их не использовать осторожно.
Как и во всех вышеперечисленных функциях, когда безопасность не имеет значения, вы можете запускать более сложные команды оболочки, передавая shell=True
.
Заметки
1. Запуск команд shell=True
аргумент shell=True
Как правило, каждый вызов run
, check_output
или Popen
конструктор выполняет одну программу. Это означает, что нет причудливых трубок в стиле Баш. Если вы хотите запускать сложные команды оболочки, вы можете передать shell=True
, который поддерживают все три функции.
Однако при этом возникают проблемы с безопасностью. Если вы делаете что-то большее, чем простой сценарий, вам лучше вызывать каждый процесс отдельно и передавать выходные данные каждого из них как входные данные следующему через
run(cmd, [stdout=etc...], input=other_output)
Или же
Popen(cmd, [stdout=etc...]).communicate(other_output)
Искушение напрямую соединить трубы сильно; сопротивляться этому. В противном случае, вы, вероятно, увидеть тупики или должны сделать Hacky таких вещей, как это.
2. Юникод соображения
check_output
возвращает строку в Python 2, а bytes
объекта в Python 3. Это стоит того момента, чтобы узнать о юникода, если вы еще не сделали.
Ответ 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
Что-то вроде того:
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,
Ответ 4
Ответ Vartec не читает все строки, поэтому я сделал версию, которая сделала:
def run_command(command):
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
return iter(p.stdout.readline, b'')
Использование совпадает с принятым ответом:
command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
print(line)
Ответ 5
Это сложное, но супер простое решение, которое работает во многих ситуациях:
import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()
Временный файл (здесь tmp) создается с выходом команды, и вы можете прочитать из него ваш желаемый результат.
Дополнительная заметка из комментариев:
Вы можете удалить файл tmp в случае одноразового задания. Если вам нужно сделать это несколько раз, нет необходимости удалять tmp.
os.remove('tmp')
Ответ 6
В Python 3.5:
import subprocess
output = subprocess.run("ls -l", shell=True, stdout=subprocess.PIPE,
universal_newlines=True)
print(output.stdout)
Ответ 7
У меня была та же проблема, но я нашел очень простой способ сделать это:
import subprocess
output = subprocess.getoutput("ls -l")
print(output)
Надеюсь, это поможет
Примечание: это решение специфично для Python3, так как subprocess.getoutput()
не работает в Python2
Ответ 8
Вы можете использовать следующие команды для запуска любой команды оболочки. Я использовал их на Ubuntu.
import os
os.popen('your command here').read()
Примечание. Это устарело с версии Python 2.6. Теперь вы должны использовать subprocess.Popen
. subprocess.Popen
. Ниже приведен пример
import subprocess
p = subprocess.Popen("Your command", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
print p.split("\n")
Ответ 9
Современное решение Python (> = 3.1):
import subprocess
res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)
Ответ 10
Ваш пробег мая может, я попытался @senderle запустить решение Vartec в Windows на Python 2.6.5, но я получал ошибки, и никаких других решений не работало. Моя ошибка: WindowsError: [Error 6] The handle is invalid
.
Я обнаружил, что мне пришлось назначить PIPE каждому дескриптору, чтобы вернуть его, который я ожидал, - следующее работало для меня.
import subprocess
def run_command(cmd):
"""given shell command, returns communication tuple of stdout and stderr"""
return subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE).communicate()
и вызывайте так: ([0]
получает первый элемент кортежа, stdout
):
run_command('tracert 11.1.0.1')[0]
Узнав больше, я считаю, что мне нужны эти аргументы, потому что я работаю над настраиваемой системой, которая использует разные дескрипторы, поэтому мне пришлось напрямую управлять всеми файлами std.
Чтобы остановить всплывающие окна консоли (с помощью Windows), сделайте следующее:
def run_command(cmd):
"""given shell command, returns communication tuple of stdout and stderr"""
# instantiate a startupinfo obj:
startupinfo = subprocess.STARTUPINFO()
# set the use show window flag, might make conditional on being in Windows:
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# pass as the startupinfo keyword argument:
return subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=startupinfo).communicate()
run_command('tracert 11.1.0.1')
Ответ 11
У меня был немного другой вкус той же проблемы со следующими требованиями:
- Захват и возврат сообщений STDOUT по мере их накопления в буфере STDOUT (т.е. в реальном времени).
- @vartec решил эту питонову с помощью генераторов и "урожая"
ключевое слово выше
- Распечатайте все строки STDOUT (даже если процесс завершается до того, как буфер STDOUT может быть полностью прочитан)
- Не тратьте процессорные циклы на процесс на высокочастотном уровне
- Проверьте код возврата подпроцесса
- Распечатайте STDERR (отдельно от STDOUT), если мы получим ненулевой код возврата ошибки.
Я объединил и изменил предыдущие ответы, чтобы придумать следующее:
import subprocess
from time import sleep
def run_command(command):
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
# Read stdout from subprocess until the buffer is empty !
for line in iter(p.stdout.readline, b''):
if line: # Don't print blank lines
yield line
# This ensures the process has completed, AND sets the 'returncode' attr
while p.poll() is None:
sleep(.1) #Don't waste CPU-cycles
# Empty STDERR buffer
err = p.stderr.read()
if p.returncode != 0:
# The run_command() function is responsible for logging STDERR
print("Error: " + str(err))
Этот код будет выполнен так же, как и предыдущие ответы:
for line in run_command(cmd):
print(line)
Ответ 12
Если вам нужно запустить команду оболочки для нескольких файлов, это помогло мне.
import os
import subprocess
# Define a function for running commands and capturing stdout line by line
# (Modified from Vartec solution because it wasn't printing all lines)
def runProcess(exe):
p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return iter(p.stdout.readline, b'')
# Get all filenames in working directory
for filename in os.listdir('./'):
# This command will be run on each file
cmd = 'nm ' + filename
# Run the command and capture the output line by line.
for line in runProcess(cmd.split()):
# Eliminate leading and trailing whitespace
line.strip()
# Split the output
output = line.split()
# Filter the output and print relevant lines
if len(output) > 2:
if ((output[2] == 'set_program_name')):
print filename
print line
Изменить: просто увидел решение Макс Перссон с предложением Ю. Ф. Себастьяна. Поехал вперед и включил это.
Ответ 13
Разделение начальной команды для subprocess
может быть сложным и громоздким.
Используйте shlex.split()
чтобы помочь себе.
Пример команды
git log -n 5 --since "5 years ago" --until "2 year ago"
Код
from subprocess import check_output
from shlex import split
res = check_output(split('git log -n 5 --since "5 years ago" --until "2 year ago"'))
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'
Без shlex.split()
код будет выглядеть следующим образом
res = check_output([
'git',
'log',
'-n',
'5',
'--since',
'5 years ago',
'--until',
'2 year ago'
])
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'
Ответ 14
Согласно @senderle, если вы используете Python3.6, как я:
def sh(cmd, input=""):
rst = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=input.encode("utf-8"))
assert rst.returncode == 0, rst.stderr.decode("utf-8")
return rst.stdout.decode("utf-8")
sh("ls -a")
Будет действовать так же, как вы запускаете команду в Bash
Ответ 15
Если вы используете модуль Python subprocess
, вы можете обрабатывать STDOUT, STDERR и код возврата команды по отдельности. Вы можете увидеть пример полной реализации вызывающей команды. Конечно, вы можете расширить его с помощью try..except
если хотите.
Приведенная ниже функция возвращает STDOUT, STDERR и код возврата, чтобы вы могли обрабатывать их в другом скрипте.
import subprocess
def command_caller(command=None)
sp = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=False)
out, err = sp.communicate()
if sp.returncode:
print(
"Return code: %(ret_code)s Error message: %(err_msg)s"
% {"ret_code": sp.returncode, "err_msg": err}
)
return sp.returncode, out, err
Ответ 16
например, execute ('ls -ahl')
дифференцированных трех/четырех возможных возвратов и платформ ОС:
- нет вывода, но успешно выполняется
- выводить пустую строку, успешно запускать
- выполнить сбой
- вывести что-нибудь, успешно выполнить
ниже
def execute(cmd, output=True, DEBUG_MODE=False):
"""Executes a bash command.
(cmd, output=True)
output: whether print shell output to screen, only affects screen display, does not affect returned values
return: ...regardless of output=True/False...
returns shell output as a list with each elment is a line of string (whitespace stripped both sides) from output
could be
[], ie, len()=0 --> no output;
[''] --> output empty line;
None --> error occured, see below
if error ocurs, returns None (ie, is None), print out the error message to screen
"""
if not DEBUG_MODE:
print "Command: " + cmd
# https://stackoverflow.com/a/40139101/2292993
def _execute_cmd(cmd):
if os.name == 'nt' or platform.system() == 'Windows':
# set stdin, out, err all to PIPE to get results (other than None) after run the Popen() instance
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
else:
# Use bash; the default is sh
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable="/bin/bash")
# the Popen() instance starts running once instantiated (??)
# additionally, communicate(), or poll() and wait process to terminate
# communicate() accepts optional input as stdin to the pipe (requires setting stdin=subprocess.PIPE above), return out, err as tuple
# if communicate(), the results are buffered in memory
# Read stdout from subprocess until the buffer is empty !
# if error occurs, the stdout is '', which means the below loop is essentially skipped
# A prefix of 'b' or 'B' is ignored in Python 2;
# it indicates that the literal should become a bytes literal in Python 3
# (e.g. when code is automatically converted with 2to3).
# return iter(p.stdout.readline, b'')
for line in iter(p.stdout.readline, b''):
# # Windows has \r\n, Unix has \n, Old mac has \r
# if line not in ['','\n','\r','\r\n']: # Don't print blank lines
yield line
while p.poll() is None:
sleep(.1) #Don't waste CPU-cycles
# Empty STDERR buffer
err = p.stderr.read()
if p.returncode != 0:
# responsible for logging STDERR
print("Error: " + str(err))
yield None
out = []
for line in _execute_cmd(cmd):
# error did not occur earlier
if line is not None:
# trailing comma to avoid a newline (by print itself) being printed
if output: print line,
out.append(line.strip())
else:
# error occured earlier
out = None
return out
else:
print "Simulation! The command is " + cmd
print ""
Ответ 17
'#!/usr/bin/python3 import subprocess nginx_ver = subprocess.getstatusoutput("nginx -v") print (nginx_ver)'
Ответ 18
Вывод может быть перенаправлен в текстовый файл, а затем прочитать его обратно.
import subprocess
import os
import tempfile
def execute_to_file(command):
"""
This function execute the command
and pass its output to a tempfile then read it back
It is usefull for process that deploy child process
"""
temp_file = tempfile.NamedTemporaryFile(delete=False)
temp_file.close()
path = temp_file.name
command = command + " > " + path
proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
if proc.stderr:
# if command failed return
os.unlink(path)
return
with open(path, 'r') as f:
data = f.read()
os.unlink(path)
return data
if __name__ == "__main__":
path = "Somepath"
command = 'ecls.exe /files ' + path
print(execute(command))