Linux shell script: Run a program only if it exists, ignore it if it does not exist

10,926

Solution 1

My interpretation would use a wrapper function named the same as the tool; in that function, execute the real tool if it exists:

figlet() {
  command -v figlet >/dev/null && command figlet "$@"
}

Then you can have figlet arg1 arg2... unchanged in your script.

@Olorin came up with a simpler method: define a wrapper function only if we need to (if the tool doesn't exist):

if ! command -v figlet > /dev/null; then figlet() { :; }; fi

If you'd like the arguments to figlet to be printed even if figlet isn't installed, adjust Olorin's suggestion as follows:

if ! command -v figlet > /dev/null; then figlet() { printf '%s\n' "$*"; }; fi

Solution 2

You can test to see if figlet exists

if type figlet >/dev/null 2>&1
then
    echo Figlet is installed
fi

Solution 3

A common way to do this is with test -x aka [ -x. Here is an example taken from /etc/init.d/ntp on a Linux system:

if [ -x /usr/bin/lockfile-create ]; then
    lockfile-create $LOCKFILE
    lockfile-touch $LOCKFILE &
    LOCKTOUCHPID="$!"
fi

This variant relies on knowing the full path of the executable. In /bin/lesspipe I found an example which works around that by combining -x and the which command:

if [ -x "`which bunzip`" ]; then bunzip -c "$1"
else echo "No bunzip available"; fi ;;

That way this will work without knowing in advance where in the PATH the bunzip executable is.

Solution 4

At the start of your script, check if figlet exists, and if it does not, define a shell function that does nothing:

type figlet >/dev/null 2>&1 || figlet() { :; }

type checks if figlet exists as a shell built-in, function, alias, or keyword, >/dev/null 2>&1 discards stdin and stdout so you don't get any output, and if it does not exist, figlet() { :; } defines figlet as a function that does nothing.

This way you don't have to edit every line of your script that uses figlet, or check if it exists every time figlet is called.

You can add a diagnostic message, if you like:

type figlet >/dev/null 2>&1 || { echo 'figlet not installed.' ; figlet() { :; } ; }

As a bonus, since you didn't mention which shell you are using, I believe this is POSIX compliant, so it should work on most any shell.

Solution 5

Another alternative -- a pattern I've seen in project auto configure scripts:

if [ -x /usr/bin/figlet ]
then
    FIGLET=/usr/bin/figlet
else
    FIGLET=:
fi

$FIGLET "Hello, world!"

In your specific case you could even do,

if [ -x /usr/bin/figlet ]
then
   SAY=/usr/bin/figlet
elif [ -x /usr/local/bin/figlet ]
then
   SAY=/usr/local/bin/figlet
elif [ -x /usr/bin/banner ]
then
   SAY=/usr/bin/banner
else
   SAY=/usr/bin/echo
fi

$SAY "Hello, world!"

If you don't know the specific path, you can try multiple elif (see above) to try known locations, or just use the PATH to always resolve the command:

if command -v figlet >/dev/null
then
    SAY=figlet
elif command -v banner >/dev/null
then
    SAY=banner
else
    SAY=echo
fi

In general, when writing scripts, I prefer to only call commands in specific locations specified by me. I don't like the uncertainty/risk of what the end user might have put into their PATH, perhaps in their own ~/bin.

If, for example, I was writing a complicated script for others that might remove files based on the output of a particular command I'm calling, I wouldn't want to accidentally pick up something in their ~/bin that might or might not be the command I expected.

Share:
10,926

Related videos on Youtube

Arlene Mariano
Author by

Arlene Mariano

Updated on September 18, 2022

Comments

  • Arlene Mariano
    Arlene Mariano over 1 year

    I am programming a Linux shell script that will print status banners during its execution only if the proper tool, say figlet, is installed (this is: reachable on system path).

    Example:

    #!/usr/bin/env bash
    echo "foo"
    figlet "Starting"
    echo "moo"
    figlet "Working"
    echo "foo moo"
    figlet "Finished"
    

    I would like for my script to work without errors even when figlet is not installed.

    What could be a practical method?

    • gip
      gip about 5 years
    • Arlene Mariano
      Arlene Mariano about 5 years
      @sudodus : just ignoring the 'figlet' (and its parameters) command would be OK. Continuing execution, of course.
    • g_uint
      g_uint about 5 years
      The title to this question got me in all sorts of metaphysical problems
    • Giacomo Alzetta
      Giacomo Alzetta about 5 years
      Do you want to ignore all errors? Just use figlet ... || true.
    • eckes
      eckes about 5 years
      If you don’t care about exit codes a shortcut is to use figlet || true, but in your case probably a shell function which Echos plaintext If no Banner can be printed is more likely what you want.
  • Vercingatorix
    Vercingatorix about 5 years
    Where does command come from? I don't have it in my installations (Red Hat 6.8 Enterprise and Cygwin64).
  • wyrm
    wyrm about 5 years
    command is a POSIX Bourne shell builtin. It normally executes the command provided, but the -v flag makes it behave more like type, another shell builtin.
  • l0b0
    l0b0 about 5 years
    @eewanco type -a command will show you. which will only show executables on your $PATH, not built-ins, keywords, functions or aliases.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 5 years
    Don't use which. And even if which worked, using test -x on its output is silly: if you get a path from which, it exists.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 5 years
    What if figlet was in /usr/local/bin or /home/bob/stuff/programs/executable/figlet?
  • kgendron
    kgendron about 5 years
    The problem here is that figlet can fail for reasons besides not being installed, so just testing the exit code doesn't suffice.
  • nigel222
    nigel222 about 5 years
    If you really want to test only for its installation, then you want to test if $? equals 127 (on my linux system). 127 is "command not found". But my thinking is that for rational commands, if command --help fails, then the installation is borked sufficiently that it might as well not be there!.
  • kgendron
    kgendron about 5 years
    126 is "command found but not executable," so you would want to test against that as well. Unfortunately, you can't depend on --help being available. Posix utilities don't have it, for one, and posix guidelines actually recommend against it. at --help fails with at: invalid option -- '-', for instance, and an exit status of 130 on my system.
  • dave_thompson_085
    dave_thompson_085 about 5 years
    @l0b0: on RedHat-family with bash and the default profile(s) which does find an applicable alias, because it aliases which itself to run /usr/bin/which and pipe it a list of the shell's aliases to look in (!) (Of course \which suppresses the alias and uses only the program, which doesn't show aliases.)
  • kgendron
    kgendron about 5 years
    Also, of course, if figlet can exit with status 127 that could be a problem too.
  • comfreak
    comfreak about 5 years
    @Gilles: Technically speaking, test -x does more than just check if a file exists (that's what test -e is for). It also checks if the file has the execute permissions set.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 5 years
    @comfreak So does which.
  • gokhan acar
    gokhan acar about 5 years
    I like the simplicity of this answer. You could also replace type figlet >/dev/null 2>&1 with hash figlet 2>/dev/null if you're using bash. (The OP said "if it's in my PATH".)