Bash conditional execution - bash

I took this code from a script I found online :
[ $# = 0 ] && usage
If there are no parameters at the command line, then call the usage method (which print the help info).
The thing I don't understand is why does the script exits after calling usage? Shouldn't it simply continue to other code?

There are multiple ways this can happen:
The usage method has an exit command in it
The usage method has a return 1 command (or other non-zero value) and the script is invoked with -e flag, for example #!/bin/sh -e shebang
The usage method has an operation that fails and the script is invoked with -e flag
Maybe there are more ways that I don't recall now.
Personally, I always use exit 1 as the last command in a usage method, so the behavior seems all natural to me.

It would carry on unless 'usage' executes 'exit' command

Related

Exiting a shell-script at the end with a non-zero code if any command fails

I am making a shell script that runs a bunch of tests as part of a CI pipeline. I would like to run all of the tests (I DO NOT WANT TO EXIT EARLY if one test fails). Then, at the end of the script, I would like to return with a negative exit code if any of the tests failed.
Any help would be appreciated. I feel like this would be a very common use case, but I wasn't able to find a solution with a bit of research. I am pretty sure that I don't want set -e, since this exits early.
My current idea is to create a flag to keep track of any failed tests:
flag=0
pytest -s || flag=1
go test -v ./... || flag=1
exit $flag
This seems strange, and like more work than necessary, but I am new to bash scripts. Am I missing something?
One possible way would be to catch the non-zero exit code via trap with ERR. Assuming your tests don't contain pipelines | and just return the error code straight to the shell launched, you could do
#!/usr/bin/env bash
exitCodeArray=()
onFailure() {
exitCodeArray+=( "$?" )
}
trap onFailure ERR
# Add all your tests here
addNumbers () {
local IFS='+'
printf "%s" "$(( $* ))"
}
Add your tests anywhere after the above snippet. So we keep adding the exit code to the array whenever a test returns a non-zero return code. So for the final assertion we check if the sum of the array elements is 0, because in an ideal case all cases should return that if it is successful. We reset the trap set before
trap '' ERR
if (( $(addNumbers "${exitCodeArray[#]}") )); then
printf 'some of your tests failed\n' >&2
exit -1
fi
The only way I could imagine using less code is if the shell had some sort of special all compound command that might look something like
# hypothetical all command
all do
pytest -s
go test -v ./...
done
whose exit status is the logical or of the exit statuses of the contained command. (An analogous any command would have the logical and of its commands' exit statuses as its own exit status.)
Lacking such a command, you current approach is what I would use. You could adapt #melpomene's suggestion of a chk function (which I would call after a command rather than having it call your command so that it works with arbitrary shell commands):
chk () { flag=$(( flag | $? )); }
flag=0
pytest -s; chk
go test -v ./...; chk
exit "$flag"
If you aren't using it for anything else, you could abuse the DEBUG trap to update flag before each command.
trap 'flag=$((flag | $?))' DEBUG
pytest -s
go test -v ./...
exit "$flag"
(Be aware that a debug trap executes before the shell executes another command, not immediately after a command is executed. It's possible that the only time this matters is if you expect the trap to fire between the last command completing and the shell exiting, but it's still worth being aware of.)
I vote for Inian's answer. Traps seem like the perfect way to go.
That said, you might also streamline things by use of arrays.
#!/usr/bin/env bash
testlist=(
"pytest -s"
"go test -v ./..."
)
for this in "${testlist[#]}"; do
$this || flag=1
done
exit $flag
You could of course fetch the content of the array from another file, if you wanted to make a more generic test harness that could be used by multiple tools. Heck, mapfile could be a good way to populate an array.

How to use detect failure in command substitution

As you know set -e is very useful to discover any failure when command is executed. However I found it to be delicate to use and I don't know how to use it in the following scenarios:
==============First example================================
set -e
function main() {
local X1="$(exp::notdefined)"
echo "Will reach here : ${LINENO}"
X2="$(exp::notdefined)"
echo "Will not reach here : ${LINENO}"
}
main
==============Second example================================
set -e
function exp::tmp() {
echo "Now '$-' is "$-" : ${LINENO}"
false
return 0
}
function main() {
X1="$(exp::tmp)"
echo "Will reach here : ${LINENO}. '\$X1' : ${X1}"
X2="$(set -e ; exp::tmp)"
echo "Will not reach here : ${LINENO}"
}
main
===============================
The first example shows that, if we use command substitution on a local variable, then it will not fail even if the command substituted is not found. I don't know how to detect these kinds of failures.
The second example shows that, if the bash options (-e) will not propagate unless we call set -e inside the command braces. Is there any better way to do this?
You request immediate exit on pipeline failure with -e, e.g.:
-e Exit immediately if a pipeline (which may consistof a single simple
command), a list, or a compound command (see SHELL GRAMMAR above),
exits with a non-zero status.
The reason the bad command substitution does not cause failure within the function is because local provides its own return status.
local [option] [name[=value] ...]
... The return status is 0 unless local is used outside a function, an
invalid name is supplied, or name is a readonly variable.
The assignment of a failed command substitution does not cause local to return non-zero. Therefore, no immediate-exit is triggered.
As far as checking for a failure of command substitution following local, since the output is assigned to the variable, and the return will not be non-zero in the event of command substitution failure, you would have to validate by checking the variable contents itself for expected values for success/failure.
From the bash manual:
Subshells spawned to execute command substitutions inherit the value of
the -e option from the parent shell. When not in posix mode, bash
clears the -e option in such subshells.
Example 2 behaves differently when bash runs with --posix; however for example 1 I can't find any documentation explaining why local would cause this.

Why do we pass 0 as a parameter to "exit"?

In the book learn ruby the hard way, I found a syntax to exit from the program:
Process.exit(0)
Why is the parameter 0 being passed in the exit method here even though it works if I pass another integer or do not pass any parameter? What is the significance of 0?
This is an 'exit code'.
This exit code has special meaning in some cases (see for example http://tldp.org/LDP/abs/html/exitcodes.html)
You can pass whatever you want, if the code isn't caught after, this will have no effects.
Here '0' is for 'Everything works fine !'
It is because when a child process is started (child process being your Ruby script in that case) the parent process (shell, system, etc.) can wait for it to finish.
Once it's finished, it can tell the parent process what is the status of it's execution. Zero usually means that the execution has been successfull and completed without any errors.
If you, on example, run your script from bash shell, and it will call Process.exit(0), you could check if it succeeded using $? variable:
$ ./my_ruby.script # calls Process.exit(0)
$ echo $?
0 # ok, script finished with no errors.

Getting the return value in "sh -e"

I'm writing a shell script with #!/bin/sh as the first line so that the script exits on the first error. There are a few lines in the file that are in the form of command || true so that the script doesn't exit right there if the command fails. However, I still want to know know the exit code of the command. How would I get the exit code without having to use set +e to temporarily disable that behavior?
Your question appears to imply set -e.
Assuming set -e:
Instead of command || true you can use command || exitCode=$?. The script will continue and the exit status of command is captured in exitCode.
$? is an internal variable that keeps the exit code of the last command.
Since || short-circuits if command succeeds, set exitCode=0 between tests or instead use: command && exitCode=0 || exitCode=$?.
But prefer to avoid set -e style scripting altogether, and instead add explicit error handling to each command in your script.
If you want to know the status of the command, then presumably you take different actions depending on its value. In which case your code should look something like:
if command; then
# do something when command succeeds
else
# do something when command fails
fi
In that case you don't need to do anything special, since the shell will not abort when command fails.
The only reasons set -e would give you any problems is if you write your code as:
command
if test $? = 1; ...
So don't do that.

why is bash -e disabled within a function run with not (!) [duplicate]

This question already exists:
“set -e” in a function [duplicate]
Closed 7 years ago.
Note: If you are looking for a workaround because set -e does not work in a function, please go to “set -e” in a function. This question is about why it does not work as expected.
When the following is run on GNU bash, version 4.1.5(1)-release :
set -ex
PS4=' ${FUNCNAME[0]}: $LINENO: '
function f() {
set -e
false
echo $#
}
! f why does it display ? It should stop because of -e
f
It displays
: 11: f why does it display '?' It should stop because of -e
f: 6: set -e
f: 7: false
f: 8: echo why does it display '?' It should stop because of -e
why does it display ? It should stop because of -e
: 12: f
f: 6: set -e
f: 7: false
I expect it to never go past the false, because -e means "exit when a command has a non zero exit status". I am aware that -e has a tricky behavior, as explained in http://mywiki.wooledge.org/BashFAQ/105 but I would like to understand what happens in this specific case. I am using -e and it proved most helpful in many very simple scenarios. This scenario is a little more tricky but if it can be explained I may use -e instead adding || exit 1 after each line.
While trying to solve a similar problem, I read the man page for set -e:
-e errexit
Exit immediately if a simple command exits with a non-zero
status, unless the command that fails is part of an until or
while loop, part of an if statement, part of a && or || list,
or if the command's return status is being inverted using !.
Note the last bit about the return status being inverted. I guess the logical negation of function f must have an effect on the behaviour of set -e within that function.
Here's from the trap section of man bash (my emphasis):
If a sigspec is ERR, the command arg is executed whenever a simple command has a non-zero exit status, subject to the following conditions. The ERR trap is not executed if the failed command is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of a command executed in a && or || list, or if the command's return value is being inverted via !. These are the same conditions obeyed by the errexit option.
So ! command effectively disables the ERR trap inside command. I guess the reasoning is that you might not want a command exiting early if you are "kinda explicitly" checking the exit code after running it with !. Looks like bad design, since it can be used to effectively disable safeguards pretty much anywhere.
I can't precisely answer the why, but I found this snippet here:
On a slightly related note, by default bash takes the error status of the last item in a pipeline, which may not be what you want. For example, false | true will be considered to have succeeded. If you would like this to fail, then you can use set -o pipefail to make it fail.
And by updating your script as such:
function f() {
echo $SHELLOPTS
set -e
set -o pipefail
false
echo "$#"
}
It seems to behave as you expect.
So my best guess for "why" (probably the same as yours by now), is that the ! causes the function to be handled in some sort of "pipe" mode. Again... why ! means 'pipe', I guess I don't really know. Maybe a better bash expert can answer that part for us.

Resources