Выполнение команд Bash в Python

На моем локальном компьютере я запускаю скрипт python, который содержит эту строку

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

Это отлично работает.

Затем я запускаю тот же код на сервере и получаю следующее сообщение об ошибке

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

Итак, я вставил print bashCommand, который печатает меня, а не команду в терминале, прежде чем запустить его с os.system().

Конечно, я снова получаю ошибку (вызванную os.system(bashCommand)), но перед этой ошибкой она печатает команду в терминале. Затем я просто скопировал этот вывод, вставил копию в терминал и нажал Enter, и это работает...

Кто-нибудь знает, что происходит?

Ответ 1

Не используйте os.system. Он устарел в пользу подпроцесса. Из документов: "Этот модуль намеревается заменить несколько старых модулей и функций: os.system, os.spawn ".

Как и в вашем случае:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

Ответ 2

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

  • Предпочитайте subprocess.run(), а не subprocess.check_call(), а друзей, а не subprocess.call(), а не subprocess.Popen(), а не os.system(), а не os.popen().
  • Понять и, вероятно, использовать text=True, он же universal_newlines=True.
  • Понять значение shell=True или shell=False и то, как оно меняет цитирование и доступность удобства оболочки.
  • Понять различия между sh и Bash
  • Понять, как подпроцесс отделен от своего родителя и, как правило, не может изменить родителя.
  • Избегайте запуска интерпретатора Python как подпроцесса Python.

Эти темы более подробно описаны ниже.

Предпочитаю subprocess.run() или subprocess.check_call()

Функция subprocess.Popen() является рабочей лошадкой низкого уровня, но ее сложно использовать правильно, и в итоге вы копируете/вставляете несколько строк кода... которые обычно уже существуют в стандартной библиотеке как набор функций-оберток более высокого уровня для различные цели, которые представлены более подробно в следующем.

Вот параграф из документации:

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

К сожалению, доступность этих функций-оболочек отличается в разных версиях Python.

  • subprocess.run() был официально представлен в Python 3.5. Он предназначен для замены всего следующего.
  • subprocess.check_output() был представлен в Python 2.7/3.1. Это в основном эквивалентно subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call() был представлен в Python 2.5. Это в основном эквивалентно subprocess.run(..., check=True)
  • subprocess.call() был представлен в Python 2.4 в исходном модуле subprocess (PEP-324). Это в основном эквивалентно subprocess.run(...).returncode

API высокого уровня против subprocess.Popen()

Измененный и расширенный subprocess.run() более логичен и более универсален, чем старые устаревшие функции, которые он заменяет. Он возвращает CompletedProcess объект, который имеет различные методы, которые позволяют вам получить состояние выхода, стандартный вывод и несколько других результатов и индикаторов состояния из готового подпроцесса.

subprocess.run() - это путь, если вам просто нужна программа для запуска и возврата управления в Python. Для более сложных сценариев (фоновые процессы, возможно, с интерактивным вводом-выводом с родительской программой Python) вам все равно нужно использовать subprocess.Popen() и самостоятельно позаботиться обо всем. Это требует довольно сложного понимания всех движущихся частей и не должно быть предпринято легко. Более простой объект Popen представляет (возможно, все еще работающий) процесс, которым нужно управлять из вашего кода в течение оставшейся части времени жизни подпроцесса.

Возможно, следует подчеркнуть, что просто subprocess.Popen() просто создает процесс. Если вы оставите все как есть, у вас будет подпроцесс, работающий одновременно с Python, так что это "фоновый" процесс. Если ему не нужно выполнять ввод или вывод или иным образом координировать действия с вами, он может выполнять полезную работу параллельно с вашей программой на Python.

Избегайте os.system() и os.popen()

Начиная с вечного времени (ну, начиная с Python 2.5) в документации модуля os содержится рекомендация предпочесть subprocess, а не os.system():

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

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

PEP-324 (о котором уже упоминалось выше) содержит более подробное обоснование того, почему os.system является проблематичным и как subprocess пытается решить эти проблемы.

