Bash functions ignore set -e - linux

How can I run a function as a "tested command" and perform action on failure, while still aborting the function as soon as an error occur?
Consider the following script
#!/bin/bash -e
function foo() {
echo Entering foo
false
echo Should not reach this line
}
foo || echo I want to see this line on failure in foo
foo
The output I'm getting is
Entering foo
Should not reach this line
Entering foo
While I would like to get
Entering foo
I want to see this line on failure in foo
Entering foo
I guess what I'm looking for is a way to mark the function as untested command. According bash man page
-e errexit
Exit immediately if any untested command fails in non-interactive
mode. The exit status of a command is considered to be explicitly
tested if the command is part of the list used to control an if,
elif, while, or until; if the command is the left hand operand of
an “&&” or “||” operator; or if the command is a pipeline preceded
by the ! operator. If a shell function is executed and its exit
status is explicitly tested, all commands of the function are con‐
sidered to be tested as well.
EDIT
The expected output was wrong. edited it for clarity

set -e is disabled in the first call of foo since it's on the left hand side of ||.
Also, you would never see the I want to see this ... string being outputted, unless the last echo in foo somehow failed (it's that last echo in foo that determines the exit status of the function).
foo() {
echo Entering foo
false && echo Should not reach this line
}
foo || echo I want to see this line on failure in foo
foo
The above outputs (with or without set -x)
Entering foo
I want to see this line on failure in foo
Entering foo
Now false is the last executed statement in foo.

I ended up wrapping the code to do so in a utility function below.
#!/bin/bash -e
# Runs given code aborting on first error and taking desired action on failure
# $1 code to invoke, can be expression or function name
# $2 error handling code, can be function name or expressions
function saferun {
set +e
(set -E ; trap 'exit 1' ERR ; eval $1)
[ $? -ne 0 ] && eval $2
set -e
}
function foo() {
echo Entering foo
false
echo Should not reach this line
}
saferun foo "echo I want to see this line on failure in foo"
foo
Let's break it down:
set +e and set -e are used to suppress failure on error, as otherwise the script will just exit on first error
trap is used to abort the execution on any error (instead of set -e) () is used to run the given code in subshell, so outer script will keep running after failure, and set -E is used to pass the trap into the subshell. so (set -E ; trap 'exit 1' ERR ; eval $1) run the given code / function aborting on first error while not exiting the whole script
$? -ne 0 check for failures and eval $2 runs the error handling code

Related

Linux: run multiple commands without losing individual return codes?

I read this question, but my problem is that I have "plenty" of commands to run; and I need a solution that works for a systems calls.
We have an exit task that basically triggers a lot of "cleanup" activity within our JVM. The part I am working on has to call a certain script, not once, but n times!
The current implementation on the Java side creates n ProcessBuilder objects; and each one runs a simple bash script.sh parm ... where parm is different on each run.
Now I want to change the Java side to only make one system call (instead of n) using ProcessBuilder.
I could just use the following command:
bash script.sh parm1 ; bash script.sh parm2 ; ... ; bash script.sh parmN
Now the thing is: if one of the runs fails ... I want all other runs to still take place; but I would like to get a "bad" return code in the end.
Is there a simple, elegant way to achieve that, one that works with command strings coming from system calls?
You can build up the return codes in a subshell as you go, then check them at the end using arithmetic evaluation. E.g., on my test system (cygwin), at a bash prompt:
$ ( r=; echo "foo" ; r=$r$?; echo "bar" ; r=$r$? ; echo "baz" ; r=$r$? ; (($r==0)) )
foo
bar
baz
$ echo $?
0 <--- all the commands in the subshell worked OK, so the status is OK
and
VVVV make this echo fail
$ ( r=; echo "foo" ; r=$r$?; echo "bar" 1>&- ; r=$r$? ; echo "baz" ; r=$r$? ; (($r==0)) )
foo
-bash: echo: write error: Bad file descriptor
baz
$ echo $?
1 <--- failure code, but all the commands in the subshell still ran.
So, in your case,
(r=; bash script.sh parm1 ; r=$r$?; bash script.sh parm2 ; r=$r$?; ... ; bash script.sh parmN r=$r$?; (($r==0)) )
You can also make that slightly shorter with a function s that stashes the return code:
$ (r=;s(){ r=$r$?;}; echo "foo" ; s; echo "bar" 1>&-; s; echo "baz" ; s; (($r==0)) )
foo
-bash: echo: write error: Bad file descriptor
baz
$ echo $?
1
s(){ r=$r$?;} defines a function s that will update r. Then s can be run after each command. The space and semicolon in the definition of s are required.
What's happening?
r= initializes r to an empty string. That will hold our return values as we go.
After each command, r=$r$? tacks that command's exit status onto r. There are no spaces in r, by construction, so I left off the quotes for brevity. See below for a note about negative return values.
At the end, (($r==0)) succeeds if r evaluates to 0. So, if all commands succeeded, r will be 000...0, which equals 0.
The exit status of a subshell is the exit status of its last command, here, the (($r==0)) test. So if r is all zeros, the subshell will report success. If not, the subshell will report failure ($?==1).
Negative exit values
If some of the programs in the subshell may have negative exit values, this will probably still work. For example, 0-255100255 is a valid expression that is not equal to zero. However, if you had two commands, the first exited with 127, and the second exited with -127, r would be 127-127, which is zero.
To avoid this problem, replace each r=$r$? with r=$r$((! ! $?)). The double logical negation $((! ! $?)) converts 0 to 0 and any other value, positive or negative, to 1. Then r will only contain 0 and 1 values, so the (($r==0)) test will be correct. (You do need spaces after each ! so bash doesn't think you're trying to refer to your command history.)
A binary OR-ing of all exit codes will be zero (0) "if" and "only if" all exit codes are zero (0).
You could get a running exit code with this simple arithmetic expression:
excode=((excode | $?))
To run all parameters, you could use an script ("callcmd") like:
#!/bin/bash
excode=0
for i
do cmd "$i"
excode=((excode | $?))
done
echo "The final exit code is $excode"
# if [[ $excode -ne 0 ]]; exit 1; fi # An alternative exit solution.
Where this script ("callcmd") is called from java as:
callcmd parm1 parm2 parm3 … parmi … parmN
The output of each command is available at the usual standard output and the error strings (if any) will also be available in the stderr (but all will be joined, so the command "cmd" should identify for which parm is emitting the error).
r=0; for parm in parm1 parm2 ... parmN; do
bash script.sh "$parm" || r=1
done
exit "$r"
You can do it like this
bash script.sh parm1 || echo "FAILED" > some.log ; bash script.sh parm2 || echo "FAILED" > some.log; ... ; bash script.sh parmN|| echo "FAILED" > some.log
Then check if there is some.log file.
|| - It's simple bash logical or ( executed if exit status of previous one is non-zero)

Pipe value to exit code

I'm looking to create a one-line command which does some stuff and gets a value (which would generally be output) and instead turn that into an exit code (so I can trigger a subsequent step which responds to non-zero exit codes).
For example, running something like this:
echo 5 | exit
And then having a subsequent echo $? output 5, the value I gave it earlier.
The above syntax doesn't work. Is there a way to achieve this?
Assuming:
cmd() { echo 5; }
For a literal answer:
cmd | (read -r rc && exit "$rc")
However, you can also run:
( exit "$(cmd)" ) # parens create a subshell so we aren't exiting the main shell
You can just use command substitution:
( exit $(cmd) )
Assuming cmd is returning an integer between 0-255
An example:
$> ( exit $(echo 5) )
$> echo $?
5

What happens if a variable is assigned with command expression in backticks

Below is the code of bash:
a=`echo hello`
echo $a
output is :
hello
But I think it should be:
hello
0
You think wrong ;-)
Putting the command in backticks assigns the output (stdout) from the expression on the right to the variable on the left.
$? gives you the "output status" (or return code) of the command - aka the "0" you were expecting.
So:
a=`echo hello`
Runs the command "echo hello" but instead of echoing to stdout, it "echoes" to varaiable a. So a=whatever_the_command_would_have_written_to_stdout (in this case "hello") - nothing is actually written to stdout because it is "captured" by the ``s
You mistakenly think that a=`echo hello`:
executes echo hello and prints its stdout output directly to the caller's stdout,
and then assigns the exit code (return value) of the echo command to variable $a.
Neither is true; instead:
echo hello's stdout output is captured in memory (without printing to the caller's stdout; that's how command substitutions work),
and that captured output is assigned to $a.
A command's exit code (a return value indicating success vs. failure) is never directly returned in POSIX-like shells such as Bash.
The only way to use an exit code is either:
explicitly, by accessing special variable $? immediately after the command ($? contains the most recent command's exit code)
implicitly, in conditionals (a command whose exit code is 0 evaluates to true in a conditional, any other exit code implies false).
Thus, to achieve what you're really trying to do, use:
echo 'hello' # Execute a command directly (its stdout output goes to the caller's stdout)
a=$? # Save the previous command's exit code in var. $a
echo "$a" # Echo the saved exit code.
As this [ this ] answer already mentioned, the return value for the last executed command is stored in
$? # ${?} is sometimes needed
If you wish a to contain 'hello' and the return value of echo hello in separate lines, ie
hello
0
below is one way to do it
$ a=`echo -en "hello\n" && echo -n $?` # here $? is ret val for 1st echo
$ echo -e "$a"
hello
0
Note
-n with echo suppresses the trailing new line
-e with echo interprets escape sequences
Since && is the logical and operator, the second echo wouldn't have been executed had the first echo failed
Another important point to note is that even the assignment ie
a=b
has a return value.

View exit code of a program (After the program exited)

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

Check the error code for a shell method which returns value

I have a sample shell method which:
Either returns a value after some processing.
Else exit with an exit code if any error occurs.
The sample script is as follows:
a.sh
#!/bin/bash
test(){ # test method
if [ $1 == 2 ]; then # the condition for checking
exit 500 # exit with exit code
else
echo $1 # do some business logic here and return the value
fi
}
I have problem with checking the error code. To use this test method I have another sample script.
b.sh
#!/bin/bash
source a.sh
val=`test $1` # call the test method
if [ $? == 500 ]; then # check the value
echo "here is an error" # error occurs
else
echo $val # no error, do something with returned value
fi
Followings are the output:
Input: ./b.sh 10
Output: 10
Expected output: 10
Input: ./b.sh 2
Output:
Expected output: here is an error
I think there is the problem in b.sh because if [ $? == 500 ]; then is always false. Is there any way to make this condition true or something to get the error code?
The idiomatic way of doing this is:
if val=$(test "$1"); then
echo "$val"
else
echo "An error occurred."
fi
The if statement tests the status of the command (or pipeline) which follows it, and executes the then branch of the status indicated success. The only time you need to explicitly check the value of $? is the rare case of a utility which returns different failure status values (and documents what they mean).
Many people seem to think that [ ... ] and other such things are part of the syntax of the if command. They are not; they are just commands (or builtins) whose names are punctuation. You can use any command whatsoever, or even several in a row; in the latter case, the status checked will be that of the last command.

Resources