1

I wrote a C++ watchdog that runs a set of scripts to determine whether there is a problem on that system.

The code is a bit hairy so I won't show it here, but it is equivalent to a system call as follow:

int const r(system("/bin/sh /path/to/script/test-health"));

Only, r is 0 when the script fails because a command is missing in an if statement. There is the offensive bit of the script:

set -e
[...]
if unknown_command arg1 arg2
then
[...]

The unknown_command obviously fails since... it is unknown. At that point the script ends because I have the set -e at the start.

The exit code, though, is going to be 0 in that situation.

Would there be a way for me to get an exit code of 1 in such a situation?

i.e. the question is detecting the error without having to add a test to know whether unknown_command exists. I know how to do that:

if ! test -x unknown_command
then
    exit 1
fi

My point is that when I write that script, I expect unknown_command to exist as I install it myself, but if something goes wrong or someone copies the script on another system without installing everything, I'd like to know that I got an error executing the script.

Alexis Wilke
  • 2,857

4 Answers4

5

From the POSIX standard, regarding set -e:

The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.

This means that executing an unknown command in an if statement will not cause the script to terminate when running under set -e. Or rather, set -e will not cause it to terminate.


User command -v to test whether a utility exists in the current PATH, unless you use full paths to the utilities that you invoke in which case a -x test would be sufficient, as in your question.

See also: Why not use "which"? What to use then?

Kusalananda
  • 333,661
3

At that point the script ends because I have the set -e at the start.

This is a falsehood.

Common script:

$ cat ./weeble
set -e
if wobble ; then echo wobbled. ; else echo did not wobble. ; fi
echo did not fall down.
type wobble || exec false
exec true
$ 

You say that you know how to test for an unknown command.

Almquist shell as /bin/sh:

$ /bin/sh ./weeble ; echo $?
./weeble: wobble: not found
did not wobble.
did not fall down.
wobble: not found
1
$ /bin/sh -e ./weeble ; echo $?
./weeble: wobble: not found
did not wobble.
did not fall down.
wobble: not found
1
$ 

Korn shell as /bin/sh:

$ /bin/sh ./weeble ; echo $?
./weeble[2]: wobble: not found
did not wobble.
did not fall down.
wobble not found
1
$ /bin/sh -e ./weeble ; echo $? 
./weeble[2]: wobble: not found
did not wobble.
did not fall down.
wobble not found
1
$ 

Bourne Again shell as /bin/sh:

$ /bin/exec -a /bin/sh /usr/local/bin/bash ./weeble ; echo $?
./weeble: line 2: wobble: command not found
did not wobble.
did not fall down.
./weeble: line 4: type: wobble: not found
1
$ /bin/exec -a /bin/sh /usr/local/bin/bash -e ./weeble ; echo $?
./weeble: line 2: wobble: command not found
did not wobble.
did not fall down.
./weeble: line 4: type: wobble: not found
1
$ 

Z shell as /bin/sh:

$ /bin/exec -a /bin/sh /usr/local/bin/zsh ./weeble ; echo $?
./weeble:2: command not found: wobble
did not wobble.
did not fall down.
wobble not found
1
$ /bin/exec -a /bin/sh /usr/local/bin/zsh -e ./weeble ; echo $?
./weeble:2: command not found: wobble
did not wobble.
did not fall down.
wobble not found
1
$ 

Since your question asks how to avert something that does not happen in the first place, and takes a falsehood as both its premise and its title, it is unanswerable.

JdeBP
  • 68,745
1

The if does not exit because the set -e has been set up:

#!/bin/sh

set -e

if unknown_command; then
    echo "On true, exit code $?"
else
    echo "on false, exit code $?"
fi

echo "after if, exit code $?"

Prints (in all shells tested, with variations of the error message):

sh            : … : unknown_command: not found                                    
on false, exit code 127                                                                                                          
after if, exit code 0 

Both with set -e set or unset.


If you want to detect missing commands, capture an exit code of 1271:

#!/bin/sh

if unknown_command; then
    echo "On true, exit code $?"
else
    ec=$?
    if [ "$ec" = '127' ]; then
        echo "the command failed because it does not exist"
        exit 127
    fi
    echo "on false, exit code $ec"
fi

echo "after if, exit code $?"

Which will execute as this:

$ ./script
./script: 5: ./script: unknown_command: not found
the command failed because it does not exist

1
About the exit code of 127

0

You should do one of two things:

1:

unknown_command arg1 arg2
subsequent command(s)

Then the set -e will cause the script to terminate if unknown_command fails.  Why are you saying if?

2:

if unknown_command arg1 arg2
then
    subsequent command(s)
              ︙
else
    issue error message(s)
    do any desired cleanup
    exit 1
fi
  • The command is similar to test. Specifically, in my case I'm looking for a process using a tool I wrote so I can detect python, java, etc. processes with ease (compared to using ps or some other such tools). The command will therefore exit with 0 if the process exists and 1 otherwise. If unknown, it returns 127 as mentioned by isaac in his answer. So I could test that instead ($?). But that was not really the point. In my case it is expected to be there, but the programmer will get no report if he misspelled the name or missed installing a package... – Alexis Wilke Feb 11 '18 at 05:34
  • OK, I don’t understand what you’re saying. – G-Man Says 'Reinstate Monica' Feb 11 '18 at 05:40