os.popen() раньше был еще более крайне обескураженным:

Устаревший с версии 2.6: эта функция устарела. Используйте модуль subprocess.

Однако с тех пор в Python 3 было переопределено простое использование subprocess, и подробности перенаправляются в документацию subprocess.Popen().

Поймите и обычно используйте check=True

Вы также заметите, что subprocess.call() имеет много тех же ограничений, что и os.system(). При регулярном использовании обычно следует проверять, завершился ли процесс успешно, что делают subprocess.check_call() и subprocess.check_output() (где последний также возвращает стандартный вывод завершенного подпроцесса). Точно так же вы обычно должны использовать check=True с subprocess.run(), если только вам не требуется, чтобы подпроцесс возвращал состояние ошибки.

На практике с check=True или subprocess.check_* Python сгенерирует исключение CalledProcessError, если подпроцесс вернет ненулевой статус выхода.

Распространенной ошибкой в subprocess.run() является пропуск check=True и удивление при сбое нижестоящего кода в случае сбоя подпроцесса.

С другой стороны, общая проблема с check_call() и check_output() заключалась в том, что пользователи, которые слепо использовали эти функции, были удивлены, когда возникло исключение, например. когда grep не нашел соответствия. (В любом случае, вам, вероятно, следует заменить grep на собственный код Python, как описано ниже.)

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

Понять и, вероятно, использовать text=True aka universal_newlines=True

Начиная с Python 3, строки внутри Python являются строками Unicode. Но нет никакой гарантии, что подпроцесс генерирует выходные данные Unicode или строки вообще.

(Если различия не сразу очевидны, рекомендуется Нед Нед Батчелдер Pragmatic Unicode, если не прямо, чтение. За ссылкой, если вы предпочитаете, за 36-минутной видео-презентацией, хотя чтение страницы будет вероятно, займет значительно меньше времени.)

В глубине души Python должен извлечь буфер bytes и как-то его интерпретировать. Если он содержит двоичный объект двоичных данных, он не должен быть декодирован в строку Unicode, потому что это склонное к ошибкам и вызывающее ошибки поведение - именно такая разновидность надоедливого поведения, которая пронизывала многие сценарии Python 2, до того, как появился способ правильно различать закодированный текст и двоичные данные.

С помощью text=True вы сообщаете Python, что фактически ожидаете возврата текстовых данных в кодировке системы по умолчанию и что они должны быть декодированы в строку Python (Unicode) в меру возможностей Python (обычно UTF-8 на любом Умеренно современная система, разве что винда?)

Если это не то, что вы запрашиваете обратно, Python просто выдаст вам строки bytes в строках stdout и stderr. Возможно, в какой-то момент вы узнаете, что они были текстовыми строками, и знаете их кодировку. Затем вы можете их декодировать.

normal = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True,
    text=True)
print(normal.stdout)

convoluted = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))

Python 3.7 представил более короткий и более описательный и понятный псевдоним text для аргумента ключевого слова, который ранее несколько вводил в заблуждение под названием universal_newlines.

Поймите shell=True против shell=False

С помощью shell=True вы передаете одну строку в вашу оболочку, а оболочка берет ее оттуда.

С помощью shell=False вы передаете список аргументов ОС, минуя оболочку.

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

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

Распространенной ошибкой является использование shell=True, а затем передача Python списка токенов или наоборот. В некоторых случаях это срабатывает, но на самом деле плохо определено и может сломаться интересными способами.

# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')

# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    shell=True)

# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
    shell=True)

correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    # Probably don't forget these, too
    check=True, text=True)

# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
    shell=True,
    # Probably don't forget these, too
    check=True, text=True)

Общая реплика "но это работает для меня" не является полезным опровержением, если вы точно не понимаете, при каких обстоятельствах она может перестать работать.

Пример рефакторинга

Очень часто функции оболочки могут быть заменены собственным кодом Python. Простые сценарии Awk или sed, вероятно, вместо этого следует просто перевести на Python.

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

