I want to completely terminate/exit a bash shell script upon error, but using a function error that lets me display a debug output before termination. Now I have the problem that the exit 1 statement inside the error function will not terminate the shell script if the function's output is captured via backticks or $().
Here is my sample script:
#!/bin/bash
function error ()
{
echo "An error has occured: $1"
exit 1
}
function do_sth ()
{
if [ $1 -eq 0 ]; then
error "First param must be greater than 0!"
else
echo "OK!"
fi
}
RESULT=`do_sth 0`
echo "This line should never be printed"
How can I immediately terminate the script in the error() function?
The problem with command substitution is, that a subshell is started to execute do_sth. exit 1 then terminates this subshell and not the main bash.
You can work around this by appending || exit $?, which exits with the exit code from the command substitution
RESULT=`do_sth 0` || exit $?
If you want to show the error message, redirect it to stderr
echo "An error has occured: $1" >&2
RESULT=`do_sth 0` || exit $?
then
echo "An error has occured: $1" >&2
There's no way to avoid the fact that the subshell cannot make the parent terminate directly: the parent will have to evaluate the value returned from the subshell. A common technique is to trap exit and print an error message in the trap function. Since you are generating the error message in a subshell, you cannot simply assign the message to a variable as otherwise might be done, but you can use the filesystem. Please note that this idea is really silly, and it is much cleaner to simply write error messages to stderr. That's what it is for, and that's why it is inherited by children. Something like:
#!/bin/sh
trap final 0 2 15
# Create a temporary file to store error messages. This is a terrible
# idea: it would be much better to simply write error messages to stderr,
# but this code is attempting to demonstrate the technique of having the
# parent print the message. Perhaps it would do better to serve as an example
# of why reporting your children's mistakes is a bad idea. The children
# should be responsible for reporting their own errors. Doing so is easy,
# since they inherit file descriptor 2 from their parent.
errmsg=$( mktemp xxxxx )
final() {
test "$?" = 0 || cat $errmsg
rm -f $errmsg
} >&2
# Must emphasize one more time that this is a silly idea. The error
# function ought to be writing to stderr: eg echo "error: $*" >&2
error() { echo "error: $*" > $errmsg; exit 1; }
do_sth() {
if test "$1" -eq 0; then
error "First param must be greater than 0!"
else
echo "OK!"
fi
}
result=$( do_sth 0 ) || exit 1
echo not printed
It seems the (clever) answer is on another question's top answer by #FatalError, here on so
This should work...
#!/bin/bash
trap "exit 1" 50 #exit process after receiving signal 50.
function myerror ()
{
echo "An error has occured: $1" >&2
}
function do_sth ()
{
if [ $1 -eq 0 ]; then
myerror "First param must be greater than 0!"
kill -50 $(ps --pid $$ -opid=) #uncommon signal 50 is used.
else
echo "OK!"
fi
}
RESULT=`do_sth 1`
echo $RESULT
RESULT=`do_sth 0`
echo $RESULT
echo "This line should never be printed"
Related
I have a project to execute a script and need to format the output, including unexpected errors. I know that I can use trap to intercept errors, so I tried to use it to format the output, the example is as follows
#!/bin/bash
set -o errtrace
status=false
trap "print " ERR
print() {
echo "{\"status\":$status,\"result\":\"$1\"}"
exit 0
}
main(){
arg=$1
if [ "$arg" == "0" ];then
status=true
print "successfully finish"
else
cat /a/b/c/d >>/dev/null
echo "abnormal termination"
fi
}
main "$#"
The logic of success meets my needs as follows
# bash format-print.sh 0
{"status":true,"result":"successfully finish"}
But when an exception error is caught it doesn't meet my needs
# bash format-print.sh 1
cat: /a/b/c/d: No such file or directory
{"status":false,"result":""}
I would like to enter the following result
# bash format-print.sh 1
cat: /a/b/c/d: No such file or directory
{"status":false,"result":"cat: /a/b/c/d: No such file or directory"}
How can I modify the code to meet my needs, and if trap does not work or is not the standard way, can you please tell me how to implement it?
It sounds like what you need is to capture the error output from your command so that you can include it in your formatted output. You don't need a trap for this, although if you wanted to use a trap to invoke print you could do that (maybe with EXIT instead of ERR though).
With direct calls to the print function, this will do what you're looking for:
#!/bin/bash
set -o errtrace
status=false
#trap "print " ERR # removed trap for testing
print() {
echo "{\"status\":$status,\"result\":\"$1\"}"
exit 0
}
main(){
arg=$1
if [ "$arg" == "0" ];then
status=true
print "successfully finish"
else
# captures stderr into a variable while discarding stdout
result=$( cat /a/b/c/d 2>&1 >/dev/null )
print "$result"
fi
}
main "$#"
This will provide the output:
$ ./test.sh 1
{"status":false,"result":"cat: /a/b/c/d: No such file or directory"}
New in bash script, have to write a function to check if the first given parameter is empty. Output error message if it is empty, otherwise success.
Use return-statement and the variable $? Output should for example like this:
./test.sh -> Script failed; ./test.sh hallo -> Script OK.
Thank you!
This is what i have now:
check_parameter() {
var=$1
if [ -z ${var} ]; then
return 1
else
return 0
fi
if [ $?== 0 ]; then
echo " OK. "
else
echo " failed. "
fi
}
check_parameter $1
It seems like the ìf [ -z $var ]or ìf [ -z ${var} ] doesn't work.
Note that a variable can be defined, but have an empty value; that is, you can distinguish between check_parameter and check_parameter "".
That said, you can handle either using a one-line parameter expansion.
check_parameter () {
: ${1?Parameter required}
}
or
check_parameter () {
: ${1:?Non-empty parameter required}
}
In both cases, the call check_parameter will have an exit status of 1 and display the given error message. Only in the second case will check_parameter "" fail and display the error message.
Note, though, that the construct will cause the function to simply return if executed in an interactive shell. In a non-interactive shell, the shell itself will exit as soon as the function returns, so it is slightly different from explicitly checking and using return if the variable is not set.
You need to separate the function. Try that.
function check_parameter() {
var=$1
if [ -z ${var} ]; then
return 1
else
return 0
fi
}
function scriptStatus(){
if [ $? == 0 ]; then
echo "Script OK. "
else
echo "Script failed. "
fi
}
check_parameter $1
scriptStatus
I think what it is looking for is something like this:
check_parameter() {
test -z "${1}"
if [ $? == 0 ]; then
echo 'Script Failed'
return 1
fi
echo 'Script OK'
return 0
}
This could be less complicated, but with the requirement of
Use return-statement and the variable $?
it seems that it's necessary to do test separately from checking $?.
Perhaps your instructor is actually looking for
check_parameter() {
if ! [ -z "$1" ]; then
echo "$0: Script OK" >&2
return 0
else
rc=$?
echo "$0: Script failed" >&2
return "$rc"
fi
}
if by itself already examines $? behind the scenes; but this takes care of preserving the exact error code - did [ -z ... ] fail with 1, or 2, or 255? - and relaying that back to the caller in case the precise failure code matters to them.
In production code, chepner's answer is what you should actually use to check for a missing parameter; but this answer is a common and useful recipe for how to handle errors in shell scripts generally. Take care to preserve the original error code and communicate it back to the caller, and print diagnostic messages to standard error, with the script's name visible in the error message in case it's being called from a larger script which calls many other helper scripts.
I have script
#!/bin/bash
set -e
if [[ ! $(asd) ]]; then
echo "caught command failure with exit code ${?}"
fi
echo "end of script"
purpose of script is to terminate execution on any non zero command exit code with set -e except when command is "caught" (comming from Java) as in case of bad command asd
if [[ ! $(asd) ]]; then
echo "caught command failure with exit code ${?}"
fi
however, though I "catch" the error and end of script prints to terminal, the error code is 0
echo "caught command failure with exit code ${?}"
so my question is how can I "catch" a bad command, and also print the exit code of that command?
edit
I have refactored script with same result, exit code is still 0
#!/bin/bash
set -e
if ! asd ; then
echo "caught command failure with exit code ${?}"
fi
echo "end of script"
Just use a short-circuit:
asd || echo "asd exited with $?" >&2
Or:
if asd; then
:
else
echo asd failed with status $? >&2
fi
You cannot do if ! asd, because ! negates the status and will set $? to 0 if asd exits non-zero and set $? to 1 if asd exits 0.
But note that in either case best practice is to simply call asd. If it fails, it should emit a descriptive error message and your additional error message is just unnecessary verbosity that is of marginal benefit. If asd does not emit a useful error message, you should fix that.
how can I "catch" a bad command, and also print the exit code of that command?
Often enough I do this:
asd && ret=$? || ret=$?
echo asd exited with $ret
The exit status of the whole expression is 0, so set -e doesn't exit. If asd succedess, then the first ret=$? executes with $? set to 0, if it fails, then the first ret=$? is omitted, and the second executes.
Sometimes I do this:
ret=0
asd || ret=$?
echo asd exited with $ret
which works just the same and I can forget if the && or || should go first. And you can also do this:
if asd; then
ret=0
else
ret=$?
fi
echo asd exited with $ret
Lets say a program that outputs a zero in case of success, or 1 in case of failure, like this:
main () {
if (task_success())
return 0;
else
return 1;
}
Similar with Python, if you execute exit(0) or exit(1) to indicate the result of running a script. How do you know what the program outputs when you run it in shell. I tried this:
./myprog 2> out
but I do not get the result in the file.
There's a difference between an output of a command, and the exit code of a command.
What you ran ./myprog 2> out captures the stderr of the command and not the exit code as you showed above.
If you want to check the exit code of the a program in bash/shell you need to use the $? operator which captures the last command exit code.
For example:
./myprog 2> out
echo $?
Will give you the exit code of the command.
BTW,
For capturing the output of a command, you may need to use 1 as your redirect where 1 captures stdout and 2 captures stderr.
The returnvalue of a command is stored in $?. When you want to do something with the returncode, it is best to store it in a variable before you call another command. The other command will set a new returncode in $?.
In the next code the echo will reset the value of $?.
rm this_file_doesnt_exist
echo "First time $? displays the rm result"
echo "Second time $? displays the echo result"
rm this_file_doesnt_exist
returnvalue_rm=$?
echo "rm returned with ${returnvalue}"
echo "rm returned with ${returnvalue}"
When you are interested in stdout/stderr as well, you can redirect them to a file. You can also capture them in a shell variable and do something with it:
my_output=$(./myprog 2>&1)
returnvalue_myprog=$?
echo "Use double quotes when you want to show the ${my_output} in an echo."
case ${returnvalue_myprog} in
0) echo "Finally my_prog is working"
;;
1) echo "Retval 1, something you give in your program like input not found"
;;
*) echo "Unexpected returnvalue ${returnvalue_myprog}, errors in output are:"
echo "${my_output}" | grep -i "Error"
;;
esac
So I need my while loop to continually loop but it stops after the first user's input, example:
[user]$: ./pleasefix.sh
Enter Input:test
test is writeable.
[user]$:
Heres my script as it is:
if [ "$#" -ne 0 ]
then
echo "$0" "is expecting no arguments; found $# $*"
echo "Usage: "$0""
exit 2
fi
while read -p "Enter Input:" userString
do
if [ -w "$userString" ]
then
echo ""$userString" is writeable."
exit 0
else
echo ""$userString" is nonexistent or not writeable."
exit 1
fi
done
What can I add to my while to make it actually loop and re prompt the user for another file name? Basically I want it to last forever until a EOF is sent (crtl + D)
Take the exit 0 and exit 1 out
You are using "exit" at both case if-else. You can remove one of them.
Remove the "exit 0" and the "exit 1"? Those will cause it to exit your script.