When does a variable add $ in bash - linux

I'm recently learning bash and confused when a variable would add $. I find code like:
i=1
while [ $i -le 10 ]
do
echo "$n * $i = `expr $i \* $n`"
i=`expr $i + 1`
done

The $ substitutes the variable. Writing $i will insert the value of i, no matter where you write it.
If you want to assign to the variable, that obviously makes no sense.

I thought #slaks' [ answer ] wouldn't be complete without this :
When not to add $ for a variable
With The Double-Parentheses [ Construct ]
x=5;
(( x++ )) # fine, note this construct accept $x form too.
When using export
var=stuff
export var #fine
When using declare
declare -a arry # fine
When not omit $
As #rici pointed out in the comment below:
you can leave out the $ in any arithmetic context, not just ((...))
and $((...)) ... For example, if arr is an array (not associative), then
${arr[x++]} is also fine.
Consider
# You wanted to create an associative array 'test' but you forgot to do
# declare -A test , Now below
test[foo]=bar # is foo a variable or a key, the reader isn't clear
# creates a simple array
echo ${test[foo]} # is foo a variable or a key?
bar
declare -p test
declare -a test='([0]="bar")'
# What happened?
# Since foo was not set at the point when 'test[foo]=bar' was called,
# bash substituted it with zero
# I meant to say test[foo]=bar hides an error.

A key thing to remember is that variables are never passed around in shell, only values. When you call something like
echo "$foo"
you might think that echo receives $foo, then looks at its value. Instead, the shell first expands $foo to the value of foo, then passes that value to echo.
The dollar sign is used to introduce any such parameter expansion, where the value of a parameter is needed. Consider:
$ foo=10
$ echo foo
foo
$ echo $foo
10
From the perspective of the echo command, there is no difference between echo $foo and echo 10; in both cases, the value passed to echo is 10.

To set a value to a variable in bash you can do
a=10
Where as to access the value of that variable you need to use
echo $a which mean $ is a symbol used to access the value of a variable.
i=1 ---> setting variable as 1
while [ $i -le 10 ] ---> simple while statement which loops till value of i is less that 10
do
echo "$n * $i = `expr $i \* $n`" ---> This is a syntax error bcoz value of n is never assigned
i=`expr $i + 1` ---> This line add's one to value of i
done ---> terminate while loop
Hope that explains you.

Related

how to use $* but it doesnt consider a special arguement in bash

