As far as I had known, [[ and [ can be expected to behave mostly the same, taking into account a few extra features [[ has. But recently I noticed a discrepancy in how bash treats octal expansions:
$ b=010; echo $((b))
8
$ [[ $b -eq 8 ]]; echo $?
0
but
$ [ $b -eq 8 ]; echo $?
1
$ test $b -eq 8; echo $?
1
$ [ $b -eq 10 ]; echo $?
0
Why does the latter expression drop the auto octal conversion? Expressions like -eq are "Arithmetic" according to help test in Bash and the Bash Reference Manual, and further according to the next section of the reference manual constants with a leading zero can be treated as octal.
POSIX sh is a little less clear on the subject: Even though POSIX arithmetic expressions still expand leading-zero integers to their octal value, it refers to -eq expressions in test as algebraic, not arithmetic.
Is there any documentation or evidence to suggest that bash makes a distinction between [[ and [ for octal expansion on purpose, or is it just an accidental feature?
[[ is known as the extended test command and behaves as ksh88, you can find an elaboration here:
http://tldp.org/LDP/abs/html/testconstructs.html#DBLBRACKETS
Also if you need to be sure of the base in bash you can use the # base operator like this:
b=010; echo $((10#$b))
10
b=10; echo $((8#$b)
8
b=013; echo $((8#$b))
11
Related
I am getting errors in using bitwise & operators in /bin/sh. I can make it work in bash, but the script which will execute the code starts in regular shell, so I need to make it work in plain regular shell. I need to simply check if a certain bit is set e.g. 7th bit
Following code work in bash, but not in /bin/sh
#!/bin/bash
y=93
if [ $((($y & 0x40) != 0)) ]; then
echo bit 7 is set
fi
I tried above in /bin/sh by removing arithmatic expansion
#!/bin/sh
y=93
val=`expr $y & 0x40`
if [ $val != 0 ]; then
echo worked 1
fi
Can someone suggest how bitwise operator can be use in plain regular shell?
You can use division and modulo operations to check for a particular bit:
if [ `expr \( $y / 64 \) % 2` -eq 1 ]; then
echo bit 6 is set
fi
I think the problem is that you're using the string returned by your arithmetic expansion, and not the exit code. You probably should do the test for equal to 0 on the outside like
if [ "$((y & 0x40))" -ne 0 ]; then
otherwise $(((y & 0x40) != 0)) is returning a string, and it will always be non-empty so the truth test will always pass.
Based on Alnitak answer, I'd do:
if expr \( $y / 128 \) % 2 > /dev/null; then
echo bit 7 is set
fi
It's just a little more concise.
Notice that: 2^7=128
This question already has answers here:
Shell equality operators (=, ==, -eq)
(4 answers)
Closed 7 years ago.
I can see some article like this where it is mentioned that "-eq" is used to compare the integers, but this doesn't say that we can't use "==" for comparing the integers.
I verified this on bash shell locally and "==" is working fine.
So can anyone let me help to understand which is better option to use, if "-eq" then why ?
To compare integers, use -eq. The difference is that == compares string value while -eq compares numeric value. Here is an example where they yield different results:
$ [ 03 = 3 ] ; echo $?
1
$ [ 03 -eq 3 ] ; echo $?
0
It is the same using [[:
$ [[ 03 == 3 ]] ; echo $?
1
$ [[ 03 -eq 3 ]] ; echo $?
0
As a number, 03 is equal to 3. But, as a string 03 and 3 are different.
Summary: To compare numeric values for equality, use -eq
It depends on the context. In a math context (which is preferable if you're writing your scripts specifically for bash), use ==.
(( foo == 3 )) ## foo = 3 would be an assignment here, and foo -eq 3 an error
A math context is also present in other situations -- for instance, indexing into a non-associative array, making == preferred but -eq illegal in the below contrieved example:
foo=( yes no )
bar=3
echo "${foo[bar == 3 ? 0 : 1]}" # echoes "yes"
In [[ ]], use -eq.
[[ $foo -eq 3 ]] ## $foo = 3 would be a string comparison here; $foo == 3 is a synonym,
## but bad finger-memory to have if one wants to write POSIX code
## elsewhere.
In [ ], use -eq -- and also quote:
[ "$foo" -eq 3 ] ## = would be a valid string comparison here; == would be a
## POSIX-incompatible string comparison here; -eq is correct.
Can anyone see what I did wrong here? I keep getting the following error message: [[: not found
read INPUT
if [[ "$INPUT" -ge 1 ]] && [[ "$INPUT" -le 10 ]]; then
Do something
else
printf "Please enter a value between 1 and 10"
fi
[[ is not available in scripts which start with #!/bin/sh, or which are started with sh yourscript. Start your script with #!/bin/bash if you want to use it.
See also http://mywiki.wooledge.org/BashGuide/Practices#Choose_Your_Shell
If you are going to use bash, by the way, there's a better syntax for numeric comparisons:
if (( input >= 1 && input <= 10 )); then ...
Note that lower-case variable names are preferred for local use -- all-upper-case names are reserved for environment variables and shell builtins.
If you're not going to use bash, use the POSIX test operator:
if [ "$input" -ge 1 ] && [ "$input" -le 10 ]; then ...
Note that when using [ ] correct quoting is essential, whereas with [[ ]] it is often superfluous; also, [ ] is missing some extensions such as pattern-matching and regular-expression operators.
It's complicated:
First, there are three separate ways of constructing your if statement. Each way has its own unique syntax on how to join two booleans. (Actually, there are four ways since one way allows you to use list operators).
A little background...
The if command is a compound command built into the shell. The if command executes the commands following the if. If that command returns a zero value, the if statement is considered true and the then clause executes. Otherwise, if it exists, the else clause will execute. Remember, the if is just a command. You can do things like this:
if ! mv "$foo" "$bar"
then
echo "I can't move $foo to $bar"
exit 2
fi
What we need is a command to do some testing for us. If the test succeeds, that test command returns an exit code of zero. If not, it returns a non-zero exit code. Then, it could be used with the if command!
The test command (Yes, there's really one!).
The [ is an alias for the test command which was created to allow you to test files, strings, and numbers for the if statement. (This is now a built in command in Bash, but its roots are actually part of /bin/test and /bin/[). These are the same:
if test "$foo" -eq "$bar"
then
...
fi
and
if [ "$foo" -eq "$bar" ]
then
...
fi
The test command (if you read the manpage has a -a And test and a -o Or test. You could have done:
if [ "$INPUT" -ge 1 -a "$INPUT" -le 10 ]
then
....
fi
This is a single test statement with three test parameters (-ge, -a, and -le).
Using List Operators
This isn't the only way to do a compound boolean test. The Bash shell has two list operators: && and ||. The list operators go in between two commands. If you use && and the left hand command returns a non-zero exit code, the right hand command is not executed, and the entire list returns the exit value of the left-hand command. If you use ||, and the left hand command succeeds, the right hand command is not executed, and the entire list returns a zero exit value. If the first command returns a non-zero exit value, the right-hand command is executed, and the entire list returns the exit value of the right-hand command.
That's why you can do things like this:
[ $bar -eq 0 ] || echo "Bar doesn't have a zero value"!
Since [ ... ] is just a command that returns a zero or non-zero value, we can use these list operators as part of our test:
if [ "$INPUT" -ge 1 ] && [ "$INPUT" -le 10 ]
then
...
fi
Note that this is two separate tests and are separated by a && list operator.
Bash's Special [[ compound command
In Kornshell, Zsh, and Bash, there are special compound commands for testing. These are the double square brackets. They appear to be just like the single square brackets command, but because they're compound commands, parsing is affected.
For example:
foo="This has white space"
bar="" #No value
if [ ! $foo = $bar ] # Doesn't work!
then
The shell expands $foo and $bar and the test will become:
if [ This has white space = ]
which just doesn't work. However,
if [[ $foo != $bar ]]
works fine because of special parsing rules. The double brackets allow you to use parentheses for grouping and && and || as boolean operators. Thus:
if [[ $INPUT -ge 1 && $INPUT -le 10 ]]
then
...
fi
Note that the && appears inside a single set of double square brackets. (Note there's no need for quotation marks)
Mathematical Boolean Expression
Bash has built in mathematical processing including mathematical boolean expressions. If you put something between double parentheses, Bash will evaluate it mathematically:
if (( $INPUT >= 1 && $INPUT <= 10 ))
then
...
fi
In this case, (( $INPUT >= 1 && $INPUT <= 10 )) is evaluated. If $INPUT is between 1 and 10 inclusively, the mathematical expression will evaluate as true (zero exit code), and thus the then clause will be executed.
So, you can:
Use the original test (single square brackets) command and use the -a to string together two boolean statements in a single test.
Use list operators to string together two separate test commands (single square brackets).
Use the newer compound test command (double square brackets) that now include && and || as boolean operators, so you have a single compound test.
Forget about test command and just use mathematical evaluation (double parentheses) to evaluate boolean expressions.
Test Constructs Can Vary by Shell
As has been mentioned in other posts, [[ is a Bash shell keyword that isn't present in the Bourne shell. You can see this from a Bash prompt with:
type '[['
[[ is a shell keyword
In a Bourne shell, you will instead get "command not found."
Be More Portable: Use the -a Test Operator
A more portable construct is to use the -a test operator to join conditions (see man test for details). For example:
if [ "$INPUT" -ge 1 -a "$INPUT" -le 10 ]; then
: # do something when both conditions are true
else
: # do something when either condition is false
fi
This will work in every Bourne-compatible shell I've ever used, and on any system that has a /bin/\[ executable.
I am trying to make a bash script with the output based on the input.
My code looks like this:
#!/bin/bash
echo "Letter:"
read a
if a=3
then
echo "LOL"
fi
if a=4
then
echo "ROFL"
fi
But when I enter 3 or 4, I get both LOL and ROFL.
Is there a way for me to get LOL for 3 and ROFL for 4?
Sorry if I'm using incorrect terms and stuff, I'm new to bash scripting.
In bash, a=3 is an assignment, not a test. Use, e.g.:
if [ "$a" = 3 ]
Inside [...], the equal sign tests for string (character) equality. If you want to test for numeric value instead, then use '-eq` as in:
if [ "$a" -eq 3 ]
The quotes around "$a" above are necessary to avoid an "operator" error when a is empty.
bash also offers a conditional expressions that begin with [[ and have a different format. Many like the [[ format better (it avoids, for example, the quote issue mentioned above) but the cost is loss of compatibility with other shells. In particular, note that dash, which is the default shell (/bin/sh) for scripts under Debian-derived distributions, does not have [[.
Bash thinks you're trying to assign a variable by saying a=3. You can do the following to fix this:
Use the = operator whilst referencing the variable with a $, like so: if [[ $a = 3 ]]
Use the -eq operator, which is special and doesn't require you to reference the variable with a $, but may not be compatible with all sh-derived shells: if [[ a -eq 3 ]]. If you wish to use -eq without Bash reference the variable: if [[ $a -eq 3 ]]
Note:
The double square brackets [[ ... ]] are a preferred format with specifically Bash conditionals. [ ... ] is good with any sh-derived shell (zsh, tcsh, etc).
if a=3 will assign value 3 to variable a
unless a is readonly variable, if a=3 always returns TRUE
same for if a=4
To compare variable a with a value, you can do this if [ $a = 3 ]
so the script should change to
#!/bin/bash
echo "Letter:"
read a
if [ $a = 3 ]
then
echo "LOL"
fi
if [ $a = 4 ]
then
echo "ROFL"
fi
Since a is read from user input, there is possibility user key in:
non numeric value
a string with empty space
nothing, user may just press Enter key
so a safer way to check is:
if [ "x$a" = "x3" ]
To force numbers to be interpreted in base10, you can prefix with 10#. Specifically 10#08 and 10#09 will be interpreted as valid decimal numbers, and not invalid octal numbers. (I'm taking the output of date +%S)
However, it seems I then can't use the variable in comparisons:
x=10#08
y=10#20
echo $((x+y)) // (returns 28, as expected)
while [ $x -lt $y ]
do
x=$((x++))
done
gives me the error
-bash: [: 10#08: integer expression expected
Is this a bug in bash?
bash's [ builtin mostly emulates the old standard [ command (aka test, and yes it's really a command), which doesn't know about these newfangled base marks. But bash's arithmetic expressions ((( ))) and conditional expressions ([[ ]]) do:
$ x=10#08
$ y=10#20
$ echo $((x+y))
28
$ [ $x -lt $y ] && echo yes
-bash: [: 10#08: integer expression expected
$ /bin/[ $x -lt $y ] && echo yes # This uses external test cmd instead of builtin
[: 10#08: bad number
$ [[ $x -lt $y ]] && echo yes
yes
$ ((x<y)) && echo yes
yes
For purely arithmetic tests, (( )) is generally easiest to use. But both are bash extensions (i.e. not available in the brand-X shell), so be sure to start your script with #!/bin/bash, not #!/bin/sh.
How about [ $(($x)) -lt $(($y)) ] or [[ $x -lt $y ]] ?
Not a bug. The shell doesn't do arithmetic evaluation in conditional expressions. See the sections Arithmetic Expansion and ARITHMETIC EVALUATION in man bash for details about when arithmetic evaluation is done.