Integer expression expected - bash

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 ].

Related

Adding a list of space separated numbers

Currently stuck in a situation where I ask the user to input a line of numbers with a space in between, then have the program display those numbers with a delay, then add them. I have everything down, but can't seem to figure out a line of code to coherently calculate the sum of their input, as most of my attempts end up with an error, or have the final number multiplied by the 2nd one (not even sure how?). Any help is appreciated.
echo Enter a line of numbers to be added.
read NUMBERS
COUNTER=0
for NUM in $NUMBERS
do
sleep 1
COUNTER=`expr $COUNTER + 1`
if [ "$NUM" ]; then
echo "$NUM"
fi
done
I've tried echo expr $NUM + $NUM to little success, but this is really all I can some up with.
Start with
NUMBERS="4 3 2 6 5 1"
echo $NUMBERS
Your script can be changed into
sum=0
for NUM in ${NUMBERS}
do
sleep 1
((counter++))
(( sum += NUM ))
echo "Digit ${counter}: Sum=$sum"
done
echo Sum=$sum
Another way is using bc, usefull for input like 1.6 2.3
sed 's/ /+/g' <<< "${NUMBERS}" | bc
Set two variables n and m, store their sum in $x, print it:
n=5 m=7 x=$((n + m)) ; echo $x
Output:
12
The above syntax is POSIX compatible, (i.e. works in dash, ksh, bash, etc.); from man dash:
Arithmetic Expansion
Arithmetic expansion provides a mechanism for evaluating an arithmetic
expression and substituting its value. The format for arithmetic expan‐
sion is as follows:
$((expression))
The expression is treated as if it were in double-quotes, except that a
double-quote inside the expression is not treated specially. The shell
expands all tokens in the expression for parameter expansion, command
substitution, and quote removal.
Next, the shell treats this as an arithmetic expression and substitutes
the value of the expression.
Two one-liners that do most of the job in the OP:
POSIX:
while read x ; do echo $(( $(echo $x | tr ' ' '+') )) ; done
bash:
while read x ; do echo $(( ${x// /+} )) ; done
bash with calc, (allows summing real, rational & complex numbers, as well as sub-operations):
while read x ; do calc -- ${x// /+} ; done
Example input line, followed by output:
-8!^(1/3) 2^63 -1
9223372036854775772.7095244707464171953

Two while loops behaving strangely, Bash script

I'm new to Bash scripting. I have written a script to help me get some info using ssh from bunch of servers. the IP address of first set of devices are from 101 to 148, and the other set are from 201 to 210.
#!/bin/bash
BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START
while [[ $SD_counter -le $SD_END ]]
do
ip=$BASE.$SD_counter
ssh $ip command1
SD_counter=$(($SD_counter +1))
if [ "$SD_counter"==148 ]
then
while [[ $HD_counter -le $HD_END ]]
do
ip=$BASE.$HD_counter
ssh $ip command2
HD_counter=$(($HD_counter +1))
done
fi
done > log_SD_HD
echo "Done!"
But for some reason command1 is executed on 192.168.11.101 first, then command2 is executed on ip range 192.168.11.201-192.168.11.210 which is the second while loop.
After that the first while loop continues till the end.
Why is this happening? I want the first while loop to be done before the second while loop. Could someone please point out what I'm doing wrong?
#0x1cf's answer provides the right pointer:
[ "$SD_counter"==148 ] doesn't work as expected.
Specifically: "$SD_counter"==148, based on bash's string synthesizing rules, is expanded to a single string literal: the value of $SD_counter is concatenated with literal ==148, and the resulting string literal is treated as a Boolean.
Since a non-empty string in a Boolean context always evaluates to true, [ "$SD_counter"==148 ] always evaluates to true due to lack of spaces around the ==.
Aside from that: in bash you should use [[ ... ]] rather than [ ... ] - it is more robust and provides more features.
Also note (as #0x1cf notes too) that - if using [ ... ] or [[ ... ]] - using the arithmetic operators is the right choice when dealing with numbers: -eq, -ne, -lt, -le, -gt, or -ge.
Generally, though, using (( ... )) expressions - arithmetic evaluation - provides more flexibility with numbers - see below.
That said, your code can be greatly simplified by using arithmetic evaluation - (( ... )) (see section ARITHMETIC EVALUATION in man bash):
It allows you to use C-style arithmetic and Boolean expressions.
If we combine this with bash's array variables, your code can be simplified to:
#!/usr/bin/env bash
BASE=192.168.11
START_INDICES=( 101 201 )
END_INDICES=( 148 210 )
COMMANDS=( command1 command2 )
numRanges=${#START_INDICES[#]}
for (( range = 0; range < numRanges; ++range )); do
cmd=${COMMANDS[range]}
for (( i=${START_INDICES[range]}; i<=${END_INDICES[range]}; ++i )); do
ip=$BASE.$i
ssh $ip $cmd
done
done > log_SD_HD
Note:
(( ... )) expressions DIFFER from normal bash assignments and conditionals in that you:
need NOT reference variables with $
need NOT double-quote variable references
you MAY have spaces around the assignment operator (=)
you MAY omit spaces around relational operators: (( SD_counter==148 )) DOES work.
( string1 ... ) creates an array with elements string1, ...; ${#arrayVar[#]} returns the count of elements of array variable arrayVar; ${arrayVar[ndx]} returns the element with (0-based) index ndx.
It's better to avoid ALL-UPPERCASE variable names such as BASE, as they may conflict with environment variables, which are by convention typically all-uppercase.
UPDATE
Hint: You can always use #!/bin/bash -x to trace and debug your scripts.
Maybe using two while loop is a good idea, just as V_Maenolis showed. However, to answer your question about what's wrong with your script, try this
Replace
if [ "$SD_counter"==148 ]
with
if [ "$SD_counter" -gt 148 ]
which works for me.
So there are two errors
There should be a space before and after == operator, that is to say, using A == B NOT A==B
The logic of comparing SD_counter == 148 is incorrect. Because when SD_counter hits 148, your script will run into the second while loop, and you'll get 147, 201, ..., 210, 148. Using -gt instead avoids the problem.
There is no reason to nest the loops from what you showed:
#!/bin/bash
BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START
while [[ $SD_counter -le $SD_END ]]
do
ip=$BASE.$SD_counter
ssh $ip command1
SD_counter=$(($SD_counter +1))
done> log_SD_HD
while [[ $HD_counter -le $HD_END ]]
do
ip=$BASE.$HD_counter
ssh $ip command2
HD_counter=$(($HD_counter +1))
done>> log_SD_HD
echo "Done!"

Binary operator compatibility for shell

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 $#

Variable in shell script

#!/bin/bash
i=1
until [ $i -gt 6 ]
do
echo "Welcome $i times."
i=$(( i+1 ))
done
Why we use double () in i=$(( i+1 )),and
why if we change the program to
i=$( i+1 )
or
i++
or
$i=$i+1
, it is not correct?
$( foo ) tries to execute foo as a command in a subshell and returns the result as a string. Since i+1 is not a valid shell command, this does not work.
$(( foo )) evaluates foo as an arithmetic expression.
It's just two similar (but different) syntaxes that do different things.
http://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/dblparens.html
Similar to the let command, the
((...)) construct permits arithmetic
expansion and evaluation. In its
simplest form, a=$(( 5 + 3 )) would
set "a" to "5 + 3", or 8. However,
this double parentheses construct is
also a mechanism for allowing C-type
manipulation of variables in Bash.

Arithmetic comparison in a shell

i want to compare two number values in a shell script (sh) but it doesn`t work:
#!/bin/sh
let a=30
let b=100
let x=$a-$b
echo $a $b $x
[ $a < $b ] && { echo ok; }
That outputs:
30 100 -70
./x: line 6: 100: No such file or directory
I believe that should be -lt (which stands for less than) rather than "<". "<" is for string comparisons.
Edit: Actually looking at this now it seems clear what the problem is. The "<" character does file redirection so that's what the shell is trying to do. You can escape that character by doing \< instead but as originally stated that will do string comparison rather than numeric comparison.
Replace < with -lt
Also, lose the "let." This isn't Basic. Bill Gates and Steve Ballmer (Developers!) have nothing to do with this universe.
"let" is perfectly fine in shell, and has nothing to do with M$ basic or what.!
#OP , you can use bc to compare numbers, especially if you are also comparing floats. See here for similar example
#!/bin/sh
let a=30
let b=100
let x=$a-$b
echo $a $b $x
if(($a < $b)) then
echo ok
fi

Resources