I want to use $* in a loop but the first argument is ignored.
in other words:
sum=0
for i in $*
do
if [ $1 = "+" ]; then
sum=$(($sum+$i))
fi
done
echo $sum
To remove the first parameter, use shift.
But before you shift, you should store the value somewhere.
As the first parameter doesn't change, you can check it just once before starting the loop:
#! /bin/bash
op=$1
shift
if [ "$op" = + ] ; then
sum=0
for i in $* ; do
sum=$(($sum+$i))
done
echo $sum
fi
Note that you don't need the quotes around +. You should quote the $op in the condition, though, to prevent parsing errors (try running the script specifying an empty string "" as the first argument).
When using $((, note that you can use shorter and faster way to increment a variable:
((sum+=i))

How to write a bash script to label each argument like this:

$ bash argcnt.sh this is a "real live" test
is
real live
(to display only paired arguments)
Because, I know only in this way:
#!/bin/bash
echo "$2"
echo "$4"
It seems like you want to print every other argument given to the script. You could then create a loop over $#:
#!/bin/bash
# idx will be 2, 4, 6 ... for as long as it's less than the number of arguments given
for ((idx = 2; idx < ${##}; idx += 2))
do
# variable indirection below:
echo "${!idx}"
done
Note: You can use $# instead of ${##} to get the number of elements in $# too. I don't know which one that is preferred by people in general.
If what you want is print every other argument, starting from the second, you can use shift:
$ cat argcnt
#!/bin/bash
while shift; do printf '%s\n' "$1"; shift; done
$ ./argcnt this is a "real live" test foo
is
real live
foo

reload a variable string inside another string

I have to declare a string composed of different variables at the starting of a loop in order to print it later just with eval $command >> file.txt avoiding retype every time the string $command itself. But my $command string is composed of other variables and I need to be able to update them before printing. Here a brief example:
a=0
command="echo \"$a\""
for i in {1..2}; do
### change variable $a
a="$i"
### print command with the new $a
eval "$command"
done
### (it fails) result:
0
0
I need $a to be reloaded everytime in order to be substituted inside the $command string, thus the loop above will return
### wanted result
1
2
I know there are other strategies to achieve this, but I wonder if there is a specific way to reload a variable inside a string
Thank you very much in advance for any help!
You can use a function instead of a variable assignment
#!/usr/bin/env bash
a=0
command(){ echo "$1" ; }
for i in {1..2}; do
a="$i"
command "$a"
done
Edit: as per #glenn jackman one can use a global variable
command(){ echo "$a"; }
And just call the function without the argument $a
command
When you put a variable inside " its expanded. Use a single quote for command variable assignment.
$a=0
$command="echo \"$a\""
$echo $command
echo "0"
$command='echo \"$a\"'
$echo $command
echo \"$a\"
$
Try
a=0
command='echo $a'
for i in {1..2}; do
### change variable $a
a="$i"
### print command with the new $a
eval "$command"
done

bash shell script concatenate string with period char

I am trying to create following string
Beta-3.8.0
but shell script always omits the . period char no matter what I do.
echo "$readVersion"
if [ -z $readVersion ]
then
echo "readVersion is empty"
exit 1
fi;
IFS=.
set $readVersion
newVersion=$(echo "$2 + 1" | bc)
newBranch="Beta-$1.$newVersion.$3"
echo $newBranch
prints:
3.8.0
Beta-3 9 0
I have also tried
newBranch='Beta-'$1'.'$newVersion'.'$3
or
newBranch="Beta-{$1}.{$newVersion}.{$3}"
although this seems printing the right value echo "$1.$newVersion.$3" why not variable doesnt work ?
I need the variable to use later on in the script...
You can save and restore the IFS once you are done.
oldIFS=$IFS
IFS=.
set $readVersion
newVersion=$(echo "$2 + 1" | bc)
IFS=$oldIFS
newBranch="Beta-$1.$newVersion.$3"
echo "$newBranch"
Or you can quote when printing:
echo "$newBranch"
The former is a better idea IMO since it conveys your intention and would make the rest of the code use the "correct" IFS. The latter just circumvents the problem.

Bash reading txt file and storing in array

I'm writing my first Bash script, I have some experience with C and C# so I think the logic of the program is correct, it's just the syntax is so complicated because apparently there are many different ways to write the same thing!
Here is the script, it simply checks if the argument (string) is contained in a certain file. If so it stores each line of the file in an array and writes an item of the array in a file. I'm sure there must be easier ways to achieve that but I want to do some practice with bash loops
#!/bin/bash
NOME=$1
c=0
#IF NAME IS FOUND IN THE PHONEBOOK THEN STORE EACH LINE OF THE FILE INTO ARRAY
#ONCE THE ARRAY IS DONE GET THE INDEX OF MATCHING NAME AND RETURN ARRAY[INDEX+1]
if grep "$NOME" /root/phonebook.txt ; then
echo "CREATING ARRAY"
while read line
do
myArray[$c]=$line # store line
c=$(expr $c + 1) # increase counter by 1
done < /root/phonebook.txt
else
echo "Name not found"
fi
c=0
for i in myArray;
do
if myArray[$i]="$NOME" ; then
echo ${myArray[i+1]} >> /root/numbertocall.txt
fi
done
This code returns the only the second item of myArray (myArray[2]) or the second line of the file, why?
The first part (where you build the array) looks ok, but the second part has a couple of serious errors:
for i in myArray; -- this executes the loop once, with $i set to "myArray". In this case, you want $i to iterate over the indexes of myArray, so you need to use
for i in "${!myArray[#]}"
or
for ((i=0; i<${#a[#]}; i++))
(although I generally prefer the first, since it'll work with noncontiguous and associative arrays).
Also, you don't need the ; unless do is on the same line (in shell, ; is mostly equivalent to a line break so having a semicolon at the end of a line is redundant).
if myArray[$i]="$NOME" ; then -- the if statement takes a command, and will therefore treat myArray[$i]="$NOME" as an assignment command, which is not at all what you wanted. In order to compare strings, you could use the test command or its synonym [
if [ "${myArray[i]}" = "$NOME" ]; then
or a bash conditional expression
if [[ "${myArray[i]}" = "$NOME" ]]; then
The two are very similar, but the conditional expression has much cleaner syntax (e.g. in a test command, > redirects output, while \> is a string comparison; in [[ ]] a plain > is a comparison).
In either case, you need to use an appropriate $ expression for myArray, or it'll be interpreted as a literal. On the other hand, you don't need a $ before the i in "${myArray[i]}" because it's in a numeric expression context and therefore will be expanded automatically.
Finally, note that the spaces between elements are absolutely required -- in shell, spaces are very important delimiters, not just there for readability like they usually are in c.
1.-This is what you wrote with small adjustments
#!/bin/bash
NOME=$1
#IF NAME IS FOUND IN THE PHONE-BOOK **THEN** READ THE PHONE BOOK LINES INTO AN ARRAY VARIABLE
#ONCE THE ARRAY IS COMPLETED, GET THE INDEX OF MATCHING LINE AND RETURN ARRAY[INDEX+1]
c=0
if grep "$NOME" /root/phonebook.txt ; then
echo "CREATING ARRAY...."
IFS= while read -r line #IFS= in case you want to preserve leading and trailing spaces
do
myArray[c]=$line # put line in the array
c=$((c+1)) # increase counter by 1
done < /root/phonebook.txt
for i in ${!myArray[#]}; do
if myArray[i]="$NOME" ; then
echo ${myArray[i+1]} >> /root/numbertocall.txt
fi
done
else
echo "Name not found"
fi
2.-But you can also read the array and stop looping like this:
#!/bin/bash
NOME=$1
c=0
if grep "$NOME" /root/phonebook.txt ; then
echo "CREATING ARRAY...."
readarray myArray < /root/phonebook.txt
for i in ${!myArray[#]}; do
if myArray[i]="$NOME" ; then
echo ${myArray[i+1]} >> /root/numbertocall.txt
break # stop looping
fi
done
else
echo "Name not found"
fi
exit 0
3.- The following improves things. Supposing a)$NAME matches the whole line that contains it and b)there's always one line after a $NOME found, this will work; if not (if $NOME can be the last line in the phone-book), then you need to do small adjustments.
!/bin/bash
PHONEBOOK="/root/phonebook.txt"
NUMBERTOCALL="/root/numbertocall.txt"
NOME="$1"
myline=""
myline=$(grep -A1 "$NOME" "$PHONEBOOK" | sed '1d')
if [ -z "$myline" ]; then
echo "Name not found :-("
else
echo -n "$NOME FOUND.... "
echo "$myline" >> "$NUMBERTOCALL"
echo " .... AND SAVED! :-)"
fi
exit 0

Resources