Python、子进程、call()、check_call 和 returncode 以查找命令是否存在

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14106720/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 10:28:10  来源:igfitidea点击:

Python, subprocess, call(), check_call and returncode to find if a command exists

pythoncommand-linecallsubprocessfile-exists

提问by Dave Brunker

I've figured out how to use call() to get my python script to run a command:

我已经想出了如何使用 call() 来让我的 python 脚本运行一个命令:

import subprocess

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

This works but there's a problem, what if users don't have lumberHyman in their command path? It would work if lumberHyman was put in the same directory as the python script, but how does the script know it should look for lumberHyman? I figured if there was a command-not-found error then lumberHyman wouldn't be in the command path, the script could try to figure out what its directory is and look for lumberHyman there and finally warn the user to copy lumberHyman into one of those two places if it wasn't found in either one. How do I find out what the error message is? I read that check_call() can return an error message and something about a returncode attribute. I couldn't find examples on how to use check_call() and returncode, what the message would be or how I could tell if the message is command-not-found.

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

Am I even going about this the right way?

我什至以正确的方式解决这个问题吗?

采纳答案by Dave Brunker

Wow, that was fast! I combined Theodros Zelleke's simple example and steveha's use of functions with abarnert comment about OSError and Lattyware's comment about moving files:

哇,这真快!我将 Theodros Zelleke 的简单示例和 steveha 对函数的使用与关于 OSError 的 abarnert 评论和 Lattyware 关于移动文件的评论结合起来:

import os, sys, subprocess

def nameandpath():
    try:
        subprocess.call([os.getcwd() + '/lumberHyman']) 
        # change the word lumberHyman on the line above to get an error
    except OSError:
        print('\nCould not find lumberHyman, please reinstall.\n')
        # if you're using python 2.x, change the () to spaces on the line above

try:
    subprocess.call(['lumberHyman'])
    # change the word lumberHyman on the line above to get an error
except OSError:
    nameandpath()

I tested it on Mac OS-X (6.8/Snow Leopard), Debian (Squeeze) and Windows (7). It seemed to work the way I wanted it to on all three operating systems. I tried using check_call and CalledProcessError but no matter what I did, I seemed to get an error every time and I couldn't get the script to handle the errors. To test the script I changed the name from 'lumberHyman' to 'deadparrot', since I had lumberHyman in the directory with my script.

我在 Mac OS-X (6.8/Snow Leopard)、Debian (Squeeze) 和 Windows (7) 上测试了它。它似乎在所有三个操作系统上都按我希望的方式工作。我尝试使用 check_call 和 CalledProcessError 但无论我做什么,我似乎每次都遇到错误,而且我无法让脚本来处理这些错误。为了测试脚本,我将名称从“lumberHyman”更改为“deadparrot”,因为我的脚本目录中有 lumberHyman。

Do you see any problems with this script the way it's written?

您是否发现此脚本的编写方式有任何问题?

回答by steveha

subprocesswill raise an exception, OSError, when a command is not found.

subprocessOSError在找不到命令时引发异常。

When the command is found, and subprocessruns the command for you, the result code is returned from the command. The standard is that code 0 means success, and any failure is some non-zero error code (which varies; check the documentation for the specific command you are running).

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

So, if you catch OSErroryou can handle the non-existent command, and if you check the result code you can find out whether the command succeeded or not.

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

The great thing about subprocessis that you can make it collect all the text from stdoutand stderr, and you can then discard it or return it or log it or display it as you like. I often use a wrapper that discards all output from a command, unless the command fails in which case the text from stderris output.

很棒的一点subprocess是,您可以让它收集来自stdout和 的所有文本stderr,然后您可以将其丢弃或返回或记录或显示为您喜欢的内容。我经常使用一个丢弃命令的所有输出的包装器,除非命令失败,在这种情况下stderr输出文本。

I agree that you shouldn't be asking users to copy executables around. Programs should be in a directory listed in the PATHvariable; if a program is missing it should be installed, or if it is installed in a directory not on the PATHthe user should update the PATHto include that directory.

我同意您不应该要求用户复制可执行文件。程序应该在PATH变量列出的目录中;如果缺少程序,则应安装该程序,或者如果将其安装在不在该目录中的目录中,PATH则用户应更新该PATH目录以包含该目录。

Note that you do have the option of trying subprocessmultiple times with various hard-coded paths to executables:

请注意,您可以选择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_lumberHyman(*tup_args):
    try:
        # try to run from /usr/local/bin
        return _run_cmd("/usr/local/bin/lumberHyman", tup_args)
    except OSError:
        pass

    try:
        # try to run from /opt/forest/bin
        return _run_cmd("/opt/forest/bin/lumberHyman", 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/lumberHyman"
        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 lumberHyman in the standard places"
    raise OSError("could not find lumberHyman in the standard places")

run_lumberHyman("-j")

EDIT: After thinking about it a little bit, I decided to completely rewrite the above. It's much cleaner to just pass a list of locations, and have a loop try the alternative locations until one works. But I didn't want to build the string for the user's home directory if it wasn't needed, so I just made it legal to put a callable into the list of alternatives. If you have any questions about this, just ask.

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

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_lumberHyman(*args):
    locations = [
        "/usr/local/bin",
        "/opt/forest/bin",
        _home_bin, # specify callable that returns user's home directory
    ]
    return try_alternatives("lumberHyman", locations, args)

run_lumberHyman("-j")

回答by Theodros Zelleke

A simple snippet:

一个简单的片段:

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