Запуск команды оболочки и получение вывода - PullRequest
743 голосов
/ 21 января 2011

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

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

Например:

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'

Ответы [ 15 ]

924 голосов
/ 21 января 2011

Ответ на этот вопрос зависит от версии 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 или выше и не требуется обратная совместимость , рекомендуется new 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'

Вы можете зафиксировать ошибки, передав stderr=subprocess.PIPE (перехват в result.stderr) или stderr=subprocess.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 также принимают хост с различными аргументами для управления процессами ввода-вывода и низкоуровневой конфигурации.

Для отправки ввода и вывода вывода, 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 все в PIPE (или DEVNULL) в заставить communicate работать вообще.

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

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

Примечания

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

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

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

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

Или

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

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

2. Особенности Unicode

check_output возвращает строку в Python 2, но объект bytes в Python 3. Стоит потратить немного времени, чтобы узнать о Unicode , если вы этого еще не сделали.

180 голосов
/ 13 февраля 2012

Это намного проще, но работает только на 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
105 голосов
/ 21 января 2011

Примерно так:

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, это может быть не совсем то, что вы хотите, но я также хочу сообщения об ошибках.функция выдает построчно по мере их поступления (обычно вам нужно подождать, пока подпроцесс завершит работу, чтобы получить вывод в целом).

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

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,
63 голосов
/ 30 октября 2012

Ответ 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)
44 голосов
/ 22 августа 2016

Это хитрое , но супер простое решение, которое работает во многих ситуациях:

import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()

Временный файл (здесь tmp) создается с помощьювывод команды и вы можете прочитать из нее желаемый вывод.

Дополнительные примечания к комментариям: Вы можете удалить файл tmp в случае одноразового задания.Если вам нужно сделать это несколько раз, нет необходимости удалять tmp.

os.remove('tmp')
19 голосов
/ 12 ноября 2016

У меня была такая же проблема, но я нашел очень простой способ сделать это:

import subprocess
output = subprocess.getoutput("ls -l")
print(output)

Надеюсь, это поможет

Примечание: это решение специфично для Python3, так как subprocess.getoutput() не работает в Python2

15 голосов
/ 04 апреля 2017

Вы можете использовать следующие команды для запуска любой команды оболочки.Я использовал их в Ubuntu.

import os
os.popen('your command here').read()
11 голосов
/ 19 февраля 2014

Ваш пробег может меняться, я попытался @ 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]

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

Чтобы остановить всплывающие окна консоли (в 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')
9 голосов
/ 19 октября 2016

У меня была немного другая разновидность той же проблемы со следующими требованиями:

  1. Захватывать и возвращать сообщения STDOUT по мере их накопления в буфере STDOUT (т.е. в реальном времени).
    • @ vartec решил эту проблему Python, используя генераторы и «yield»
      Ключевое слово выше
  2. Печать всех строк STDOUT (, даже если процесс завершается до того, как буфер STDOUT может быть полностью прочитан )
  3. Не тратьте циклы процессора, опрашивая процесс на высокой частоте
  4. Проверьте код возврата подпроцесса
  5. Вывести 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)
3 голосов
/ 01 апреля 2015

Если вам нужно запустить команду оболочки для нескольких файлов, это помогло мне.

import os
import subprocess

# Define a function for running commands and capturing stdout line by line
# (Modified from Vartec's 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

Редактировать: Только что увидел решение Макса Перссона с предложением Дж. Ф. Себастьяна. Ушел вперед и включил это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...