Binary operator compatibility for shell - bash

I can't help noticing there are so many shell codes using comparison operators as test command's arguments, not with signs.
For example, to test if no arguments are received in shell using test:
if test $# -eq 0
then
echo "No arguments received"
fi
Why can't we replace -eq with more, say, traditional, intuitive, familiar, universal, and, readable sign with ==? So that we have the following:
if test $# == 0
then
echo "No arguments received"
fi
The same goes with other comparison operators < <= => >.
I'm assuming there must be some technical reasons behind this (perhaps compatibility issue?) to favor -eq format over ==, but I'm not aware of them.

First of all, use $#, not $#.
The technical reason for the difference is that the operators define how the operands are interpretted.
For <, = and >, the operands are considered strings. This means that 10 < 2 because 1-something comes before 2-something in the alphabet, and 1 != 01 because the strings are of different lengths.
For -eq, -gt, -lt, the operands are considered integers. This means that 2 -lt 10 because the number 2 is smaller than the number 10, and 1 -eq 01 because these are both numerically equivalent ways of writing 1.

You can use (( and )) (arithmetic processor) in BASH that supports all the operators like ==, >=, <=, <, > etc:
if (( $# == 0 ))
then
echo "No arguments received"
fi
PS: # of parameters is represented by $#

Related

Shell operators into python scripts [duplicate]

This question already has answers here:
Is there a list of 'if' switches anywhere?
(5 answers)
Closed 26 days ago.
I don't understand the following shell operators function.
if [ $count -lt 2 ]
then
echo $CLASS $TC >> $WORKSPACE/testcasestoremove.txt
fi
what exactly -lt doing here?
and
if [ $linesToRemove -gt 0 ]
then
cat $WORKSPACE/testcasestoremove.txt
exit 1
fi
what exactly -gt is doing here?
I did some research but I can not found anything related to that. Any help will be much appreciated.
They are 'less than' and 'greater than' comparison operators.
From the bash manual (man bash):
arg1 OP arg2
OP is one of -eq, -ne, -lt, -le, -gt, or -ge. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively. Arg1 and arg2 may be positive or negative integers.

When is it optional to use ‘$’ character before a variable name in bash?

Could someone please explain why this works, specifically the fact that I am not using ‘$’ character before the names of the variables inside the if statement? I have searched the Bash Reference Manual, but could not find an explanation.
#!/usr/bin/env bash
one="$1"
two="$2"
three="$3"
four="$4"
if [[ one -le two ]] && [[ three -ge four ]]; then
echo "TRUE: $one <= $two && $three >= $four"
else
echo "FALSE: $one <= $two && $three >= $four"
fi
I have also tested it with a loop like this, which works perfectly
for x1 in {1..3}; do
for x2 in {1..3}; do
for x3 in {1..3}; do
for x4 in {1..3}; do ./test $x1 $x2 $x3 $x4;
done; done; done; done | sort
Dollar signs are optional inside an arithmetic context. This is any context where a value is going to be interpreted as a number.
$(( ... )) creates an arithmetic context in all POSIX shells
(( ... )) creates an arithmetic context in bash, including in for ((expr1; expr2; expr3)).
let creates an arithmetic context in shells (like bash) that support that ancient, pre-POSIX, nonstandard syntax.
In ${array[idx]} or array[idx]=value, idx is evaluated as arithmetic as long as the array has not been declared to be associative.
In [[ value1 -le value2 ]], because -le is an arithmetic operator (it only does numeric comparisons, not string comparisons), both value1 and value2 are parsed as arithmetic. This is also true for -eq, -ne, -lt, -gt and -ge.
In ${string:start:len}, both start and len are arithmetic contexts.
When declare -i variable has declared a variable to have a numeric type, and variable=value is subsequently run, value is an arithmetic context.
Note that this is not true for arithmetic comparisons inside test or [ commands; test (whether or not called under the name [) acts like a regular shell command rather than special syntax (despite having a built-in implementation as a performance optimization).
In the description of Bash Conditional Expressions the description of the arithmetic comparison operators (-lt, -gt, etc.) says:
When used with the [[ command, Arg1 and Arg2 are evaluated as arithmetic expressions (see Shell Arithmetic).
And when you follow that link it says:
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax.
And the description of Arithmetic Expansion -- $((expression)) says:
All tokens in the expression undergo parameter and variable expansion, command substitution, and quote removal. ... The evaluation is performed according to the rules listed below (see Shell Arithmetic).

bash if statement doesn't work as expected

I'm new in bash scripting.
I use this code:
#!/bin/bash
count=0
ende="100"
while true; do
out=$(php '/var/www/testsh.php' $count)
if [ "$out"!=="$ende" ]
then
echo "$out i break"
break
fi
echo "sleeping"
sleep 10
((count++))
done
In my PHP: echo '100';
./test.sh gives me:
100 i break
but it should output sleeping till $out is not 100
"$out"!=="$ende" gets substituted into 100!==100, which is a non-empty string, which [ evaluate the same as -n 100!==100 - true, because it is a non-zero string.
"$out" != "$ende" would get substituted into 100 != 100 which is an operator with two operands, and evaluates to false (as it compares two strings).
In a different scenario, != might produce a logical error, since it compares operands as strings. Fortunately, equality of strings in your case is identical to equality of integers (for which you would use -ne), but don't count for it always to be the case.
In [, operators need their space
In [, there's no such thing as !==
For integers, use -eq, -ne, -lt, -le, -gt, -ge
Also, you might like seq:
for count in $(seq 100)
do
# will happen 100 times, with `count` from `0` to `99`
done

Using variables in Bash boolean expressions

Can someone explain how to perform Boolean operations and store them in variables in Bash?
I tried:
A=true
B=false
C=!$A
D=$A && $B
echo $C
echo $D
I also tried without dollars, with [], with {}, ()... How can one do such a simple operation in bash?
result in console are:
!true
true
It seems they are always treated as strings.
You deduced right, bash variables by default contain strings, and its values are treated as strings.
You can use the declare built-in command to explicitly say they store integers (declare -i myintvar), or indexed arrays (declare -a myarr), or associative arrays (declare -A mymap), etc., but not booleans.
The closest you can get to booleans is to use integer values 0 and 1 and evaluate expressions as arithmetic expressions with the (( expr )) command (bash-specific), or with arithmetic expansion $(( expr )) (POSIX-compatible). Those commands evaluate expr according to rules of shell arithmetic.
For example:
A=1
B=0
(( C = \!A )) # logical negation ==> C = 0
(( D = A && B )) # logical AND ==> D = 0
E=$(( A ^ B )) # bitwise XOR ==> E = 1
In bash, you can also use declare -i and let:
declare -i E='A||B' # equivalent to: E=$((A||B)), or ((E=A||B))
let C='!A' # equivalent to: C=$((\!A)), or ((C=\!A))
which are a longer way of saying ((..)) or $((..)). They both force arithmetic evaluation of the expressions given.
Note that ! has a special meaning in most shells (including bash), it causes history expansion. To prevent it, we must escape it with a backslash, or quote it.
Unfortunately, bash does not support boolean variables in a proper meaning. There is no "true" and "false" constants as in programming languages. Instead, /bin/true and /bin/false are two executables that don't do anything except return exit status 0 or 1. Contrary to common logic, exit status 0 is a synonim for "true" and 1 is a synonim for "false". The closest you can get to evaluating boolean expressions is either
[[ expr ]] which returns a exit status 0 or 1 depending on evaluating expr
&& and || (these are conditionals depending on last command's exit status)
[ which is actually an executable with a silly name (not part of bash) that supports some basic expressions
if...elif..else..fi which you can use to your advantage to manipulate variables within the workflow
like this?
t=true
f=false
# if $t; then echo Hi; fi
Hi
# if $f; then echo Hi; fi
# if ! $f; then echo Hi; fi
Hi
# if ! ($t && $t); then echo Hi; fi
# if ($t && $t); then echo Hi; fi
Hi

Integer expression expected

I want to read my files line by line every 5 seconds. This time I just tried one-line bash command to do this.
And bash command is:
let X=1;while [ $X -lt 20 ];do cat XXX.file |head -$X|tail -1;X=$X+1;sleep 5;done
However I got the error like:
-bash: [: 1+1: integer expression expected
What's the problem?
btw, why can't we do $X < 20? (Instead we have to do -lt, less than?)
thx
Your assignment X=$X+1 doesn't perform arithmetic. If $X is 1, it sets it to the string "1+1". Change X=$X+1 to let X=X+1 or let X++.
As for the use of -lt rather than <, that's just part of the syntax of [ (i.e., the test command). It uses = and != for string equality and inequality -eq, -ne, -lt, -le, -gt, and -ge for numbers. As #Malvolio points out, the use of < would be inconvenient, since it's the input redirection operator.
(The test / [ command that's built into the bash shell does accept < and >, but not <= or >=, for strings. But the < or > character has to be quoted to avoid interpretation as an I/O redirection operator.)
Or consider using the equivalent (( expr )) construct rather than the let command. For example, let X++ can be written as ((X++)). At least bash, ksh, and zsh support this, though sh likely doesn't. I haven't checked the respective documentation, but I presume the shells' developers would want to make them compatible.
I would use
X=`expr $X + 1`
but that's just me. And you cannot say $X < 20 because < is the input-redirect operator.
The sum X=$X+1 should be X=$(expr $X + 1 ).
You can also use < for the comparison, but you have to write (("$X" < "20")) with the double parenthesis instead of [ $X -lt 20 ].

Resources