How can I check if a program exists from a Bash script?

后端 未结 30 1956
有刺的猬
有刺的猬 2020-11-21 07:21

How would I validate that a program exists, in a way that will either return an error and exit, or continue with the script?

It seems like it should be easy, but it\

相关标签:
30条回答
  • 2020-11-21 07:40

    I second the use of "command -v". E.g. like this:

    md=$(command -v mkdirhier) ; alias md=${md:=mkdir}  # bash
    
    emacs="$(command -v emacs) -nw" || emacs=nano
    alias e=$emacs
    [[ -z $(command -v jed) ]] && alias jed=$emacs
    
    0 讨论(0)
  • 2020-11-21 07:43

    Command -v works fine if the POSIX_BUILTINS option is set for the <command> to test for, but it can fail if not. (It has worked for me for years, but I recently ran into one where it didn't work.)

    I find the following to be more failproof:

    test -x $(which <command>)
    

    Since it tests for three things: path, existence and execution permission.

    0 讨论(0)
  • 2020-11-21 07:43

    I wanted the same question answered but to run within a Makefile.

    install:
        @if [[ ! -x "$(shell command -v ghead)" ]]; then \
            echo 'ghead does not exist. Please install it.'; \
            exit -1; \
        fi
    
    0 讨论(0)
  • 2020-11-21 07:48

    Answer

    POSIX compatible:

    command -v <the_command>
    

    Example use:

    if ! command -v COMMAND &> /dev/null
    then
        echo "COMMAND could not be found"
        exit
    fi
    

    For Bash specific environments:

    hash <the_command> # For regular commands. Or...
    type <the_command> # To check built-ins and keywords
    

    Explanation

    Avoid which. Not only is it an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.

    Why care?

    • Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
    • Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.

    So, don't use which. Instead use one of these:

    $ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
    $ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
    $ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
    

    (Minor side-note: some will suggest 2>&- is the same 2>/dev/null but shorter – this is untrue. 2>&- closes FD 2 which causes an error in the program when it tries to write to stderr, which is very different from successfully writing to it and discarding the output (and dangerous!))

    If your hash bang is /bin/sh then you should care about what POSIX says. type and hash's exit codes aren't terribly well defined by POSIX, and hash is seen to exit successfully when the command doesn't exist (haven't seen this with type yet). command's exit status is well defined by POSIX, so that one is probably the safest to use.

    If your script uses bash though, POSIX rules don't really matter anymore and both type and hash become perfectly safe to use. type now has a -P to search just the PATH and hash has the side-effect that the command's location will be hashed (for faster lookup next time you use it), which is usually a good thing since you probably check for its existence in order to actually use it.

    As a simple example, here's a function that runs gdate if it exists, otherwise date:

    gnudate() {
        if hash gdate 2>/dev/null; then
            gdate "$@"
        else
            date "$@"
        fi
    }
    

    Alternative with a complete feature set

    You can use scripts-common to reach your need.

    To check if something is installed, you can do:

    checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."
    
    0 讨论(0)
  • 2020-11-21 07:49

    I'd say there isn't any portable and 100% reliable way due to dangling aliases. For example:

    alias john='ls --color'
    alias paul='george -F'
    alias george='ls -h'
    alias ringo=/
    

    Of course, only the last one is problematic (no offence to Ringo!). But all of them are valid aliases from the point of view of command -v.

    In order to reject dangling ones like ringo, we have to parse the output of the shell built-in alias command and recurse into them (command -v isn't a superior to alias here.) There isn't any portable solution for it, and even a Bash-specific solution is rather tedious.

    Note that a solution like this will unconditionally reject alias ls='ls -F':

    test() { command -v $1 | grep -qv alias }
    
    0 讨论(0)
  • 2020-11-21 07:50

    It depends on whether you want to know whether it exists in one of the directories in the $PATH variable or whether you know the absolute location of it. If you want to know if it is in the $PATH variable, use

    if which programname >/dev/null; then
        echo exists
    else
        echo does not exist
    fi
    

    otherwise use

    if [ -x /path/to/programname ]; then
        echo exists
    else
        echo does not exist
    fi
    

    The redirection to /dev/null/ in the first example suppresses the output of the which program.

    0 讨论(0)
提交回复
热议问题