cmd = '''while read -r x;
   do ping -c 3 "$x" | grep 'round-trip min/avg/max'
   done <hosts.txt'''

# Trivial but horrible
results = subprocess.run(
    cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)

# Reimplement with shell=False
with open('hosts.txt') as hosts:
    for host in hosts:
        host = host.rstrip('\n')  # drop newline
        ping = subprocess.run(
             ['ping', '-c', '3', host],
             text=True,
             stdout=subprocess.PIPE,
             check=True)
        for line in ping.stdout.split('\n'):
             if 'round-trip min/avg/max' in line:
                 print('{}: {}'.format(host, line))

Здесь нужно кое-что отметить:

  • С shell=False вам не нужны кавычки, которые требует оболочка вокруг строк. В любом случае, вставлять кавычки - это ошибка.
  • Часто имеет смысл запускать как можно меньше кода в подпроцессе. Это дает вам больше контроля над выполнением из вашего кода Python.
  • Сказав это, сложные конвейеры оболочки утомительны и иногда сложно переопределить в Python.

Реорганизованный код также показывает, насколько действительно полезна оболочка для вас с очень кратким синтаксисом - к лучшему или к худшему. Python говорит, что явное лучше, чем неявное, но код Python довольно многословен и, возможно, выглядит более сложным, чем на самом деле. С другой стороны, он предлагает ряд точек, в которых вы можете захватить контроль посреди чего-то другого, о чем тривиально свидетельствует улучшение, которое мы можем легко включить в имя хоста вместе с выводом команды оболочки. (Это ни в коем случае не является сложной задачей в оболочке, но за счет еще одной утечки и, возможно, другого процесса.)

Общие конструкции оболочки

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

  • Глобирование или расширение подстановочного знака можно заменить на glob.glob() или очень часто на простые сравнения строк Python, такие как for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash имеет различные другие возможности расширения, такие как .{png,jpg} расширение скобок и {1..100}, а также расширение тильды (~ расширяется до вашего домашнего каталога и, в более общем случае, ~account - до домашнего каталога другого пользователя)
  • Переменные оболочки, такие как $SHELL или $my_exported_var, иногда можно просто заменить переменными Python. Экспортируемые переменные оболочки доступны, например, как os.environ['SHELL'] (значение export состоит в том, чтобы сделать переменную доступной для подпроцессов - переменная, которая не доступна для подпроцессов, очевидно, не будет доступна для Python, выполняющегося как подпроцесс оболочки, или наоборот. env= аргумент ключевого слова для методов subprocess позволяет вам определить среду подпроцесса в виде словаря, чтобы один из способов сделать переменную Python видимой для подпроцесса). С shell=False вам нужно понять, как удалять любые кавычки; например, cd "$HOME" эквивалентен os.chdir(os.environ['HOME']) без кавычек вокруг имени каталога. (Очень часто cd в любом случае бесполезен или не нужен, и многие начинающие опускают двойные кавычки вокруг переменной и обходятся ею до одного дня...)
  • Перенаправление позволяет вам читать из файла в качестве стандартного ввода и записывать стандартный вывод в файл. grep 'foo' <inputfile >outputfile открывает outputfile для записи и inputfile для чтения и передает его содержимое в качестве стандартного ввода в grep, чей стандартный вывод затем попадает в outputfile. Обычно это не сложно заменить на собственный код Python.
  • Трубопроводы - это форма перенаправления. echo foo | nl запускает два подпроцесса, где стандартный вывод echo является стандартным вводом nl (на уровне ОС, в Unix-подобных системах это дескриптор одного файла). Если вы не можете заменить один или оба конца конвейера собственным кодом Python, возможно, в конце концов подумайте об использовании оболочки, особенно если конвейер имеет более двух или трех процессов (хотя посмотрите на модуль pipes в Стандартная библиотека Python или ряд более современных и универсальных сторонних конкурентов).
  • Управление заданиями позволяет прерывать задания, запускать их в фоновом режиме, возвращать их на передний план и т.д. Основные сигналы Unix для остановки и продолжения процесса, конечно же, доступны и в Python. Но задания - это высокоуровневая абстракция в оболочке, которая включает группы процессов и т.д., Которые вы должны понимать, если хотите сделать что-то подобное из Python.

