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

676

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

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

Например:

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'
Теги:
subprocess

14 ответов

779
Лучший ответ

Ответ на этот вопрос зависит от версии используемого вами 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'

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

Вы можете передать stderr=subprocess.STDOUT, чтобы сообщения об ошибках включались в возвращаемый вывод, но не передавайте 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

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

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

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

Примечания

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

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

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

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

или

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

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

2. Unicode соображения

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

  • 2
    Да, я видел это, но я использую 2.6 (моя ошибка, не говоря уже о версии Python)
  • 3
    Оба с check_output() и communicate() вы должны ждать , пока процесс не будет сделан, с poll() вы получаете выход , как это происходит. На самом деле зависит, что вам нужно.
Показать ещё 25 комментариев
167

Это проще, но работает только с Unix (включая Cygwin).

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

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

Это работает только в python2.7: он недоступен на python3. Для решения, которое работает в обоих, вместо этого используйте модуль subprocess:

import subprocess
output=subprocess.Popen(["date"],stdout=PIPE)
response=output.communicate()
print response
  • 29
    Устаревший, но очень полезный для старых версий Python без subprocess.check_output
  • 20
    Обратите внимание, что это зависит от Unix. Например, в Windows произойдет сбой.
Показать ещё 3 комментария
97

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

def runProcess(exe):    
    p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    while(True):
      retcode = p.poll() #returns None while subprocess is running
      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,
  • 1
    Спасибо за помощь! Но функция уходит в бесконечный цикл для меня ...
  • 0
    Не забудьте реализовать какой-то активный цикл, чтобы получить выходные данные, чтобы избежать потенциальной тупиковой ситуации в функциях wait и call .
Показать ещё 10 комментариев
56

Ответ 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)
  • 6
    вы можете использовать return iter(p.stdout.readline, b'') вместо цикла while
  • 1
    Это довольно крутое использование iter, не знал этого! Я обновил код.
Показать ещё 5 комментариев
42

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

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

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

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

os.remove('tmp')
  • 3
    Хаки, но супер просто + работает где угодно .. могу объединить его с mktemp чтобы заставить его работать в многопоточных ситуациях
  • 2
    Возможно, самый быстрый способ, но лучше добавить os.remove('tmp') чтобы сделать его "безфайловым".
Показать ещё 3 комментария
16

В Python 3.5:

import subprocess

output = subprocess.run("ls -l", shell=True, stdout=subprocess.PIPE, 
                        universal_newlines=True)
print(output.stdout)
13

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

import os
os.popen('your command here').read()
  • 1
    Устаревший с версии 2.6 - docs.python.org/2/library/os.html#os.popen
  • 1
    @FilippoVitale Спасибо. Я не знал, что это устарело.
Показать ещё 1 комментарий
12

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

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

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

Примечание. Это решение является специфичным для python3, поскольку subprocess.getoutput() не работает в python2

  • 0
    Как это решает проблему ОП? Пожалуйста, дополните.
  • 2
    Возвращает вывод команды в виде строки, так просто
Показать ещё 5 комментариев
12

Современное решение Python ( >= 3.1):

 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')
  • 1
    Интересно - это должно быть в Windows. Я добавлю примечание, указывающее на это на случай, если люди получат похожие ошибки.
  • 0
    используйте DEVNULL вместо subprocess.PIPE если вы не пишете / читаете из канала, иначе вы можете повесить дочерний процесс.
Показать ещё 1 комментарий
7

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

  • Захват и возврат сообщений 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)
  • 0
    Не могли бы вы объяснить, как добавление sleep (.1) не приведет к потере циклов процессора?
  • 1
    Если бы мы продолжали вызывать p.poll() без перерыва между вызовами, мы бы тратили циклы ЦП, вызывая эту функцию миллионы раз. Вместо этого мы «дросселируем» наш цикл, сообщая ОС, что нам не нужно беспокоиться в течение следующей 1/10 секунды, чтобы она могла выполнять другие задачи. (Возможно, что p.poll () тоже спит, что делает наш оператор сна избыточным).
2

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

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

Изменить: просто увидел решение Макс Перссон с предложением Ю. Ф. Себастьяна. Поехал вперед и включил это.

0

'#!/usr/bin/python3 import subprocess nginx_ver = subprocess.getstatusoutput("nginx -v") print (nginx_ver)'

0

например, 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 ""

Ещё вопросы

Сообщество Overcoder
Наверх
Меню