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

后端 未结 3 2097
执笔经年
执笔经年 2021-02-07 04:35

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

import subprocess

mycommandline = [\'lumberjack\', \'-sleep all night\', \'-work a         


        
3条回答
  •  清歌不尽
    2021-02-07 05:10

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

    When the command is found, and subprocess runs 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).

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

    The great thing about subprocess is that you can make it collect all the text from stdout and 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 stderr is output.

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

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

    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")
    

    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_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")
    

提交回复
热议问题