Syntax error: operand expected when using Bash - linux

I have two arrays that I want to loop in. I construct those properly and before going into for loop, I do echo them to be sure everything is ok with arrays.
But when I run the script, it outputs an error:
l<=: syntax error: operand expected (error token is "<="
I consulted the mighty Google and I understood it suffers from the lack of the second variable, but I mentioned earlier I do echo the values and everything seems to be OK. Here is the snippet..
#!/bin/bash
k=0
#this loop is just for being sure array is loaded
while [[ $k -le ${#hitEnd[#]} ]]
do
echo "hitEnd is: ${hitEnd[k]} and hitStart is: ${hitStart[k]}"
# here outputs the values correct
k=$((k+1))
done
k=0
for ((l=${hitStart[k]};l<=${hitEnd[k]};l++)) ; do //this is error line..
let array[l]++
k=$((k+1))
done
The variables in the for loop are echoed correctly but for loop won't work.. where am I wrong?
#
as gniourf_gniourf answered:
"... At some point, k will reach the value ${#hitEnd[#]}, and this is
exactly when hitEnd[k] is not defined and expands to an empty string!
Bang!"
meaning error output is displayed not at the beginning of the loop, but when k has a greater value than array's indices, pointing an index that array does not include...

That's because at some point ${hitEnd[k]} expands to nothing (it is undefined). I get the same error with ((l<=)). You should write your for loop as:
k=0
for ((l=${hitStart[0]};k<${#hitEnd[#]} && l<=${hitEnd[k]};l++)); do
so as to always have an index k that corresponds to a defined field in the array ${hitEnd[#]}.
Also, instead of
k=$((k+1))
you can just write
((++k))
Done!
Your script revised using better modern bash practice:
#!/bin/bash
k=0
#this loop is just for being sure array is loaded
while ((k<=${#hitEnd[#]})); do
echo "hitEnd is: ${hitEnd[k]} and hitStart is: ${hitStart[k]}"
# here outputs the values correct
((++k))
done
k=0
for ((l=hitStart[0];k<${#hitEnd[#]} && l<=hitEnd[k];++l)); do
((++array[l]))
((++k))
done
Now, I'm not too sure the for loop does exactly what you want it to... Don't you mean this instead?
#!/bin/bash
# define arrays hitStart[#] and hitEnd[#]...
# define array array[#]
#this loop is just for being sure array is loaded
for ((k=0;k<${#hitEnd[#]};++k)); do
echo "hitEnd is: ${hitEnd[k]} and hitStart is: ${hitStart[k]}"
# here outputs the values correct
((++k))
done
for ((k=0;k<${#hitEnd[#]};++k)); do
for ((l=hitStart[k];l<=hitEnd[k];++l)); do
((++array[l]))
done
done

A bit bandaid-y, but you rewrite your for-loop into a while loop:
l="${hitStart[k]}"
while [[ "$l" -le "${hitEnd[k]}" ]]; do
let array[l]++
k=$((k+1))
l=$((l+1))
done

Related

how can i make the lines variable in a file? [duplicate]

I'm trying to read from a file, that has multiple lines, each with 3 informations I want to assign to the variables and work with.
I figured out, how to simply display them each on the terminal, but can't figure out how to actually assign them to variables.
while read i
do
for j in $i
do
echo $j
done
done < ./test.txt
test.txt:
1 2 3
a b c
So I want to read the line in the outer loop, then assign the 3 variables and then work with them, before going to the next line.
I'm guessing I have to read the values of the lines without an inside loop, but I can't figure it out right now.
Hope someone can point me in the right direction.
I think all you're looking for is to read multiple variables per line: the read command can assign words to variables by itself.
while read -r first second third; do
do_stuff_with "$first"
do_stuff_with "$second"
do_stuff_with "$third"
done < ./test.txt
The below assumes that your desired result is the set of assignments a=1, b=2, and c=3, taking the values from the first line and the keys from the second.
The easy way to do this is to read your keys and values into two separate arrays. Then you can iterate only once, referring to the items at each position within those arrays.
#!/usr/bin/env bash
case $BASH_VERSION in
''|[123].*) echo "ERROR: This script requires bash 4.0 or newer" >&2; exit 1;;
esac
input_file=${1:-test.txt}
# create an associative array in which to store your variables read from a file
declare -A vars=( )
{
read -r -a vals # read first line into array "vals"
read -r -a keys # read second line into array "keys"
for idx in "${!keys[#]}"; do # iterate over array indexes (starting at 0)
key=${keys[$idx]} # extract key at that index
val=${vals[$idx]} # extract value at that index
vars[$key]=$val # assign the value to the key inside the associative array
done
} < "$input_file"
# print for debugging
declare -p vars >&2
echo "Value of variable a is ${vars[a]}"
See:
BashFAQ #6 - How can I use variable variables (indirect variables, pointers, references) or associative arrays?
The bash-hackers page on the read builtin, documenting use of -a to read words into an array.

How do you compare the value of an array to a variable in bash script?

I'm practicing bash and honestly, it is pretty fun. However, I'm trying to write a program that compares an array's value to a variable and if they are the same then it should print the array's value with an asterisk to the left of it.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite="black"
for i in {0..4};do echo ${color[$i]};
if {"$favorite"=$color[i]}; then
echo"* $color[i]"
done
output should be *black
There's few incorrect statements in your code that prevent it from doing what you ask it to. The comparison in bash is done withing square brackets, leaving space around them. You correctly use the = for string comparison, but should enclose in " the string variable. Also, while you correctly address the element array in the echo statement, you don't do so inside the comparison, where it should read ${color[$i]} as well. Same error in the asterisk print. So, here a reworked code with the fixes, but read more below.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite=black
for i in {0..4};do
echo ${color[$i]};
if [ "$favorite" = "${color[$i]}" ]; then
echo "* ${color[$i]}"
fi
done
While that code works now, few things that probably I like and would suggest (open to more expert input of course by the SO community): always enclose strings in ", as it makes evident it is a string variable; when looping an array, no need to use index variables; enclose variables always within ${}.
So my version of the same code would be:
#!/bin/bash
color=("red" "blue" "black" "brown" "yellow")
favorite="black"
for item in ${color[#]}; do
echo ${item}
if [ "${item}" = "${favorite}" ]; then
echo "* $item"
fi
done
And a pointer to the great Advanced Bash-Scripting Guide here: http://tldp.org/LDP/abs/html/

bash script function scope

function generateFileList {
for entry in "$ORIGINATION_PATH"/*
do
entry=${entry%.*} # retain the part before the dot
entry=${entry##*/} # retain the part after the last slash
if [ $(contains "${FILENAME[#]}" $entry) == "n" ]; then
FILENAME[$fn_counter]=$entry
fn_counter=(expr $fn_counter + 1)
echo $entry "added to filelist"
echo ${FILENAME[$fn_counter]}
fi
done
NUMBER_OF_FILES=$(expr ${#FILENAME[#]} + 1)}
I have this function .My $ORIGINATION_PATH has many files in it. However, when I call this function my $FILENAME array gets populated only with one entry.Why? Inside the function everything seems fine, and it seems that $FILENAME array gets all the values it needs to get, but when I check outside the function I only get one value in the $FILENAME aray
Problems with your code and suggestions for improvement:
You should initialize ${FILENAME[#]} to an empty array (either in the function itself if you always want the function to generate a new list of files from scratch, or before calling the function if you want to be able to build up a composite list of files by calling the function repeatedly on different base directories).
You should initialize $fn_counter to zero before starting the loop. Or, for the composite build-up idea, to the number of elements currently in ${FILENAME[#]}. Actually, another, perhaps preferable solution, would be to remove the $fn_counter variable entirely and replace it with ${#FILENAME[#]}, since it should always be equal to that value.
In the line fn_counter=(expr $fn_counter + 1), you're assigning $fn_counter to an array, rather than incrementing it. This is because you forgot the dollar before the open parenthesis. If you ran fn_counter=$(expr $fn_counter + 1) then it would work. But there's a better way to increment a numeric variable: let ++fn_counter.
You don't have to dollar-prefix variables in arithmetic expressions. So, for example, we can say ${FILENAME[fn_counter]} instead of ${FILENAME[$fn_counter]}.
You're trying to echo the element of ${FILENAME[#]} that was just added in the current iteration, but indexing it with $fn_counter after it was incremented, which is incorrect. You can solve this by subtracting 1 from it, i.e. echo "${FILENAME[fn_counter-1]}". Or, if removing $fn_counter, echo "${FILENAME[${#FILENAME[#]}-1]}".
When assigning $NUMBER_OF_FILES, I don't know why you're adding 1 to ${#FILENAME[#]}. The number of elements in the ${FILENAME[#]} array should be equal to the number of files, without requiring an increment, no? I recommend removing this variable entirely, since the value can be accessed directly as ${#FILENAME[#]}.
I recommend you pass inputs as arguments (e.g. pass $ORIGINATION_PATH as an argument) and use the local keyword to reduce the likelihood of variable clashes between functions. Globals are the default in bash, which creates dangerous possibilities for different functions to step on each others' toes. For example, imagine if the contains function (assuming it's a shell function) assigned a value to the global $entry variable.
I recommend always using the [[ command rather than [, as it's more powerful, and it's good to be consistent.
As written, your script won't work correctly on an empty directory. You could test in advance if the directory is empty (e.g. [[ -n "$(find "$ORIGINATION_PATH" -maxdepth 0 -empty)" ]]). Another solution is to set nullglob. Another solution is to skip glob words that don't actually exist (e.g. if [[ ! -e "$entry" ]]; then continue; fi;).
Always double-quote variable expansions to protect against word splitting, which takes place after variable expansion. For example, the contains call should be contains "${FILENAME[#]}" "$entry" (notice the double-quoting around $entry). The only exceptions are (1) when assigning a string variable to a string variable, i.e. new=$old, in which case you don't have to quote it, and (2) when expanding a numeric variable, which is guaranteed not to be corrupted by word splitting.
Here's a working solution, filling in the missing pieces:
function contains {
local target="${#:$#:1}";
local -a array=("${#:1:$#-1}");
local elem='';
for elem in "${array[#]}"; do
if [[ "$elem" == "$target" ]]; then
echo 'y';
return;
fi;
done;
echo 'n';
} ## end contains()
function generateFileList {
local path="$1";
local entry='';
for entry in "$path"/*; do
if [[ ! -e "$entry" ]]; then continue; fi;
entry=${entry%.*}; ## retain the part before the dot
entry=${entry##*/}; ## retain the part after the last slash
if [[ "$(contains "${FILENAME[#]}" "$entry")" == 'n' ]]; then
FILENAME[${#FILENAME[#]}]=$entry;
echo "$entry added to filelist";
echo "${FILENAME[${#FILENAME[#]}-1]}";
fi;
done;
} ## end generateFileList()
ORIGINATION_PATH='...';
FILENAME=(); ## build up result on global ${FILENAME[#]} var
generateFileList "$ORIGINATION_PATH";
echo "\${#FILENAME[#]} == ${#FILENAME[#]}";
echo "\${FILENAME[#]} == (${FILENAME[#]})";

Access a variable referred back twice UNIX

This is a simple question, but I have the variables
classNext1="Exists!"
classNext2="Exists!"
classNext3="Does not exist yet!"
classNext4="Does not exist yet!"
I am trying to figure out which variable is next in order, the next variable that wouldn't equal "Exists!"
I have this while loop
i=0
while [ $i -lt 10 ]
do
i=`expr $i + 1` # Increment i
temp="extraClass$i"
if [ "$($temp)" != 'Exists!' ];then
echo "SUCUDESSS"
nextClass="extraClass$i"
break
fi
echo $temp
done
The next class in the list would be classNext3, but it would just go and assign nextClass=nextClass1.
So the if statement would always be true in the first iteration. The problem is that $temp equals extraClass$i, it wouldn't actually equal the value of $extraClass($i) at the i th iteration. How would I structure it so that the if statement would actually get the value of $extraClass($i) instead of the literal string"extraClass($i)"??
To use the value of a variable as the name of a variable to access, use variable indirection, as explained here.
if [ "${!temp}" != 'Exists!' ]; then
But whenever you find yourself naming variables with numerical suffixes, and wanting to access them dynamically, that practically always means you should be using an array instead of separate variables. This goes for just about any programming language.

Conditional statement not working as expected

I am using Konsole on kubuntu 14.04.
I want to take arguments to this shell-script, and pass it to a command. The code is basically an infinite loop, and I want one of the arguments to the inner command to be increased once every 3 iterations of the loop. Ignoring the actual details, here's a gist of my code:
#!/bin/bash
ct=0
begin=$1
while :
do
echo "give: $begin as argument to the command"
#actual command
ct=$((ct+1))
if [ $ct%3==0 ]; then
begin=$(($begin+1))
fi
done
I am expecting the begin variable to be increased every 3 iterations, but it is increasing in the every iteration of the loop. What am I doing wrong?
You want to test with
if [ $(expr $cr % 3) = 0 ]; then ...
because this
[ $ct%3==0 ]
tests whether the string $ct%3==0, after parameter substitution, is not empty. A good way for understanding this is reading the manual for test and look at the semantics when it is given 1, 2, 3 or more arguments. In your original script, it only sees one argument, in mine it sees three. White space is very important in the shell. :-)
In BASH you can completely utilize ((...)) and refactor your script like this:
#!/bin/bash
ct=0
begin="$1"
while :
do
echo "give: $begin as argument to the command"
#actual command
(( ct++ % 3 == 0)) && (( begin++ ))
done

Resources