Imagine the code:
ls || echo "Unable to execute ls (returned non zero)"
What if I needed to execute more commands like:
ls || echo "this is echo 1" <some operator> echo "this is echo 2" <some operator> exit 1
in C (assumming I have a function ls) I could do (even if it looks insane):
ls() || (command1() && command2());
but I doubt I can use parentheses like this in bash.
I know I can create a Bash function that would contain these commands, but what if I needed to combine this with exit 1 (exit in a function would exit that function, not whole script)?
You can group multiple commands within { }. Saying:
some_command || { command1; command2; }
would execute command1 and command2 if some_command exited with a non-zero return code.
{}
{ list; }
Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The
semicolon (or newline) following list is required.
In fact, you can use parentheses. They just tell bash "run the commands in a subshell". You can also use curlies:
ls || { echo 1 ; echo 2 ; }
Note that ; or a newline before the closing curlie is not optional.
Related
This question already has answers here:
How does AND and OR operators work in Bash?
(6 answers)
Closed 2 years ago.
When I execute this in the following line in my shell
echo this || echo that && echo other
The output is:
this
other
Now I don't understand how echo other is executed, because echo that does not return a successful exit status because it is not executed. The && operator only executes the right command when the left command exits successfully.
Does this mean the && operator will execute the righthand command if anything on the left side exits successfully?
You can rewrite that example as:
(echo this || echo that) && echo other
You say echo that does not return a successful exit status, but rather, it doesn't return anything at all - it is not executed. So the expression (echo this || echo that) has a successful result (the return of echo this), which makes echo other be executed.
A good example for this situation is just running echo this || echo that - it has a return value of 0, as such, not executing echo that does not turn it into a failure.
Using both || and && in the same line of code, is not a good practice, while it might work on some cases/situations, that does not mean it will work on all cases.
see http://mywiki.wooledge.org/BashPitfalls#cmd1_.26.26_cmd2_.7C.7C_cmd3
Always use an if statement if you feel like doing a short circuit.
Command grouping is a work around if you really need to do that, using the { }
echo this || { echo that && echo other; }
&& and || have the same order of precedence.
So echo this || echo that occurs first and then && echo other.
The effect of:
command1 || command2
is as follows:
If command1 returns zero: command2 is not executed, and overall return value is 0.
If command1 returns non-zero: command2 is executed, and overall return value is that of command2.
It is somewhat analogous to short-circuit evaluation of an "or" operator in various programming languages (e.g. a || b in C) except that a "true" value is a zero (i.e. successful) exit status rather than a non-zero value of an expression.
Given that the echo statements return 0, this means that the effect of
echo this || echo that
is to execute echo this but not echo that, and to have an overall exit status of 0.
Given this, and that the || and && are treated with equal precedence (so are evaluated from left to right), the command sequence:
echo this || echo that && echo other
will also cause echo other to be executed (because foo && bar will execute bar if foo returned 0).
So, command1 || command2 && command3 is valid, but if should not be read as meaning:
(a) Run command1. Then if command1 failed then run command2 but if command1 succeeded then run command3.
Instead, it should be read as:
(b) Run command1. If command1 failed then run command2. If either (command1 succeeded) or (command1 failed but command2 succeeded), then run command3.
(where "succeeded" means exited with zero status).
If the intention is as described in (a), then this should instead be implemented using:
if ! command1 ; then command2 ; else command3 ; fi
or
if command1 ; then command3 ; else command2 ; fi
Assuming thoses functions :
return_0() {
return 0
}
return_1() {
return 1
}
Then the following code :
if return_0; then
echo "we're in" # this will be displayed
fi
if return_1; then
echo "we aren't" # this won't be displayed
fi
if return_0 -a return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
Why I am getting into the last ifstatement ?
Aren't we supposed to be out of the condition with those 0 and 1 ?
-a is one of the options of the test command (which is also implemented by [ and [[). So you can't just use -a by itself. You probably want to use &&, which is a control operator token for an AND list.
if return_0 && return_1; then ...
You can use -a to tell test to "and" two different test expressions, like
if test -r /file -a -x /file; then
echo 'file is readable and executable'
fi
But this is equivalent to
if [ -r /file -a -x /file ]; then ...
which may be more readable because the brackets make the test part of the expression clearer.
See the Bash Reference Manual for further information on...
&&, see lists
if statements and the various test commands and keywords, see conditional constructs
When you execute
if return_0 -a return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
You execute the line return_0 -a return_1. This actually means that you pass -a and return_1 as arguments to return_0. If you want to have an and operation, you should make use of the && syntax.
if return_0 && return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
The useful information to understand this is:
AND and OR lists are sequences of one of more pipelines separated by the && and || control operators, respectively. AND and OR lists are executed with left associativity. An AND list has the form
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
An OR list has the form
command1 || command2
command2 is executed if and only if command1 returns a non-zero exit status. The return status of AND and OR lists is the exit status of the last command executed in the list.
Let's imagine we have this code (script.sh):
#!/bin/bash
set -e
f() {
echo "[f] Start" >&2
echo "f:before-false1"
echo "f:before-false2"
false
echo "f:after-false"
echo "[f] Fail! I don't want this executed" >&2
}
out=$(f)
The output:
$ bash myscript.sh
[f] Start
[f] Fail! I don't want this executed
I understand that $(...) starts a sub-shell where set -e is not propagated, so my question is: what's the idiomatic way to make this run as expected without too much clutter? I can see 3 solutions, none of which I like (nor I am actually sure they indeed work): 1) Add set -e to the start of f (and every other function in the app). 2) Run $(set -e && f). 3) Add ... || return 1 to every command that may fail.
It's not the prettiest solution, but it does allow you to emulate set -e for the current shell as well as any functions and subshells:
#!/bin/bash
# Set up an ERR trap that unconditionally exits with a nonzero exit code.
# Similar to -e, this trap is invoked when a command reports a nonzero
# exit code (outside of a conditional / test).
# Note: This trap may be called *multiple* times.
trap 'exit 1' ERR
# Now ensure that the ERR trap is called not only by the current shell,
# but by any subshells too:
# set -E (set -o errtrace) causes functions and subshells to inherit
# ERR traps.
set -E
f() {
echo "[f] Start" >&2
echo "f:before-false1"
echo "f:before-false2"
false
echo "f:after-false"
echo "[f] Fail! I don't want this executed" >&2
}
out=$(f)
Output (to stderr) if you call this script (exit code afterward will be 1) - note how the 2nd echo to stderr (>&2) is not printed, proving that the execution of false aborted the command substitution:
[f] Start
Note:
By design, set -e / trap ERR only respond to failures that aren't part of conditionals (see man bash, under the description of set (search for literal " set ["), for the exact rules, which changed slightly between Bash 3.x and 4.x).
Thus, for instance, f does NOT trigger the trap in the following commands: if ! f; then ..., f && echo ok; the following triggers the trap in the subshell (command substitution $(...), but not in the enclosing conditional ([[ ... ]]): [[ $(f) == 'foo' ]] && echo ok, so the script as a whole doesn't abort.
To exit a function / subshell explicitly in such cases, use something like || return 1 / || exit 1, or call the function / subshell separately, outside of a conditional first; e.g., in the [[ $(f) == 'foo' ]] case: res=$(f); [[ $res == 'foo' ]] - res=$(f) will then trigger the trap for the current shell too.
As for why the trap code may be invoked multiple times: In the case at hand, false inside f() first triggers the trap, and then, because the trap's exit 1 exits the subshell ($(f)), the trap is triggered again for the current shell (the one running the script).
Instead of command substitution, you should use process substitution to call your function so that set -e remains in effect:
mapfile arr < <(f) # call function f using process substitution
out="${arr[*]}" # convert array content into a string
declare -p out # check output
Output:
[f] Start
declare -- out="f:before-false1
f:before-false2
"
I need to differentiate two cases: ( …subshell… ) vs $( …command substitution… )
I already have the following function which differentiates between being run in either a command substitution or a subshell and being run directly in the script.
#!/bin/bash
set -e
function setMyPid() {
myPid="$(bash -c 'echo $PPID')"
}
function echoScriptRunWay() {
local myPid
setMyPid
if [[ $myPid == $$ ]]; then
echo "function run directly in the script"
else
echo "function run from subshell or substitution"
fi
}
echoScriptRunWay
echo "$(echoScriptRunWay)"
( echoScriptRunWay; )
Example output:
function run directly in the script
function run from subshell or substitution
function run from subshell or substitution
Desired output
But I want to update the code so it differentiates between command substitution and subshell. I want it to produce the output:
function run directly in the script
function run from substitution
function run from subshell
P.S. I need to differentiate these cases because Bash has different behavior for the built-in trap command when run in command substitution and in a subshell.
P.P.S. i care about echoScriptRunWay | cat command also. But it's new question for me which i created here.
I don't think one can reliably test if a command is run inside a command substitution.
You could test if stdout differs from the stdout of the main script, and if it does, boldly infer it might have been redirected. For example
samefd() {
# Test if the passed file descriptors share the same inode
perl -MPOSIX -e "exit 1 unless (fstat($1))[1] == (fstat($2))[1]"
}
exec {mainstdout}>&1
whereami() {
if ((BASHPID == $$))
then
echo "In parent shell."
elif samefd 1 $mainstdout
then
echo "In subshell."
else
echo "In command substitution (I guess so)."
fi
}
whereami
(whereami)
echo $(whereami)
I'd like to achieve the test with a cleaner, less ugly one-liner:
#!/bin/bash
test -d "$1" || (echo "Argument 1: '$1' is not a directory" 1>&2 ; exit 1) || exit 1
# ... script continues if $1 is directory...
Basically I am after something which does not duplicate the exit, and preferably does not spawn a sub-shell (and as a result should also look less ugly), but still fits in one line.
Without a subshell and without duplicating exit:
test -d "$1" || { echo "Argument 1: '$1' is not a directory" 1>&2 ; exit 1; }
You may also want to refer to Grouping Commands:
{}
{ list; }
Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The
semicolon (or newline) following list is required.