I have a script that requires at least one argument. If the arguments are incorrect I will obviously return a non-zero exit code.
I have decided to print the usage instructions when called with no arguments, but I'm uncertain of the exit code. Should it be
0 (success)
one of the values [1-125] (error)
An argument could be made either way. Is some option recommended?
It depends on whether you want the callers of your script to know programatically why it failed.
If, for example, it will only ever be run by a human, you could just return 1 and rely on the error text to inform them.
You could even return zero regardless if you only ever want a human to easily know (via the output) if it failed but I would consider this very bad form - Microsoft have made this mistake in the past with some of their commands not setting %errorlevel%, making it very difficult for a script to figure out whether it's worked or not.
Even if you decide not to indicate why it failed, you should at least indicate that it failed.
If you want a program to easily figure out why it failed, return a different code for each discernible error type.
Any Unix command when it returns control to its parent process returns a code, number between 0 and 255.
Success is traditionally represented with exit 0; failure is normally indicated with a non-zero exit-code. This value can indicate different reasons for failure.
So what you have also is another case of unsuccessful completion of your program which should be treated as returning a proper error code and not 0.
Also note that traditional Unix system calls don't signal an error code for empty arguments, they have to be caught explicitly. There are ones for argument list too long (E2BIG) and invalid arguments (EINVAL).
This is what I decided on using, based mostly on Inians answer and the reserved exit codes presented here.
I decided on somewhat reusing the exit codes defined in /usr/include/asm-generic/errno-base.h1. There was nothing suitable for "too little arguments", so I picked the first one from the 64-113 range.
# Error codes
E2BIG=7
EINVAL=22
E2SMALL=64
NAME="my-script"
USAGE=$(cat << END
the help text
RETURN CODES
$NAME has the following return codes:
0 success
$E2BIG Argument list too long
$EINVAL Invalid argument
$E2SMALL Argument list too short
END
)
# This script requires exactly one argument. If less are provided it prints
# the USAGE instructions
if [ ${#} -lt 1 ] ; then
printf '%s\n' "${USAGE}"
exit $E2SMALL
fi
if [ "${1}" = "-h" ] || [ "${1}" = "--help" ] ; then
printf '%s\n' "${USAGE}"
exit 0
fi
if [ ${#} -gt 1 ] ; then
printf '%s\n' "${USAGE}"
exit $E2BIG
fi
It's a bit verbose, but at least there's a properly defined error code if the script is called improperly.
1 This may be debatable, but since the codes were already defined...
Related
(I'm not a linux guy) and I want to check the status of a service when its updating (takes about 10 minutes) to make sure it is successful. I use a function to run the status command and while loop as follow:
get_status() { echo ...my command runs here and return the statue; }
I simply can get the status like $(get_status). Now I want to see what is the status and take action:
while $(get_status) == "PENDING"; do echo retrying... && sleep 5; done
I've tried different ways like single/double brackets but cannot get the while comparison to work properly? Can anybody help please?
The while loop doesn't know anything about comparison tests. It only knows how to check the exit status of a command, and $(get_status) == "PENDING" is not a command. The brackets you want are for either a test command or a bash conditional expression command.
while test "$(get_status)" = "PENDING"; do
or
# [ is a synonym for test, with the added requirement that
# there be a final argument ] to complete the illusion of
# syntax.
while [ "$(get_status) = "PENDING" ]; do
or
while [[ $(get_status) == "PENDING" ]]; do
In the first two cases, = is preferred as the correct equality operator for test/[. In the last case, == may be used, and the quotes can be dropped around $(get_status) because no word-splitting or filename generation is performed on expansions in [[ ... ]]. (The quotes could be dropped around the literal word PENDING in all three cases, but could remain necessary for some right-hand arguments inside [[ ... ]] for reasons beyond the scope of this question.)
I found this code in an autoconf configure script. What is the following code trying to do?
if ${am_cv_autoconf_installed+:} false; then :
$as_echo_n "(cached) " >&6
else
Lots of stuff going on here. Let's break it down.
First of all, the syntax ${var+foo} is a common idiom for checking whether the variable var has been defined. If var is defined, then ${var+foo} will expand to the string foo. Otherwise, it will expand to an empty string.
Most commonly (in bash, anyway), this syntax is used as follows:
if [ -n "${var+foo}" ]; then
echo "var is defined"
else
echo "var is not defined"
fi
Note that foo is just any arbitrary text. You could just as well use x or abc or ilovetacos.
However, in your example, there are no brackets. So whatever ${am_cv_autoconf_installed+:} expands to (if anything) will be evaluated as a command. As it turns out, : is actually a shell command. Namely, it's the "null command". It has no effect, other than to set the command exit status to 0 (success). Likewise, false is a shell command that does nothing, but sets the exit status to 1 (failure).
So depending on whether the variable am_cv_autoconf_installed is defined, the script will then execute one of the following commands:
: false
-OR-
false
In the first case, it calls the null command with the string "false" as an argument, which is simply ignored, causing the if statement to evaluate to true. In the second case, it calls the false command, causing the if statement to evaluate to false.
So all this is really doing is checking whether am_cv_autoconf_installed is defined. If this were just an ordinary bash script and didn't require any particular level of portability, it would have been a lot simpler to just do:
if [ -n "${am_cv_autoconf_installed+x}" ]; then
However, since this is a configure script, it was no doubt written this way for maximum portability. Not all shells will have the -n test. Some may not even have the [ ] syntax.
The rest should be fairly self-explanatory. If the variable is defined, the if statement evaluates to true (or more accurately, it sets the exit status to 0), causing the $as_echo_n "(cached) " >&6 line to execute. Otherwise, it does whatever is in the else clause.
I'm guessing $as_echo_n is just the environment-specific version of echo -n, which means it will print "(cached) " with no trailing newline. The >&6 means the output will be redirected to file descriptor 6 which presumably is set up elsewhere in the script (probably a log file or some such).
I'm trying to find a way to emulate the behavior of set -e in a function, but only within the scope of that function.
Basically, I want a function where if any simple command would trigger set -e it returns 1 up one level. The goal is to isolate sets of risky jobs into functions so that I can gracefully handle them.
If you want any failing command to return 1, you can achieve that by following each command with || return 1.
For instance:
false || return 1 # This will always return 1
I am a big fan of never letting any command fail without explicit handling. For my scripts, I am using an exception handling technique where I return errors in a way that is not return codes, and trap all errors (with bash traps). Any command with a non-zero return code automatically means an improperly handled situation or bug, and I prefer my scripts to fail as soon as such a situation occurs.
Caution: I highly advise against using this technique. If you run the function in a subshell environment, you almost get the behavior you desire. Consider:
#!/bin/bash
foo() ( # Use parens to get a sub-shell
set -e # Does not impact the main script
echo This is executed
false
echo This should *not* be executed
)
foo # Function call fails, returns 1
echo return: $?
# BUT: this is a good reason to avoid this technique
if foo; then # Set -e is invalid in the function
echo Foo returned 0!!
else
echo fail
fi
false # Demonstrates that set -e is not set for the script
echo ok
Seems like you are looking for "nested exceptions" somewhat like what Java gives. For your requirement of scoping it, how about doing a set -e at the beginning of the function and making sure to run set +e before returning from it?
Another idea, which is not efficient or convenient, is to call your function in a subshell:
# some code
(set -e; my_function)
if [[ $? -ne 0 ]]; then
# the function didn't succeed...
fi
# more code
In any case, please be aware that set -e is not the greatest way to handle errors in a shell script. There are way too many issues making it quite unreliable. See these related posts:
What does set -e mean in a bash script?
Error handling in Bash
The approach I take for large scripts that need to exist for a long time in a production environment is:
create a library of functions to do all the standard stuff
the library will have a wrapper around each standard action (say, mv, cp, mkdir, ln, rm, etc.) that would validate the arguments carefully and also handle exceptions
upon exception, the wrapper exits with a clear error message
the exit itself could be a library function, somewhat like this:
--
# library of common functions
trap '_error_handler' ERR
trap '_exit_handler' EXIT
trap '_int_handler' SIGINT
_error_handler() {
# appropriate code
}
# other handlers go here...
#
exit_if_error() {
error_code=${1:-0}
error_message=${2:-"Uknown error"}
[[ $error_code == 0 ]] && return 0 # it is all good
# this can be enhanced to print out the "stack trace"
>&2 printf "%s\n" $error_message
# out of here
my_exit $error_code
}
my_exit() {
exit_code=${1:-0}
_global_graceful_exit=1 # this can be checked by the "EXIT" trap handler
exit $exit_code
}
# simple wrapper for cp
my_cp() {
# add code to check arguments more effectively
cp $1 $2
exit_if_error $? "cp of '$1' to '$2' failed"
}
# main code
source /path/to/library.sh
...
my_cp file1 file2
# clutter-free code
This, along with effective use of trap to take action on ERR and EXIT events, would be a good way to write reliable shell scripts.
Doing more research, I found a solution I rather like in Google's Shell Style Guide. There are some seriously interesting suggestions here, but I think I'm going to go with this for readability:
if ! mv "${file_list}" "${dest_dir}/" ; then
echo "Unable to move ${file_list} to ${dest_dir}" >&2
exit "${E_BAD_MOVE}"
fi
I want to write a robust bash script and I use set -e to make sure the script will stop running whenever something goes wrong. It works great but there is still one hole: conditions. If I write if f; then .... fi and function f fails on executing some command, the script will not be terminated. Therefore I need to check return code of every command in f, as well as in all the subroutines f invoke, recursively. This is annoying.
Is there something, e.g. some flag or option in bash, that makes it fail even inside a condition. The only exception is return statement directly inside f. If f calls g and g returns 1, then it is still considered as error, with the exception that g is also called as condition, i.e. if g; then ... fi, then return statement inside g is allowed. So on so forth.
Succinctly, No.
If you want the shell to exit on failure, don't test f; simply run it. The code in the then clause should simply follow the invocation of f because you'll only ever get to execute it if f succeeded.
Old code:
set -e
if f; then echo "f succeeded"; fi
New code:
set -e
f
echo "f succeeded"
You'll only see "f succeeded" if it does succeed; if it fails, the set -e ensures the script exits.
What you're asking to do is slightly odd. I can imagine two possibilities for why you're asking:
You want the script to exit if anything goes wrong inside the body of the function f, even if the function itself still returns success.
You want to distinguish between the function successfully computing what turns out to be a legitimate false value, and failing.
For case 1 - don't do that. If anything goes wrong inside f, the function should absolutely return false (if not exit the whole script itself). And if it returns false, you're good - see #JonathanLeffler's answer.
For case 2, you're effectively trying to make $? serve two duties. It can be either an error indicator or a Boolean result; it can't be both.
If you want your script to exit if something goes wrong, you could just have the function itself call exit when that happens, so the caller doesn't have to worry about that case.
Alternatively, you could have the function echo its computed Boolean value instead of returning it, and reserve $? for success/failure. Something like this, maybe:
f() {
if that thing is true; then
echo 1
else
echo 0
fi
if something went wrong; then
return 1
else
return 0
fi
}
result=$(f) # we'll blow up and exit if f returns nonzero in `$?`
if (( result )); then
...
fi
You would have to distinguish between f being false and f encountering an error.
Traditionally both falsehood and error conditions are signaled in the same way: by setting $? to a non-zero value.
Some commands do make such a distinction. For example, the grep command sets $? to 0 if the pattern was found, 1 if it wasn't, and 2 if there was an error (such as a missing file). So for the grep command, you could do something like:
grep ...
case $? in
0) # ok
;;
1) # pattern not found
;;
*) # error
;;
esac
But that's specific to grep. There is no universal convention for distinguishing between a command yielding a "false" result and failing, and in many cases there is no such distinction to make.
To do what you want, you'll have to define just what constitutes an error, and then determine for each command you might execute how to detect an error condition.
Can anyone tells me what does this script means found in a .sh file:
[ ! -n "$T_R" ] && echo "Message Appear" && exit 1;
Edit: Correcting for misinformation pointed out by tripleee
The brackets [ ]
are an alias for 'test', which tests whether a condition is met. Not to complicate matters, but do note that this is discrete from the the bash shell keyword [[ ]] (Thanks, tripleee for clearing that up!). See This post for further details. These days, most people seem to use the latter due to its more robust feature set.
Between the brackets, the script is testing to determine whether the variable "$T_R" is an empty string.
The -n operator returns true if the length of the string passed to it as an argument is non-empty.
The ! inverts the case (the test succeeds if the result is not
true). So in this case, test suceeds (returns 0) if the length of
the string variable "$T_R" is **not non-zero ** (i.e. if the
variable is an empty-string, or is non-existant).
The double-ampersand, && operator means only execute the subsequent code in the event of success, so the message "Message Appear" will only be echoed in the event the test succeeds (again, if "$T_R" is empty or unset).
Finally, the && exit 1 says to exit returning status 1 after successfully echoing the Message Appear message.
The bash and test man pages are extremely helpful on all of these topics and should be consulted for further details.
The chained && is a common short-circuit idiom.
Instead of writing
if true; then
if true; then
echo moo
fi
fi
you can abbreviate to just true && true && echo moo.
echo will usually succeed so true && echo moo && exit 1 will execute both the echo and the exit if true succeeds (which obviously it always will).
(There are probably extreme corner cases where echo could fail, but if that happens, you are toast anyways so I don't think it makes sense to try to guard against those.)
The [ is an alias for test which is a general comparison helper for shell scripts (in Bash, it's even a built-in). test -n checks whether a string is non-empty.
! is the general negation operator, so it inverts the test to checking for an empty string.
(This is slightly unidiomatic, because there is a separate test -z "$T_R" which checks specifically for the string being empty.)