Python, subprocess, call(), check_call 和 returncode 来查找命令是不是存在

Posted

技术标签:

【中文标题】Python, subprocess, call(), check_call 和 returncode 来查找命令是不是存在【英文标题】:Python, subprocess, call(), check_call and returncode to find if a command existsPython, subprocess, call(), check_call 和 returncode 来查找命令是否存在 【发布时间】:2012-12-15 21:58:00 【问题描述】:

我已经弄清楚如何使用 call() 让我的 python 脚本运行命令:

import subprocess

mycommandline = ['lumberjack', '-sleep all night', '-work all day']
subprocess.call(mycommandline)

这可行,但有一个问题,如果用户的命令路径中没有伐木工人怎么办?如果将伐木工人放在与 python 脚本相同的目录中,它会起作用,但是脚本如何知道它应该寻找伐木工人?我想如果有一个未找到命令的错误,那么伐木工人就不会在命令路径中,脚本可以尝试找出它的目录是什么并在那里寻找伐木工人,最后警告用户将伐木工人复制到其中之一如果在任何一个地方都没有找到那两个地方。如何找出错误消息是什么?我读到 check_call() 可以返回错误消息和有关 returncode 属性的内容。我找不到有关如何使用 check_call() 和 returncode、消息是什么或如何判断消息是否为 command-not-found 的示例。

我的做法是否正确?

【问题讨论】:

check_call() 如果命令没有完全退出(这是不存在的命令所期望的),则应该引发错误 另外,我建议不要告诉用户复制可执行文件,除非作为最后的手段。包管理器几乎总是安装东西的最佳选择。 @DaveBrunker:你似乎对callcheck_call 有点困惑。 call 已经为您提供了返回码——不是作为属性,而是作为函数的返回值。使用check_call 的原因是它会为您检查返回码,从而引发CalledProcessError。对于找不到的程序,任何一个都会向您提出OSError(或类似的,取决于您的 Python 版本)。 @inspectorG4dget:实际上,不存在的命令不应该以非干净方式退出,因为它们甚至无法启动。这就是为什么他们提出OSError,而不是CalledProcessError,甚至使用call,而不仅仅是check_call。请参阅 Theodros Zelleke 的回答。 【参考方案1】:

subprocess 将在找不到命令时引发异常 OSError

找到命令后,subprocess 为您运行命令,从命令返回结果代码。标准是代码 0 表示成功,任何失败都是一些非零错误代码(会有所不同;请查看您正在运行的特定命令的文档)。

所以,如果你捕捉到OSError,你就可以处理不存在的命令,如果你检查结果代码,你就可以知道命令是否成功。

subprocess 的伟大之处在于您可以让它收集来自stdoutstderr 的所有文本,然后您可以随意丢弃或退回或记录或显示它。我经常使用包装器来丢弃命令的所有输出,除非命令失败,在这种情况下输出来自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's 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")

编辑:经过一番思考,我决定完全重写上面的内容。只传递一个位置列表,然后循环尝试替代位置,直到一个工作正常,这要干净得多。但是如果不需要的话,我不想为用户的主目录构建字符串,所以我只是将可调用对象放入替代列表中是合法的。如果您对此有任何疑问,请尽管提问。

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's 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's home directory
    ]
    return try_alternatives("lumberjack", locations, args)

run_lumberjack("-j")

【讨论】:

【参考方案2】:

一个简单的sn-p:

try:
    subprocess.check_call(['executable'])
except subprocess.CalledProcessError:
    pass # handle errors in the called executable
except OSError:
    pass # executable not found

【讨论】:

【参考方案3】:

哇,真快!我将 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 但无论我做了什么,我似乎每次都得到一个错误,我无法让脚本来处理这些错误。为了测试脚本,我将名称从“lumberjack”更改为“deadparrot”,因为我的脚本所在目录中有 lumberjack。

您认为这个脚本的编写方式有什么问题吗?

【讨论】:

以上是关于Python, subprocess, call(), check_call 和 returncode 来查找命令是不是存在的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 python 2.7.6 使 subprocess.call 超时?

在 Python 中使用 subprocess.call 播放音频

Python执行外部命令(subprocess,call,Popen)

Python下的subprocess.call()使用和注意事项

如何在 python 中杀死使用 subprocess.call 调用的子进程? [复制]

三十python 中subprocess介绍