I saw the following bash statement being used on the internet:
PYTHON_BIN_PATH=$(which python || which python3 || true)
I understand that if which python fails, then which python3 will be executed, but I don't understand the purpose of the true at the end of the condition. Any idea?
try running:(Note the bla)
which python_bla || which python3_bla_bla || true
echo $?
0
You will get RC=0. It means it a construct to successfully proceed to next command. Here we know python_bla or python3_bla_bla does not exist,but still command gave rc=0
Example: Check the RC of following three commands, I have changed the spelling of date command to incorrect but true is causing RC to remain 0.
date;echo $?
Thu Nov 9 01:40:44 CST 2017
0
datea;echo $?
If 'datea' is not a typo you can use command-not-found to lookup the package that contains it, like this:
cnf datea
127
datea||true;echo $?
If 'datea' is not a typo you can use command-not-found to lookup the package that contains it, like this:
cnf datea
0
Note: You can also use : operator instead of true to get the same results.Example:
command || :
To be more rigorous I guess.
for example:
if aaa isn't an existed global binary file.
After executing which aaa,you can execute echo $? and the result is 1.
But if you execute which aaa | true the result will be 0.
Simple. It will check either your system has out of the box python(version of python which has come with your O.S) or it has python version 3 in it. It will also confirm python's executable path too, you could simply print variable named PYTHON_BIN_PATH by doing echo "$PYTHON_BIN_PATH" and could check it too once.
EDIT: Here is a simple example to understand. Let's say we have a variable named val with NULL value and we do this:
echo $val || true
Output will be NULL then as it's previous command didn't give any output.
Let's say we have val=4 then we run it as follows.
val="4"
echo $val || true
4
The idea is to set the PYTHON_BIN_PATH to nothing if both which fails.
Visibly there is no difference between
PYTHON_BIN_PATH=$(which python || which python3 || true)
and
PYTHON_BIN_PATH=$(which python || which python3)
But running the command alone makes things more obvious. Suppose that python doesn't exist on the system.
$(which python || which python3)
echo $? #Returning the exit status of the previous command
1 # A non zero status generally means the previous statement failed
$(which python || which python3 || true)
echo $?
0
In short, using true at the end always give a zero exit status for
$( command || true )
Related
I am running the following script using tcsh. In my while loop, I'm running a C++ program that I created and will return a different exit code depending on certain things. While it returns an exit code of 0, I want the script to increment counter and run the program again.
#!/bin/tcsh
echo "Starting the script."
set counter = 0
while ($? == 0)
# counter ++
./auto $counter
end
I have verified that my program is definitely returning with exit code = 1 after a certain point. However, the condition in the while loop keeps evaluating to true for some reason and running.
I found that if I stick the following line at the end of my loop and then replace the condition check in the while loop with this new variable, it works fine.
while ($return_code == 0)
# counter ++
./auto $counter
set return_code = $?
end
Why is it that I can't just use $? directly? Is another operation underneath the hood performed in between running my custom program and checking the loop condition that's causing $? to change value?
That is peculiar.
I've altered your example to something that I think illustrates the issue more clearly. (Note that $? is an alias for $status.)
#!/bin/tcsh -f
foreach i (1 2 3)
false
# echo false status=$status
end
echo Done status=$status
The output is
Done status=0
If I uncomment the echo command in the loop, the output is:
false status=1
false status=1
false status=1
Done status=0
(Of course the echo in the loop would break the logic anyway, because the echo command completes successfully and sets $status to zero.)
I think what's happening is that the end that terminates the loop is executed as a statement, and it sets $status ($?) to 0.
I see the same behavior with both tcsh and bsd-csh.
Saving the value of $status in another variable immediately after the command is a good workaround -- and arguably just a better way of doing it, since $status is extremely fragile, and will almost literally be clobbered if you look at it.
Note that I've add a -f option to the #! line. This prevents tcsh from sourcing your init file(s) (.cshrc or .tcshrc) and is considered good practice. (That's not the case for sh/bash/ksh/zsh, which assign a completely different meaning to -f.)
A digression: I used tcsh regularly for many years, both as my interactive login shell and for scripting. I would not have anticipated that end would set $status. This is not the first time I've had to find out how tcsh or csh behaves by trial and error and been surprised by the result. It is one of the reasons I switched to bash for interactive and scripting use. I won't tell you to do the same, but you might want to read Tom Christiansen's classic "csh.whynot".
Slightly shorter/simpler explanation:
Recall that with tcsh/csh EACH command (including shell builtin) return a status. Therefore $? (aliases to $status) is updated by 'if' statements, 'for' loops, assignments, ...
From practical point of view, better to limit the usage of direct use of $? to an if statement after the command execution:
do-something
if ( $status == 0 )
...
endif
In all other cases, capture the status in a variable, and use only that variable
do-something
something_status=$?
if ( $something_status == 0 )
...
endif
To expand on the $status, even a condition test in an if statement will modify the status, therefore the following repeated test on $status will not never hit the '$status == 5', even when do-something will return status of 5
do-something
if ( $status == 2 ) then
echo FOO
else if ( $status == 5 ) then
echo BAR
endif
I know the title is a bit weird, but I'm not sure how to better word it.
I'm using $(return &> /dev/null) to detect if a file is sourced or not.
I know that this method is not 100% reliable, but it has worked for me without issues before now. I've figured out how to work around the problem, but I can't figure out why this is happening.
Edit: This is for an internal company project and is not intended to be POSIX compliant or portable to other shells.
I've tried this on three different systems and had the same results:
Redhat 6.9; $BASH_VERSION=4.1.2(1)-release
Mint 18.2; $BASH_VERSION=4.3.48(1)-release
Arch; $BASE_VERSION=4.4.23(1)-release
If $? is 1 (false) when $(return &> /dev/null) the return code is reversed.
$ hr | cat test_0source - test_0source.sh; hr; ./test_0source
#!/bin/bash
# test_0source
false
test_0source.sh
false
source test_0source.sh
true
test_0source.sh
true
source test_0source.sh
========================================
#!/bin/bash
# test_0source.sh
# shellcheck disable=SC2091
$(return &> /dev/null)
echo "$?"
========================================
1
1
1
0
I expected to be seeing
1
0
1
0
My workaround is to add true right before the return check. While this gives me the correct results, adding false instead of true cause the return check to always return 1.
I realize I'm missing something basic here, but I'm not seeing it. Why is this doing this?
Edit: I had an incorrect value for $? in my initial explanation above.
Update:
First, using $(return 0 &> /dev/null) resolves the problem, thanks to #ondre-k.
Ondre also pointed out that comparing BASH_SOURCE against $0 is a more idiomatic way to do this:
$ hr | cat test_0bs - test_0bs.sh; hr; test_0bs | less
#!/bin/bash
# test_0bs
false
test_0bs.sh
hr
false
source test_0bs.sh
hr
true
test_0bs.sh
hr
true
source test_0bs.sh
========================================
#!/bin/bash
# test_0bs.sh
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
echo "We are not being sourced."
else
echo "We are being sourced."
fi
========================================
We are not being sourced.
========================================
We are being sourced.
========================================
We are not being sourced.
========================================
We are being sourced.
I suspect there may be an edge case or two around this, but it also seems to me that these cases will be less of a concern.
What you are doing is basically "abusing" the fact that return can only happen from function or a sourced script and assuming it yields 0 when a return was possible (we're sourced) and 1 if not, while suppressing the error output. You also do that in a subshell. Oddly enough, bash is still OK with placement of the return but does not return from a sourced script and keeps going. So far, so god.
The problem is, that "naked" (w/o explicit value specified) return, just like exit propagate last seen return code (return code of the command immediately preceding it).
In other words false; return would be the same thing as return 1. You can also try this out by replacing true and false by (exit 255) and see what happens. The 1 you are seeing for return following a false is not the one of "error: you cannot say return now" as your test is looking for, but just an ordinary return that has returned the last from the command preceding it (false). This difference would also become obvious if you've dropped the stderr redirection.
TL;DR for this construct to work as you expected, change it to return 0.
I hope I have not missed some corner case, but this should work as an alternative with bash by comparing name of the executed script and a source file. [[ "${BASH_SOURCE}" = ${0} ]] evaluates to 0 if file has not been sourced and 1 if it has. Or replace = with != to get same meaning of values as in the return case above.
I have a test script like this:
asdf() {
return 1
}
asdf && echo 123 && exit
echo 321
321 is outputed instead of 123
Why is that?
echo 123 will only be executed iff asdf succeeds as you have tacked the short circuit operator -- && after asdf; now you have return-ed from the function asdf with return value 1, which in turn becomes the exit status of asdf, so the command asdf is considered failed, hence echo 123 (and of course exit) is never run.
There is no such condition on echo 321, hence it is executed in the usual manner.
The && operator is a logical AND. It does lazy evaluation : if the statement to the left evaluates to false (i.e. non-zero return code), then applying the logical AND will still evaluate to false anyway, so evaluating the right part is useless (in the logical predicate sense).
Since your function returns 1 (false as far as Bash is concerned), everything to the right is ignored, your exit is not reached, and the echo statement is executed.
If you want to force execution of all commands on that line, use this instead:
{ asdf ; echo 123 ; exit ; }
A command list like this will always evaluate every one of them, irrespective of their return code. Please note that the last semi-colon is mandatory.
I have a rather complex series of commands in bash that ends up returning a meaningful exit code. Various places later in the script need to branch conditionally on whether the command set succeed or not.
Currently I am storing the exit code and testing it numerically, something like this:
long_running_command | grep -q trigger_word
status=$?
if [ $status -eq 0 ]; then
: stuff
else
: more code
if [ $status -eq 0 ]; then
: stuff
else
For some reason it feels like this should be simpler. We have a simple exit code stored and now we are repeatedly typing out numerical test operations to run on it. For example I can cheat use the string output instead of the return code which is simpler to test for:
status=$(long_running_command | grep trigger_word)
if [ $status ]; then
: stuff
else
: more code
if [ $status ]; then
: stuff
else
On the surface this looks more straight forward, but I realize it's dirty.
If the other logic wasn't so complex and I was only running this once, I realize I could embed it in place of the test operator, but this is not ideal when you need to reuse the results in other locations without re-running the test:
if long_running_command | grep -q trigger_word; then
: stuff
else
The only thing I've found so far is assigning the code as part of command substitution:
status=$(long_running_command | grep -q trigger_word; echo $?)
if [ $status -eq 0 ]; then
: stuff
else
Even this is not technically a one shot assignment (although some may argue the readability is better) but the necessary numerical test syntax still seems cumbersome to me. Maybe I'm just being OCD.
Am I missing a more elegant way to assign an exit code to a variable then branch on it later?
The simple solution:
output=$(complex_command)
status=$?
if (( status == 0 )); then
: stuff with "$output"
fi
: more code
if (( status == 0 )); then
: stuff with "$output"
fi
Or more eleganter-ish
do_complex_command () {
# side effects: global variables
# store the output in $g_output and the status in $g_status
g_output=$(
command -args | commands | grep -q trigger_word
)
g_status=$?
}
complex_command_succeeded () {
test $g_status -eq 0
}
complex_command_output () {
echo "$g_output"
}
do_complex_command
if complex_command_succeeded; then
: stuff with "$(complex_command_output)"
fi
: more code
if complex_command_succeeded; then
: stuff with "$(complex_command_output)"
fi
Or
do_complex_command () {
# side effects: global variables
# store the output in $g_output and the status in $g_status
g_output=$(
command -args | commands
)
g_status=$?
}
complex_command_output () {
echo "$g_output"
}
complex_command_contains_keyword () {
complex_command_output | grep -q "$1"
}
if complex_command_contains_keyword "trigger_word"; then
: stuff with "$(complex_command_output)"
fi
If you don't need to store the specific exit status, just whether the command succeeded or failed (e.g. whether grep found a match), I's use a fake boolean variable to store the result:
if long_running_command | grep trigger_word; then
found_trigger=true
else
found_trigger=false
fi
# ...later...
if ! $found_trigger; then
# stuff to do if the trigger word WASN'T found
fi
#...
if $found_trigger; then
# stuff to do if the trigger WAS found
fi
Notes:
The shell doesn't really have boolean (true/false) variables. What's actually happening here is that "true" and "false" are stored as strings in the found_trigger variable; when if $found_trigger; then executes, it runs the value of $found_trigger as a command, and it just happens that the true command always succeeds and the false command always fails, thus causing "the right thing" to happen. In if ! $found_trigger; then, the "!" toggles the success/failure status, effectively acting as a boolean "not".
if long_running_command | grep trigger_word; then is equivalent to running the command, then using if [ $? -ne 0 ]; then to check its exit status. I find it a little cleaner, but you have to get used to thinking of if as checking the success/failure of a command, not just testing boolean conditions. If "active" if commands aren't intuitive to you, use a separate test instead.
As Charles Duffy pointed out in a comment, this trick executes data as a command, and if you don't have full control over that data... you don't have control over what your script is going to do. So never set a fake-boolean variable to anything other than the fixed strings "true" and "false", and be sure to set the variable before using it. If you have any nontrivial execution flow in the script, set all fake-boolean variables to sane default values (i.e. "true" or "false") before the execution flow gets complicated.
Failure to follow these rules can lead to security holes large enough to drive a freight train through.
Why don't you set flags for the stuff that needs to happen later?
cheeseballs=false
nachos=false
guppies=false
command
case $? in
42) cheeseballs=true ;;
17 | 31) cheeseballs=true; nachos=true; guppies=true;;
66) guppies=true; echo "Bingo!";;
esac
$cheeseballs && java -crash -burn
$nachos && python ./tex.py --mex
if $guppies; then
aquarium --light=blue --door=hidden --decor=squid
else
echo SRY
fi
As pointed out by #CharlesDuffy in the comments, storing an actual command in a variable is slightly dubious, and vaguely triggers Bash FAQ #50 warnings; the code reads (slightly & IMHO) more naturally like this, but you have to be really careful that you have total control over the variables at all times. If you have the slightest doubt, perhaps just use string values and compare against the expected value at each junction.
[ "$cheeseballs" = "true" ] && java -crash -burn
etc etc; or you could refactor to some other implementation structure for the booleans (an associative array of options would make sense, but isn't portable to POSIX sh; a PATH-like string is flexible, but perhaps too unstructured).
Based on the OP's clarification that it's only about success v. failure (as opposed to the specific exit codes):
long_running_command | grep -q trigger_word || failed=1
if ((!failed)); then
: stuff
else
: more code
if ((!failed)); then
: stuff
else
Sets the success-indicator variable only on failure (via ||, i.e, if a non-zero exit code is returned).
Relies on the fact that variables that aren't defined evaluate to false in an arithmetic conditional (( ... )).
Care must be taken that the variable ($failed, in this example) hasn't accidentally been initialized elsewhere.
(On a side note, as #nos has already mentioned in a comment, you need to be careful with commands involving a pipeline; from man bash (emphasis mine):
The return status of a pipeline is the exit status of the last command,
unless the pipefail option is enabled. If pipefail is enabled, the
pipeline's return status is the value of the last (rightmost) command
to exit with a non-zero status, or zero if all commands exit successfully.
To set pipefail (which is OFF by default), use set -o pipefail; to turn it back off, use set +o pipefail.)
If you don't care about the exact error code, you could do:
if long_running_command | grep -q trigger_word; then
success=1
: success
else
success=0
: failure
fi
if ((success)); then
: success
else
: failure
fi
Using 0 for false and 1 for true is my preferred way of storing booleans in scripts. if ((flag)) mimics C nicely.
If you do care about the exit code, then you could do:
if long_running_command | grep -q trigger_word; then
status=0
: success
else
status=$?
: failure
fi
if ((status == 0)); then
: success
else
: failure
fi
I prefer an explicit test against 0 rather than using !, which doesn't read right.
(And yes, $? does yield the correct value here.)
Hmm, the problem is a bit vague - if possible, I suggest considering refactoring/simplify, i.e.
function check_your_codes {
# ... run all 'checks' and store the results in an array
}
###
function process_results {
# do your 'stuff' based on array values
}
###
create_My_array
check_your_codes
process_results
Also, unless you really need to save the exit code then there is no need to store_and_test - just test_and_do, i.e. use a case statement as suggested above or something like:
run_some_commands_and_return_EXIT_CODE_FROM_THE_LAST_ONE
if [[ $? -eq 0 ]] ; then do_stuff else do_other_stuff ; fi
:)
Dale
How can I check the return value (true/false) of bash command in an if statement in Ruby. I want something like this to work,
if ("/usr/bin/fs wscell > /dev/null 2>&1")
has_afs = "true"
else
has_afs = "false"
end
It complains with the following error meaning, it will always return true.
(irb):5: warning: string literal in condition
What's the correct syntax ?
UPDATE :
/usr/bin/fs wscell
looks for afs installed and running condition. It will throw a string like this,
This workstation belongs to cell <afs_server_name>
If afs is not running, the command exits with status 1
You want backticks rather than double-quotes. To check a program's output:
has_afs = `/usr/bin/fs wscell > /dev/null 2>&1` == SOMETHING ? 'true' : 'false'
Where SOMETHING is filled in with what you're looking for.
You should probably use system() or Backticks and then check the exit status of the command ($?.exitstatus):
Heres a good quicktip read: http://rubyquicktips.com/post/5862861056/execute-shell-commands)
UPDATE:
system("/usr/bin/fs wscell > /dev/null 2>&1") # Returns false if command failed
has_afs = $?.exitstatus != 1 # Check if afs is running