I want to try simple script
flag=false
while !$flag
do
read x
if [ "$x" -eq "true" ]
then
flag=true
fi
echo "${x} : ${flag}"
done
But when I run it, if I type true, I will see that x="true" and flag="true", but the cycle doesn’t end. What is wrong with the script? How can I properly invert a boolean variable?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
There are two errors in your script. The first is that you need a space between ! and $flag, otherwise the shell looks for a command called !$flag. The second error is that -eq is for integer comparisons, but you’re using it on a string. Depending on your shell, either you’ll see an error message and the loop will continue forever because the condition [ "$x" -eq "true" ] cannot be true, or every non-integer value will be treated as 0 and the loop will exit if you enter any string (including false) other than a number different from 0.
While ! $flag is correct, it’s a bad idea to treat a string as a command. It would work, but it would be very sensitive to changes in your script, since you’d need to make sure that $flag can never be anything but true or false. It would be better to use a string comparison here, like in the test below.
flag=false
while [ "$flag" != "true" ]
do
read x
if [ "$x" = "true" ]
then
flag=true
fi
echo "${x} : ${flag}"
done
There’s probably a better way to express the logic you’re after. For example, you could make an infinite loop and break it when you detect the termination condition.
while true; do read -r x if [ "$x" = "true" ]; then break; fi echo "$x: false" done
Method 2
While there are no Boolean variables in Bash, it is very easy to emulate them using arithmetic evaluation.
flag= # False flag=0 # False flag=1 # True (actually any integer number != 0 will do, but see remark below about toggling) flag="some string" # Maybe False (make sure that the string isn't interpreted as a number) if ((flag)) # Test for True then : # do something fi if ! ((flag)) # Test for False then : # do something fi flag=$((1-flag)) # Toggle flag (only works when flag is either empty or unset, 0, 1, or a string which doesn't represent a number)
This also works in ksh. I wouldn’t be surprised if it works in all POSIX-compliant shells, but haven’t checked the standard.
Method 3
If you can be absolutely sure that the variable will contain either 0 or 1, you can use the bitwise XOR-equal operator to flip between the two values:
$ foo=0 $ echo $foo 0 $ ((foo ^= 1)) $ echo $foo 1 $ ((foo ^= 1)) $echo $foo 0
Method 4
This works for me:
flag=false
while [[ "$flag" == "false" ]]
do
read x
if [[ "$x" == "true" ]]
then
flag=true
fi
echo "${x} : ${flag}"
done
But, actually, what you should do is replace your while with:
while ! $flag
Method 5
until is short for while ! (and until, contrary to ! is Bourne), so you can do:
flag=false
until "$flag"
do
IFS= read -r x
if [ "$x" = true ]
then
flag=true
fi
printf '%sn' "$x : $flag"
done
Which should be the same as:
flag=false
while ! "$flag"
do
IFS= read -r x
if [ "$x" = true ]
then
flag=true
fi
printf '%sn' "$x : $flag"
done
You could also write it as:
until IFS= read -r x printf '%sn' "$x" [ "$x" = true ] do continue done
The part between the until/while and do doesn’t have to be a single command.
! is a keyword in the POSIX shell syntax. It needs to be delimited, a token separated from what follows and be the first token that preceded a pipeline (! foo | bar negates the pipeline and foo | ! bar is invalid though some shells accept it as an extension).
!true is interpreted as the !true command which is unlikely to exist. ! true should work. !(true) should work in POSIX-compliant shells as that ! is delimited as it’s followed by a ( token, but in practice it doesn’t work in ksh / bash -O extglob where it conflicts with the !(pattern) extended glob operator nor zsh (except in sh emulation) where it conflicts with glob(qualifiers) with bareglobqual and glob(group) without.
Method 6
[ ${FOO:-0} == 0 ] && FOO=1 || FOO=0
or even:
[ ${FOO:-false} == false ] && FOO=true || FOO=false
should do the job
Method 7
There is no concept of a boolean variable in the shell.
Shell variables could only be text (an string), and, in some cases, that text may be interpreted as an integer (1, 0xa, 010, etc. ).
Therefore, a flag=true implies no truthfulness or falseness to the shell at all.
String
What could be done is either a string comparison [ "$flag" == "true" ] or use the variable content in some command and check its consequences, like execute true (because there are both an executable called true and one called false) as a command and check if the exit code of that command is zero (successful).
$flag; if [ "$?" -eq 0 ]; then ... fi
Or shorter:
if "$flag"; then ... fi
If the content of a variable is used as a command, a ! could be used to negate the exit status of the command, if an space exists between both (! cmd), as in:
if ! "$flag"; then ... fi
The script should change to:
flag=false
while ! "$flag"
do
read x
if [ "$x" == "true" ]
then
flag=true
fi
echo "${x} : ${flag}"
done
Integer
Use numeric values and Arithmetic Expansions.
In this case, the exit code of $((0)) is 1 and the exit code of $((1)) is 0.
In bash, ksh and zsh the arithmetic could be carried out inside a ((..)) (note that the starting $ is missing).
flag=0; if ((flag)); then ... fi
A portable version of this code is more convoluted:
flag=0; if [ "$((flag))" -eq 0 ]; then ... fi # test for a number flag=0; if [ "$((flag))" == 0 ]; then ... fi # test for the string "0"
In bash/ksh/zsh you could do:
flag=0
while ((!flag))
do
read x
[ "$x" == "true" ] && flag=1
echo "${x} : ${flag}"
done
Alternatively
You can “Invert a boolean variable” (provided it contains a numeric value) as:
((flag=!flag))
That will change the value of flag to either 0 or 1.
Note: Please check for errors in https://www.shellcheck.net/ before posting your code as a question, many times that is enough to find the problem.
Method 8
You can use this function:
function invert_boolean() {
if [[ "$1" == "true" ]]; then
echo "false";
else
echo "true";
fi;
}
And use it like this:
VAR=false;
INVERTED=$(invert_boolean $VAR);
echo "INVERTED: $INVERTED";
# prints INVERTED: true
This works for both strings (eg. "true") and booleans (eg. false).
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0