Понять различия между sh и Bash

subprocess запускает ваши команды оболочки с /bin/sh, если вы специально не запрашиваете иное (кроме, конечно, в Windows, где он использует значение переменной COMSPEC). Это означает, что различные функции только для Bash, такие как массивы, [[ и т.д., недоступны.

Если вам нужно использовать синтаксис Bash-only, вы можете передайте путь к оболочке как executable='/bin/bash' (где, конечно, если ваш Bash установлен где-то еще, вам нужно изменить путь).

subprocess.run('''
    # This for loop syntax is Bash only
    for((i=1;i<=$#;i++)); do
        # Arrays are Bash-only
        array[i]+=123
    done''',
    shell=True, check=True,
    executable='/bin/bash')

A subprocess отделен от своего родителя и не может его изменить

Несколько распространенная ошибка - делать что-то вроде

subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True)  # Doesn't work

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

Дочерний процесс запускается совершенно отдельно от Python, и когда он завершается, Python не имеет представления о том, что он сделал (кроме неопределенных индикаторов, которые он может вывести из состояния выхода и вывода из дочернего процесса). Ребенок обычно не может изменить родительскую среду; он не может установить переменную, изменить рабочий каталог или, во многих словах, связаться со своим родителем без сотрудничества с родителем.

Непосредственное исправление в этом конкретном случае заключается в запуске обеих команд в одном подпроцессе;

subprocess.run('foo=bar; echo "$foo"', shell=True)

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

os.environ['foo'] = 'bar'

или передайте настройку среды дочернему процессу с помощью

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(не говоря уже об очевидном рефакторинге subprocess.run(['echo', 'bar']); но echo, конечно, плохой пример того, что нужно запускать в подпроцессе).

Не запускайте Python из Python

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

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

Если вам нужен параллелизм, вы можете запускать функции Python в подпроцессах с помощью модуля multiprocessing. Существует также threading, который запускает несколько задач в одном процессе (более легкий). и дает вам больше контроля, но также более ограничен в том, что потоки внутри процесса тесно связаны и связаны с одним GIL.)

Ответ 3

Вызвать его с помощью подпроцесса

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

Ошибка, которую вы получаете, по-видимому, связана с тем, что на сервере нет модуля свопинга, вы должны установить swap на сервере, а затем запустить script снова

Ответ 4

Возможно, вы используете программу bash, с параметром -c для выполнения команд:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])

Ответ 5

Вы можете использовать "подпроцесс", но я всегда чувствовал, что это не "питонический" способ сделать это. Поэтому я создал Султан (бесстыдный плагин), который позволяет легко запускать функции командной строки.

https://github.com/aeroxis/sultan

Ответ 6

В соответствии с ошибкой вам не хватает пакета с именем swap на сервере. Для этого требуется /usr/bin/cwm. Если вы работаете в Ubuntu/Debian, установите python-swap с помощью aptitude.

Ответ 7

Также вы можете использовать "os.popen". Пример:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

Выход:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py

None

Ответ 8

Чтобы запустить команду без оболочки, передайте команду в виде списка и осуществите перенаправление в Python, используя [subprocess]:

#!/usr/bin/env python
import subprocess

with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)

Примечание: нет > test.nt в конце. stdout=file реализует перенаправление.


Чтобы запустить команду с использованием оболочки в Python, передайте команду в виде строки и включите shell=True:

#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

Здесь оболочка отвечает за перенаправление вывода (> test.nt в команде).


Чтобы запустить команду bash, которая использует bashisms, укажите явно исполняемый файл bash, например, для эмуляции подстановки процесса bash:

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')

Ответ 9

Питонический способ сделать это - использовать subprocess.Popen

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

В качестве примера:

import subprocess

args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running 'echo Hello!' on cmd line

args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"' on cmd line