My bash knowledge is a bit rusty (and hasn't been very solid before as well), so I seem to be unable to find an answer to the following question:
As the heading says, I'd like to know how I can determine whether a non-zero exit code after command execution has been set by bash (meaning a real error) or by the command (possibly indicating an error, dependent on the command and my purpose).
For example, let's have a look at the following very simple script:
#!/bin/bash
string='abc'
grep 'd' <<< "$string"
echo $?
This outputs 1, which is expected after having read grep
's manual (excerpt, shortening mine):
EXIT STATUS
Normally the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred. [...]
After having read the respective section of bash
's manual, I am having a problem (excerpt, shortening and emphasis mine):
EXIT STATUS
[...]
If a command is not found, the child process created to execute it returns a status of 127. If a command is found but is not executable, the return status is 126.
If a command fails because of an error during expansion or redirection, the exit status is greater than zero.
Shell builtin commands return a status of 0 (true) if successful, and non-zero (false) if an error occurs while they execute. All builtins return an exit status of 2 to indicate incorrect usage, generally invalid options or missing arguments.
[...]
My problem is the emphasized statement.
My scripts generally need to treat real errors (like lack of permissions, needed programs not being available, resource exhaustion etc.) specially, but in my example above, it is not an error in the above-mentioned sense when grep
does not select a line; instead, it just means that its input did not contain a matching character sequence.
However, if I take the section from bash
's manual literally, it could be bash
itself which may have set the exit status of 1
. From that section, we know what happens if a command could not be found (exit status 127
) or is not executable (exit status 126
).
The next statement in that section, as I understand it, means that every other error can be mapped to any exit status in the inclusive range [1, 255]
by bash. Notably, it can be mapped to exit status 1
. I am considering that a major problem because I believe that there is a vast number of errors besides "command not found" or "command not executable". For example, command execution could be prevented by memory exhaustion, file handle exhaustion, timeouts due to disk read errors, and so on.
In contrast to the "grep could not find a matching line" error, these are real severe errors which mostly must cause an email being sent to the administrator for immediate action.
But now it seems that I can't differentiate between the two sorts of errors (non-zero exit status set by executed command vs. non-zero exit status set by bash after having tried to execute the command).
Could anybody point me to a reasonable solution?
Similar questions
During my research, I have come across a lot of similar questions. However, to my best understanding, nobody had the exact same problem.
Instead, most people just wanted to suppress a non-zero exit code returned by a command (applied to my example, they would have wanted to have exit status 0
instead of 1
when grep
did not select a line), and were given a solution similar to command || true
.
While this might be acceptable for them, it is not a solution to me, because it also would suppress the real errors mentioned above. For example, consider the following:
root@cerberus:~/scripts# { ThisProgramDoesNotExist 2>/dev/null || true; } && { echo "Gotcha!"; }
Gotcha!
root@cerberus:~/scripts#
This demonstrates how that solution suppresses not only non-zero exit statuses (or is it "stati"?) from an executed command, but also severe errors reported by bash when failing to start a command. This is a no-go in most of my scripts.
grep 'd' <<< "$string"
to fail at the bash level.cp f1 f2; grep Cheshire < f2
might be a better example, where a command failure in thecp
command can cause a bash failure that prevents thegrep
from running. And Gilles’s answer seems to be trying to give you ways to run thegrep
even if the redirection fails. I don’t think you want that, but I can’t tell from reading your question.$?
is calculated in commands that run multiple programs, likefoo $(bar)
andFOO=$(bar) baz
. It was a great exercise in collaboration — I got a couple of helpful edits and many intelligent, constructive comments.