Add (collect) exit codes in bash - bash

I need to depend on few separate executions in a script and don't want to bundle them all in an ugly 'if' statement. I would like to take the exit code '$?' of each execution and add it; at the end, if this value is over a threshold - I would like to execute a command.
Pseudo code:
ALLOWEDERROR=5
run_something
RESULT=$?
..other things..
run_something_else
RESULT=$RESULT + $?
if [ $RESULT -gt ALLOWEDERROR ]
then echo "Too many errors"
fi
Issue: Even though the Internet claims otherwise, bash refuses to treat the RESULT and $? as integer. What is the correct syntax?
Thanks.

You might want to take a look at the trap builtin to see if it would be helpful:
help trap
or
man bash
you can set a trap for errors like this:
#!/bin/bash
AllowedError=5
SomeErrorHandler () {
(( errcount++ )) # or (( errcount += $? ))
if (( errcount > $AllowedError ))
then
echo "Too many errors"
exit $errcount
fi
}
trap SomeErrorHandler ERR
for i in {1..6}
do
false
echo "Reached $i" # "Reached 6" is never printed
done
echo "completed" # this is never printed
If you count the errors (and only when they are errors) like this instead of using "$?", then you don't have to worry about return values that are other than zero or one. A single return value of 127, for example, would throw you over your threshold immediately. You can also register traps for other signals in addition to ERR.

A quick experiment and dip into bash info says:
declare -i RESULT=$RESULT + $?
since you are adding to the result several times, you can use declare at the start, like this:
declare -i RESULT=0
true
RESULT+=$?
false
RESULT+=$?
false
RESULT+=$?
echo $RESULT
2
which looks much cleaner.
declare -i says that the variable is integer.
Alternatively you can avoid declare and use arithmetic expression brackets:
RESULT=$(($RESULT+$?))

Use the $(( ... )) construct.
$ cat st.sh
RESULT=0
true
RESULT=$(($RESULT + $?))
false
RESULT=$(($RESULT + $?))
false
RESULT=$(($RESULT + $?))
echo $RESULT
$ sh st.sh
2
$

For how to add numbers in Bash also see:
help let

If you want to use ALLOWEDERROR in your script, preface it with a $, e.g $ALLOWEDERROR.

