How can i fix the bad substitution? - bash

I got loc_list0,1,2,3,
and i try to do it efficiently and type
b=0
while [ $b -lt 4 ]
do
grep -c "${loc_list$b[0]}" Record$b.txt
done
It says Bad Substitution on ${loc_list$b[0]}, but ok for Record$b. What is the reason behind? I am new to bash shell can anyone tell me how to fix it instead of writing duplicate codes.
Thanks man!
But another problems come when i want to use two varibales for iteration
thanks man, how about i got two variables
b and c which works as counting numbers of iteration
such that:
b=0
c=0
while [ $b -lt 5 ]
do
temp_length=( "${loc_list$b[#]}" )
while [ $c -lt ${#temp_length[#]} ]
do
...
c=$((c+1))
done
...
b=$((b+1))
done
how to fix the bad substitution this time?

You need to use indirect parameter substitution. With arrays, the index you want is considered part of the name.
name=loc_list$b[0]
grep -c "${!name}" Record$b.txt
Record$b.txt works because it is a simple string concatenation, Record + $b + .txt. You aren't try to further expand the result.

Related

Integer Expression Expected : Bash

I read on stack overflow that we need quotes here and added quotes, but it ddn't helped.
array_length=${fileNames[#]}
COUNTER=0
while [ "$COUNTER" -lt "$array_length" ]; do
I get the error still. Please help. Completely new to bash
array_length=${fileNames[#]}
should have been
array_length=${#fileNames[#]}
For the expected behaviour, your while loop should be:
while [ "$COUNTER" -lt "$array_length" ]
do
.
#do something
.
((COUNTER++)) # Equivalent to COUNTER=COUNTER+1
done
${fileNames[#]} expands to the whole array while prefixing it with # gives you the number of elements.
Sidenotes:
1. Don't forget to increment COUNTER inside while loop.
2. Try avoiding capitalized variables like COUNTER as they are usually reserved for the system.

If Statement bash command

I am having a problem with this code:
highmem="58.0"
counter=1
while [ $counter = 1 ];
do
#sleep 5
mem=`ps -C ats -o pid=,%mem= | awk {'print $2'}`
echo $mem
echo $highmem
if [ "$mem" -gt "$highmem" ]
then
echo test
fi
done
Getting ./memtest: line 9: [: 20.3: integer expression expected.
I can't figure it out what I'm doing wrong. I assume I'm comparing integer to text?
There are a couple things wrong with this code:
As Cyrus noted, you are missing a done at the end of the script, to end the loop.
[ $counter = 1 ] is not a valid comparison, you should use [ $counter -eq 1 ]
This loop will never end since the value of counter is never changed.
Apparently the values of mem and highmem are not both integers. Which you should notice from the print statements. Since both variables seem to be floats with a single digit after the period, simply multiply them by 10 and then compare them. This can be achieved as follows: if [ "${mem//./}" -gt "${highmem//./}" ].
The problem was two fold - first I was using -GT rather than > sign. The other problem was that I was comparing 11.6 to 60 .. So I was comparing text to numeric.
The simple work around was to round up or just remove the point or fraction out of the equation and then just do the work. There are may ways of doing thsi I just did it with awk and cut. So at the end my highmem was equal a whole number with no fraction as well as the mem so both number were turned to whole to compare
Thanks for all your helps

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!"

While loop never ends

I'm working with bash and I'm trying to do something like this:
A=1
while [ $A=1 ]; do
read line
echo $line | grep test >/dev/null
A=$?
echo $A
done
This script never ends even when the grep finishes successfully and A is set to 0. What am I missing here? Below is a sample of the output.
$ ./test.sh
asdf
1
test
0
hm...
1
You need to use the correct comparison operator. Bash has different operators for integer and string comparison.
In addition, you need the correct spacing in the comparison expression.
You need
while [ $A -eq 1 ]; do
See here for more
I find Bash's syntax pretty awful - have you tried something like:
while [ $A -eq 1 ] ... ?
It may be trying to re-assign 1 to $A or something strange like that.
Try:
while [ $A -eq 1 ]; do
Most of the answers have focused on the integer/string and spacing problem, which is fine, but your code looks so unidiomatic that IMO it should be completely re-factored. Let's say the idea is to process lines until one line matches the regex 'test':
while read line; do
if [[ "$line" =~ test ]] && break
# do something with $line
done
Of course this can be simplified further if you take advantage of text processing tools like sed:
sed -e '/test/,$d'
you can do this instead. No need to call external grep.
while true; do
read line
case "$line" in
*test* ) break;;
esac
done
echo $line
Have you not tried this
while [ $A == "1" ]
....
done
Edit: Whoops since Dan mentioned my error I graciously admit my mistake and have edited this accordingly - Thanks Dan for the heads up...
while [ $A -eq 1 ]
....
done
Sorry! :(
Hope this helps,
Best regards,
Tom.
All of your answers are in the Advanced Bash-Scripting guide. It is awesome.

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