Bash $(false) is true as a function argument - bash

I created a function to echo T or F.
tf() {
$1 && echo "T" || echo "F"
}
It seems to work...
tf true # T
tf false # F
But then I want to use it for feedback when using the 'test' command. I pass it a test that should result in F for false:
tf $(test 4 -eq 3) # T ?
But it comes back with a T for true!
What is this? I thought $() would substitute the result (false) in its place!
So I check to see what happens without the function; everything else being equal:
$(test 4 -eq 3) && echo "T" || echo "F" # F
...and it's the correct answer; F for false. 4 does not equal 3.
So: why does command substitution of false become true when it is passed as an argument to a function?
Also I see that plain $(false) becomes true when it is passed as a function argument. But outside of a function, $(false) is correctly false:
tf $(false) # T ?
$(false) && echo "T" || echo "F" # F
How can I safely pass true-or-false-evaluating commands in to a function, and be sure that their true or false result will be dealt with correctly inside the body of the function? Is there some other formatting in which to wrap it?

Looks like you are mixing things up here.
test only returns an exit code of 0 (TRUE) or 1 (FALSE)
You can easily check it by running:
$(test 4 -eq 3)
echo $?
You are trying to provide this value to a function
tf $(test 4 -eq 3) which does not work because $(test 4 -eq 3) does not return a value, only an exit code.
When you are trying it out manually it works, because && and || operators are relying on exit code of previous command.
To make it work, you need to change the way you supply parameters to your function. For example like this (probably you'll want to split this up to a variable to make it readable):
tf $(test 4 -eq 3 && echo true || echo false)
Same happens with 'plain' $(false) as you mention below. When you execute true or false you will get an exit code of either 0 or 1. They will not make it to the function, but work well with && and || operators:
$(false) && echo "T" || echo "F"

Related

Consider a specific exit code not a failure and proceed

I found questions about ignoring errors in shell scripts, but they ignore any error, not just a specific one.
My use case is that in some cases my_command returns an exit code -3 (253), but that is not a real error and any &&-chained commands should execute. In case of a different exit code != 0, it should stop.
So basically I'm looking for something like
my_command || $(($?==253 ? true : false) && next_command
and I've looked at the different options of using ternary operators, but none of them seem to apply to my case.
What can I do?
There are some problems cmd1 || $(($?==253 ? true : false) && cmd2:
A ) is missing after false.
You don't want $(( ... )) but (( ... )). The former would execute the result of the expression (that is a number!) as a command. The latter just evaluates the expression and fails if the result is 0 and succeeds otherwise. Note that is the opposite of how exit codes work.
true and false are not commands here, but variables. If an undefined variable is used inside (( ... )) its value is always 0. Therefore the command ((... ? true : false)) always fails.
Here is want you could have written instead:
cmd1 || (($?==253 ? 1 : 0)) && cmd2
Test:
prompt$ true || (($?==253 ? 1 : 0)) && echo ok
ok
prompt$ false || (($?==253 ? 1 : 0)) && echo ok
prompt$ ( exit 253; ) || (($?==253 ? 1 : 0)) && echo ok
ok
However, the ternary operator isn't really needed here. The following would be equivalent:
cmd1 || (($?==253)) && cmd2
Despite that, an if would probably be better. If you don't want to use one inline, you can write a function for that:
# allow all exit codes given as the first argument
# multiple exit codes can be comma separated
allow() {
ok=",$1,"
shift
"$#"
[[ "$ok" = *,$?,* ]]
}
allow 0,253 cmd1 && cmd2
Or define a wrapper just for this one command
cmd1() {
command cmd1 "$#" || (( $? == 253 ))
}
cmd1 && cmd2
The code you have evaluates to a number (once you fix the missing closing parenthesis), which is not what you want; but you can refactor it slightly if you want to maintain the same sort of flow, at the expense of a subshell.
my_command || ( exit $(($?==253)) ) && next_command
Probably a less expensive and, all things counted, both more obvious and elegant solution is the trivial
my_command || [ $? == 253 ] && next_command
Um, like this?
my_command; (($?==0|$?==253)) && next_command

Compound if / logical XOR bash bug?

#!/bin/bash
function logic_test()
{
left_bracket=$1
right_bracket=$2
if [[ ($left_bracket || $right_bracket) && ! ($left_bracket && $right_bracket) ]]
then
errEcho "Input error: insertIntoConfigFile arg1 does not contain matching []."
else
errEcho "Passed"
fi
}
logic_test true true
logic_test true false
logic_test false true
logic_test false false
Expected results, as per XOR's functioning:
Passed
Input Error
Input Error
Passed
Actual results in 4.3.11(1)-release
Passed
Passed
Passed
Passed
Am I failing to see something trivial in my XOR implementation, or is the if logic buggy? If it is a bug, how do I go about submitting said bug?
I'm thinking this might be a bug...if I change the if to
if [[ ($left_bracket && !$right_bracket) || (!$left_bracket && $right_bracket) ]]
then all 4 outputs become "input error"
You can use set -xv to turn on debugging. It will show you the problem: bash interprets the things in [[ .. ]] as strings and tests them with -n. None of them is emtpy, so it works as 'true true' everytime.
To fix it, just remove the [[ and ]]. Bash will correctly interpret true and false as commands to run and use their exit code in the logic.

What is the correct way to use the bash if else && || shortcut?

I have come across some strange behavious (well probably not strange but I don't understand it !)
I want to write some if / e,se statements using the bash shorthand syntax:
[[ 1 -eq 1 ]] && echo "true" || echo "false"
The output of the above code gives:
true
Now the code above actually works fine. But this next code does not:
[[ 1 -eq 1 ]] && infMsg "true" || infMsg "false"
infMsg is just a function. The output from the above code gives:
true
false
I only want it to say 'true'.
Is there something I am missing in the way the && || syntax handles exit codes from functions ?
I suspect your exit code for 'infMsg' is not 0 (success).
If we break down the first piece of code:
[[ 1 -eq 1 ]] && echo "true" || echo "false"
What's happening is:
The [[ 1 -eq 1 ]] && echo "true" portion of code is evaluated first since && has a higher precedence than || (more info[here1)
The first half of that returns true. Then, since the first half was true, the second half executes the echo "true" statement. (&& -> If part one is true, run the second part)
Now we have the || echo "false". This part actually doesn't get executed since the first half (echo "true") returned true since the echo printed successfully ((||-> If part one is false, run the second part).).
Now if we break down the second piece of code:
[[ 1 -eq 1 ]] && infMsg "true" || infMsg "false"
What's most likely happening is:
[[ 1 -eq 1 ]] && infMsg "true" is evaluated. The first side is true. Then, since the first half was true, the second half executes the infMsg "true" statement. (&& -> If part one is true, run the second part).
Now we have || infMsg "false". If the infMsg "true" from the previous command didn't return a status code that is interpreted as success (0), the first half is interpreted as false, triggering the infMsg "false"((||-> If part one is false, run the second part).)..
The important info to pull away here:
|| only runs the second half if the first half is FALSE.
&& only run the second half if the first half is TRUE.

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 boolean expression and its value assignment

Is there a way to to evaluate a boolean expression and assign its value to a variable?
In most of the scripting languages there is way to evaluates e.g
//PHS
$found= $count > 0 ; //evaluates to a boolean values
I want similar way to evaluate in bash:
BOOL=[ "$PROCEED" -ne "y" ] ;
This is not working and tried other way but could not get a boolean value. IS there a way to
do this WITHOUT using IF ?
You could do:
[ "$PROCEED" = "y" ] ; BOOL=$?
If you're working with set -e, you can use instead:
[ "$PROCEED" = "y" ] && BOOL=0 || BOOL=1
BOOL set to zero when there is a match, to act like typical Unix return codes. Looks a bit weird.
This will not throw errors, and you're sure $BOOL will be either 0 or 1 afterwards, whatever it contained before.
I would suggest:
[ "$PROCEED" = "y" ] || BOOL=1
This has the advantage over checking $? that it works even when set -e is on. (See writing robust shell scripts.)
Rather than using ... && BOOL=0 || BOOL=1 suggested in the currently-accepted answer, it's clearer to use true and false.
And since this question is about bash specifically (not POSIX shell), it's also better to use [[ instead of [ (see e.g. 1 and 2), which allows using == instead of =.
So if you had to use a one-liner for something like this in bash, the following would be better:
[[ "$PROCEED" == "y" ]] && should_proceed=true || should_proceed=false
Then you can use the derived variable ergonomically in boolean contexts...
if $should_proceed; then
echo "Proceeding..."
fi
...including with the ! operator:
if ! $should_proceed; then
echo "Bye for now."
exit 0
fi
Assignment:
found=$((count > 0))
For a boolean test:
BOOL=$(test "$PROCEED" = y && echo true || echo false)
In general, a
x=$(...)
assigns the output of ... to the variable x. The y does not need quotes, because it contains nothing which needs to be masked.
A -ne is used for arithmetic comparison; see help test for an overview and quick reminder.
As explained in the accepted answer, the return value seems odd as true will return 0 and false 1. To make it easier to understand:
#!/bin/bash
test=$( [[ $1 == "y" ]]; echo $(($? == 0)) )
echo "$test"
# It will print "1", otherwise "0".
# To use it in conditions:
if [ $test ]; then
...
fi
Another way is:
test=$( [[ $1 == "y" ]] && echo "true" || echo "false" )
# In this case `[]` are not required:
if $test; then
...
fi

Resources