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

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

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

Например:

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))