As mouviciel mentioned collecting sum of return codes looks rather senseless. Probably, you can use array for accumulating non-zero result codes and check against its length. Example of this approach is below:
#!/bin/sh
declare RESULT
declare index=0
declare ALLOWED_ERROR=1
function write_result {
if [ $1 -gt 0 ]; then
RESULT[index++]=$1
fi
}
true
write_result $?
false
write_result $?
false
write_result $?
echo ${#RESULT[*]}
if [ ${#RESULT[*]} -gt $ALLOWEDERROR ]
then echo "Too many errors"
fi

Here are some ways to perform an addition in bash or sh:
RESULT=`expr $RESULT + $?`
RESULT=`dc -e "$RESULT $? + pq"`
And some others in bash only:
RESULT=$((RESULT + $?))
RESULT=`bc <<< "$RESULT + $?"`
Anyway, exit status on error is not always 1 and its value does not depend on error level, so in the general case there is not much sense to check a sum of statuses against a threshold.

Related

if statement always goes to the else

I just started learning Bash scripting and i have to do a program that separate between one bit map image to two (the image is broken), I already found on the web how to write loops and statements
but i don't know why my if statement is always goes to the else.
the if is modulo by 2 thats equals to 0
here is the following code
#!/bin/sh
OUTPUT="$(hexdump -v -e '/1 "%02X\n"' merge.bmp)"
echo $OUTPUT
vars=0
count=1
touch one
touch two
for i in $OUTPUT
do
if (($vars%2==0))
then
echo "1"
else
echo "2"
fi
vars=$((vars+count))
done
in the terminal the following error is
./q3.sh: 14: ./q3.sh: 2885%2==0: not found
2
i really don't know why the if always print 2
The shebang line is wrong, it should be:
#!/bin/bash
((expression)) is a bash extension, not available in sh.
The /bin/sh version of the (()) bashism is this:
if test $(($vars % 2)) -eq 0; then
echo "1"
...
fi
Since $(()) knows about variable names, you may even drop the dollar and write
if test $((vars % 2)) -eq 0; then
echo "1"
...
fi

Can bash echo command return non-zero exit code?

Like the title says, is there any case where echo will exit non-zero in bash/sh?
code ex.
until monitor_thing_happens; do
test $retry_counter -eq 0 && echo "thing didn't happen" && exit 1
let "retry_counter--"
echo "tries remaining: ${retry_counter}"
sleep 5
done
In the above example, if echo exits non-zero, the && logic breaks, we never exit 1, and we loop forever. Any danger / edge case where echo can exit non-zero?
Yes, echo has a non-zero return status if there's a write error.
Quoting the bash manual:
'echo'
echo [-neE] [ARG ...]
Output the ARGs, separated by spaces, terminated with a newline.
The return status is 0 unless a write error occurs.
A demonstration:
$ cat foo.bash
#!/bin/bash
echo hello
echo "The echo command returned a status of $?" > /dev/tty
$ ./foo.bash > /dev/full
./foo.bash: line 3: echo: write error: No space left on device
The echo command returned a status of 1
$
/dev/full is a device, similar to /dev/zero except that any attempt to write to it will fail with an ENOSPC error.
Nope, no risk. From man bash:
echo [-neE] [arg ...]
Output the args, separated by spaces, followed by a newline.
The return status is always 0. If -n is specified, the trailing
newline is suppressed. If the -e option is given, interpretation of
the following backslash-escaped characters is enabled. The -E option
disables the interpretation of these escape characters, even on systems
where they are interpreted by default. The xpg_echo shell option may
be used to dynamically determine whether or not echo expands these
escape characters by default. echo does not interpret -- to mean the
end of options. echo interprets the following escape sequences:
Emphasis on "The return status is always 0".
From a code quality standpoint, I would recommend not using test unless you're forced to for shell compatibility reasons. In general, use [[, but for arithmetic expressions you can also use ((:
# The generic way
[[ $retry_counter -eq 0 ]] && echo "Thing didn't happen" && exit 1
# The arithmetic way
(( retry_counter == 0 )) && echo "Thing didn't happen" && exit 1
From help man (bash):
Exit Status:
Returns success unless a write error occurs.
UPDATED
So if you echo to a stream that suddenly fails, you will get another exit code.
Different comments show risks.
You can try
retry_counter=5
while [ retry_counter -gt 0 ]; do
monitor_thing_happens && break
(( retry_counter-- ))
echo "tries remaining: ${retry_counter}"
sleep 5
done
Not without risk ! When the function monitor_things_happen resets the same variable retry_counter the loop wil run a long time.

better way to fail if bash `declare var` fails?

Problem
In some bash scripts, I don't want to set -e. So I write variable declarations like
var=$(false) || { echo 'var failed!' 1>&2 ; exit 1 ; }
which will print var failed! .
But using declare, the || is never taken.
declare var=$(false) || { echo 'var failed!' 1>&2 ; exit 1 ; }
That will not print var failed!.
Imperfect Solution
So I've come to using
declare var=$(false)
[ -z "${var}" ] || { echo 'var failed!' 1>&2 ; exit 1 ; }
Does anyone know how to turn the Imperfect Solution two lines into a neat one line ?
In other words, is there a bash idiom to make the declare var failure neater?
More Thoughts
This seems like an unfortunate mistake in the design of bash declare.
Firstly, the issue of two lines vs. one line can be solved with a little thing called Mr. semicolon (also note the && vs. ||; pretty sure you meant the former):
declare var=$(false); [ -z "${var}" ] && { echo 'var failed!' 1>&2 ; exit 1 ; }
But I think you're looking for a better way of detecting the error. The problem is that declare always returns an error code based on whether it succeeded in parsing its options and carrying out the assignment. The error you're trying to detect is inside a command substitution, so it's outside the scope of declare's return code design. Thus, I don't think there's any possible solution for your problem using declare with a command substitution on the RHS. (Actually there are messy things you could do like redirecting error infomation to a flat file from inside the command substitution and reading it back in from your main code, but just no.)
Instead, I'd suggest declaring all your variables in advance of assigning them from command substitutions. In the initial declaration you can assign a default value, if you want. This is how I normally do this kind of thing:
declare -i rc=-1;
declare s='';
declare -i i=-1;
declare -a a=();
s=$(give me a string); rc=$?; if [[ $rc -ne 0 ]]; then echo "s [$rc]." >&2; exit 1; fi;
i=$(give me a number); rc=$?; if [[ $rc -ne 0 ]]; then echo "i [$rc]." >&2; exit 1; fi;
a=($(gimme an array)); rc=$?; if [[ $rc -ne 0 ]]; then echo "a [$rc]." >&2; exit 1; fi;
Edit: Ok, I thought of something that comes close to what you want, but if properly done, it would need to be two statements, and it's ugly, although elegant in a way. And it would only work if the value you want to assign has no spaces or glob (pathname expansion) characters, which makes it quite limited.
The solution involves declaring the variable as an array, and having the command substitution print two words, the first of which being the actual value you want to assign, and the second being the return code of the command substitution. You can then check index 1 afterward (in addition to $?, which can still be used to check the success of the actual declare call, although that shouldn't ever fail), and if success, use index 0, which elegantly can be accessed directly as a normal non-array variable can:
declare -a y=($(echo value-for-y; false; echo $?;)); [[ $? -ne 0 || ${y[1]} -ne 0 ]] && { echo 'error!'; exit 1; }; ## fails, exits
## error!
declare -a y=($(echo value-for-y; true; echo $?;)); [[ $? -ne 0 || ${y[1]} -ne 0 ]] && { echo 'error!'; exit 1; }; ## succeeds
echo $y;
## value-for-y
I don't think you can do better than this. I still recommend my original solution: declare separately from command substitution+assignment.

How do I set $? or the return code in Bash?

I want to set a return value once so it goes into the while loop:
#!/bin/bash
while [ $? -eq 1 ]
do
#do something until it returns 0
done
In order to get this working I need to set $? = 1 at the beginning, but that doesn't work.
You can set an arbitrary exit code by executing exit with an argument in a subshell.
$ (exit 42); echo "$?"
42
So you could do:
(exit 1) # or some other value > 0 or use false as others have suggested
while (($?))
do
# do something until it returns 0
done
Or you can emulate a do while loop:
while
# do some stuff
# do some more stuff
# do something until it returns 0
do
continue # just let the body of the while be a no-op
done
Either of those guarantee that the loop is run at least one time which I believe is what your goal is.
For completeness, exit and return each accept an optional argument which is an integer (positive, negative or zero) which sets the return code as the remainder of the integer after division by 256. The current shell (or script or subshell*) is exited using exit and a function is exited using return.
Examples:
$ (exit -2); echo "$?"
254
$ foo () { return 2000; }; foo; echo $?
208
* This is true even for subshells which are created by pipes (except when both job control is disabled and lastpipe is enabled):
$ echo foo | while read -r s; do echo "$s"; exit 333; done; echo "$?"
77
Note that it's better to use break to leave loops, but its argument is for the number of levels of loops to break out of rather than a return code.
Job control is disabled using set +m, set +o monitor or shopt -u -o monitor. To enable lastpipe do shopt -s laspipe. If you do both of those, the exit in the preceding example will cause the while loop and the containing shell to both exit and the final echo there will not be performed.
false always returns an exit code of 1.
#!/bin/bash
false
while [ $? -eq 1 ]
do
#do something until it returns 0
done
#!/bin/bash
RC=1
while [ $RC -eq 1 ]
do
#do something until it returns 0
RC=$?
done
Some of answers rely on rewriting the code. In some cases it might be a foreign code that you have no control over.
Although for this specific question, it is enough to set $? to 1, but if you need to set $? to any value - the only helpful answer is the one from Dennis Williamson's.
A bit more efficient approach, which does not spawn a new child (but is a also less terse), is:
function false() { echo "$$"; return ${1:-1}; }
false 42
Note: echo part is there just to verify it runs in the current process.
I think you can do this implicitly by running a command that is guaranteed to fail, before entering the while loop.
The canonical such command is, of course, false.
Didn't find anything lighter than just a simple function:
function set_return() { return ${1:-0}; }
All other solutions like (...) or [...] or false might contain an external process call.
Old question, but there's a much better answer:
#!/bin/bash
until
#do something until it returns success
do
:;
done
If you're looping until something is successful, then just do that something in the until section. You can put exactly the same code in the until section you were thinking you had to put in the do/done section. You aren't forced to write the code in the do/done section and then transfer its results back to the while or until.
$? can contain a byte value between 0..255. Return numbers outside this range will be remapped to this range as if a bitwise and 255 was applied.
exit value - can be used to set the value, but is brutal since it will terminate a process/script.
return value - when used in a function is somewhat traditional.
[[ ... ]] - is good for evaluating boolean expressions.
Here is an example of exit:
# Create a subshell, but, exit it with an error code:
$( exit 34 ); echo $? # outputs: 34
Here are examples of return:
# Define a `$?` setter and test it:
set_return() { return $1; }
set_return 0; echo $? # outputs: 0
set_return 123; echo $? # outputs: 123
set_return 1000; echo $? # outputs: 232
set_return -1; echo $? # outputs: 255
Here are are examples of [ ... ]:
# Define and use a boolean test:
lessthan() { [[ $1 < $2 ]]; }
lessthan 3 8 && echo yes # outputs: yes
lessthan 8 3 && echo yes # outputs: nothing
Note, when using $? as a conditional, zero (0) means success, non-zero means failure.
Would something like this be what your looking for ?
#!/bin/bash
TEMPVAR=1
while [ $TEMPVAR -eq 1 ]
do
#do something until it returns 0
#construct the logic which will cause TEMPVAR to be set 0 then check for it in the
#if statement
if [ yourcodehere ]; then
$TEMPVAR=0
fi
done
You can use until to handle cases where #do something until it returns 0 returns something other than 1 or 0:
#!/bin/bash
false
until [ $? -eq 0 ]
do
#do something until it returns 0
done
This is what I'm using
allow_return_code() {
local LAST_RETURN_CODE=$?
if [[ $LAST_RETURN_CODE -eq $1 ]]; then
return 0
else
return $LAST_RETURN_CODE
fi
}
# it converts 2 to 0,
my-command-returns-2 || allow_return_code 2
echo $?
# 0
# and it preserves the return code other than 2
my-command-returns-8 || allow_return_code 2
echo $?
# 8
Here is an example using both "until" and the ":"
until curl -k "sftp://$Server:$Port/$Folder" --user "$usr:$pwd" -T "$filename";
do :;
done

Bash, no-arguments warning, and case decisions

I am learning bash.
I would like to do a simple script that, when not arguments given, shows some message. And when I give numers as argument,s depending on the value, it does one thing or another.
I would also like to know suggestions for the best online manuals for beginners in bash
Thanks
if [[ $# -eq 0 ]] ; then
echo 'some message'
exit 0
fi
case "$1" in
1) echo 'you gave 1' ;;
*) echo 'you gave something else' ;;
esac
The Advanced Bash-Scripting Guide is pretty good. In spite of its name, it does treat the basics.
If only interested in bailing if a particular argument is missing, Parameter Substitution is great:
#!/bin/bash
# usage-message.sh
: ${1?"Usage: $0 ARGUMENT"}
# Script exits here if command-line parameter absent,
#+ with following error message.
# usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
Example
if [ -z "$*" ]; then echo "No args"; fi
Result
No args
Details
-z is the unary operator for length of string is zero.
$* is all arguments.
The quotes are for safety and encapsulating multiple arguments if present.
Use man bash and search (/ key) for "unary" for more operators like this.
Old but I have reason to rework the answer now thanks to some previous confusion:
if [[ $1 == "" ]] #Where "$1" is the positional argument you want to validate
then
echo "something"
exit 0
fi
This will echo "Something" if there is no positional argument $1. It does not validate that $1 contains specific information however.
If there is not only 1 argument, then print usage and exit
if [[ $# != 1 ]] ; then
echo 'USAGE: bin/siege COOKIE'
exit 0
fi

Resources