Python, subprocess, call(), check_call и returncode, чтобы найти, существует ли команда
Я выяснил, как использовать call(), чтобы заставить мой python script выполнить команду:
import subprocess
mycommandline = ['lumberjack', '-sleep all night', '-work all day']
subprocess.call(mycommandline)
Это работает, но есть проблема, что, если у пользователей нет дровосека в их командном пути? Он работал бы, если бы дровосек был помещен в тот же каталог, что и python script, но как знает script, он должен искать дровосека? Я подумал, была ли ошибка с ошибкой команды, а lumberjack не будет в командной строке, script может попытаться выяснить, что это за папка, и искать там лесоруб и, наконец, предупредить пользователя о том, чтобы скопировать дровосека в одно из этих двух мест, если оно не было найдено ни в одном из них. Как узнать, что такое сообщение об ошибке? Я прочитал, что check_call() может вернуть сообщение об ошибке и что-то о атрибуте returncode. Я не мог найти примеры того, как использовать check_call() и returncode, что такое сообщение или как я мог бы сказать, не было ли сообщение неподтвержденным командами.
Я даже об этом думаю правильно?
Ответы
Ответ 1
Ничего себе, это было быстро! Я комбинировал простой пример Theodros Zelleke и использование функций steveha с комментарием abarnert о комментариях OSError и Lattyware о перемещении файлов:
import os, sys, subprocess
def nameandpath():
try:
subprocess.call([os.getcwd() + '/lumberjack'])
# change the word lumberjack on the line above to get an error
except OSError:
print('\nCould not find lumberjack, please reinstall.\n')
# if you're using python 2.x, change the () to spaces on the line above
try:
subprocess.call(['lumberjack'])
# change the word lumberjack on the line above to get an error
except OSError:
nameandpath()
Я тестировал его на Mac OS-X (6.8/Snow Leopard), Debian (Squeeze) и Windows (7). Казалось, он работает так, как я хотел, чтобы он работал во всех трех операционных системах. Я попытался использовать check_call и CalledProcessError, но независимо от того, что я сделал, я, казалось, получал ошибку каждый раз, и я не мог заставить script обрабатывать ошибки. Чтобы проверить script, я сменил имя с 'lumberjack' на 'deadparrot', так как у меня был dumberjack в каталоге с моим script.
Вы видите какие-либо проблемы с этим script способом написания?
Ответ 2
Простой фрагмент:
try:
subprocess.check_call(['executable'])
except subprocess.CalledProcessError:
pass # handle errors in the called executable
except OSError:
pass # executable not found
Ответ 3
subprocess
вызовет исключение, OSError
, когда команда не будет найдена.
Когда команда найдена, и subprocess
запускает для вас команду, возвращается код результата из команды. Стандартом является то, что код 0 означает успех, и любой сбой - это некоторый ненулевой код ошибки (который меняется, проверьте документацию для конкретной команды, которую вы запускаете).
Итак, если вы поймаете OSError
, вы можете обработать несуществующую команду, и если вы проверите код результата, вы сможете узнать, была ли эта команда успешной или нет.
Самое замечательное в subprocess
заключается в том, что вы можете собрать весь текст из stdout
и stderr
, и затем вы можете отбросить его или вернуть его или зарегистрировать или отобразить по своему усмотрению. Я часто использую оболочку, которая отбрасывает все выходные данные из команды, если команда не завершилась, и в этом случае выводится текст из stderr
.
Я согласен с тем, что вы не должны просить пользователей копировать исполняемые файлы. Программы должны быть в каталоге, указанном в переменной PATH
; если программа отсутствует, она должна быть установлена или если она установлена в каталоге, а не на PATH
, пользователь должен обновить PATH
, чтобы включить этот каталог.
Обратите внимание, что у вас есть возможность несколько раз попробовать subprocess
с различными жестко закодированными путями:
import os
import subprocess as sp
def _run_cmd(s_cmd, tup_args):
lst_cmd = [s_cmd]
lst_cmd.extend(tup_args)
result = sp.call(lst_cmd)
return result
def run_lumberjack(*tup_args):
try:
# try to run from /usr/local/bin
return _run_cmd("/usr/local/bin/lumberjack", tup_args)
except OSError:
pass
try:
# try to run from /opt/forest/bin
return _run_cmd("/opt/forest/bin/lumberjack", tup_args)
except OSError:
pass
try:
# try to run from "bin" directory in user home directory
home = os.getenv("HOME", ".")
s_cmd = home + "/bin/lumberjack"
return _run_cmd(s_cmd, tup_args)
except OSError:
pass
# Python 3.x syntax for raising an exception
# for Python 2.x, use: raise OSError, "could not find lumberjack in the standard places"
raise OSError("could not find lumberjack in the standard places")
run_lumberjack("-j")
EDIT: Немного подумав об этом, я решил полностью переписать выше. Это гораздо чище, чтобы просто передать список мест, и попробуйте использовать альтернативные места, пока вы не заработаете. Но я не хотел создавать строку для домашней директории пользователя, если она не нужна, поэтому я просто сделал законным положить вызываемый в список альтернатив. Если у вас есть какие-либо вопросы по этому поводу, просто спросите.
import os
import subprocess as sp
def try_alternatives(cmd, locations, args):
"""
Try to run a command that might be in any one of multiple locations.
Takes a single string argument for the command to run, a sequence
of locations, and a sequence of arguments to the command. Tries
to run the command in each location, in order, until the command
is found (does not raise OSError on the attempt).
"""
# build a list to pass to subprocess
lst_cmd = [None] # dummy arg to reserve position 0 in the list
lst_cmd.extend(args) # arguments come after position 0
for path in locations:
# It legal to put a callable in the list of locations.
# When this happens, we should call it and use its return
# value for the path. It should always return a string.
if callable(path):
path = path()
# put full pathname of cmd into position 0 of list
lst_cmd[0] = os.path.join(path, cmd)
try:
return sp.call(lst_cmd)
except OSError:
pass
raise OSError('command "{}" not found in locations list'.format(cmd))
def _home_bin():
home = os.getenv("HOME", ".")
return os.path.join(home, "bin")
def run_lumberjack(*args):
locations = [
"/usr/local/bin",
"/opt/forest/bin",
_home_bin, # specify callable that returns user home directory
]
return try_alternatives("lumberjack", locations, args)
run_lumberjack("-j")