Python, subprocess, call(), check_call и returncode, чтобы найти, существует ли команда - программирование

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, что такое сообщение или как я мог бы сказать, не было ли сообщение неподтвержденным командами.

Я даже об этом думаю правильно?

4b9b3361

Ответ 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")