BASH SCRIPT : How to use a variable inside another variable - bash

I need to store command line arguments passed in an array
my Command is
./test1.sh 2 4 6
Now i need to store 2 4 6 in an array and im using..
s1=$#
"it tells how many arguments are passed."
for (( c=1; c<=$s1; c++ ))
do
a[$c]}=${$c}
I have written ${$c} for taking the arguments value but it is showing bad substition.

This will give you an array with the arguments: args=("$#")
And you can call them like this: echo ${args[0]} ${args[1]} ${args[2]}

bash variable can be indirectly referenced by \$$VARNAME or by ${!VARNAME} in version 2
so your assignment statement must be :
a[$c]=${!c}
or
eval a[$c]=\$$c

You can always pick the first argument and drop it. Use $1 and shift.
c=1
while [ $# -gt 0 ]; do
a[$c]=$1
c=$((c+1))
shift
done

Related

`set -u` (nounset) vs checking whether I have arguments

I'm trying to improve this nasty old script. I found an undefined variable error to fix, so I added set -u to catch any similar errors.
I get an undefined variable error for "$1", because of this code
if [ -z "$1" ]; then
process "$command"
It just wants to know if there are arguments or not. (The behaviour when passed an empty string as the first argument is not intended. It won't be a problem if we happen to fix that as well).
What's a good way to check whether we have arguments, when running with set -u?
The code above won't work if we replace "$1" with "$#", because of the special way "$#" is expanded when there is more than one argument.
$# contains the number of arguments, so you can test for $1, $2, etc. to exist before accessing them.
if (( $# == 0 )); then
# no arguments
else
# have arguments
fi;
You can ignore the automatic exit due to set -u by setting a default value in the parameter expansion:
#!/bin/sh
set -u
if [ -z "${1-}" ] ; then
echo "\$1 not set or empty"
exit 1
fi
echo "$2" # this will crash if $2 is unset
The syntax is ${parameter-default}, which gives the string default if the named parameter is unset, and the value of parameter otherwise. Similarly, ${parameter:-default} gives default if the named parameter is unset or empty. Above, we just used an empty default value. (${1:-} would be the same here, since we'd just change an empty value to an empty value.)
That's a feature of the POSIX shell and works with other variables too, not just the positional parameters.
If you want to tell the difference between an unset variable and an empty value, use ${par+x}:
if [ "${1+x}" != x ] ; then
echo "\$1 is not set"
fi
My personal favorite :
if
(($#))
then
# We have at least one argument
fi
Or :
if
((!$#))
then
# We have no argument
fi
If each positional argument has a fixed meaning, you can also use this construct:
: ${1:?Missing first argument}
If the first positional argument isn't set, the shell will print "Missing first argument" as an error message and exit. Otherwise, the rest of the script can continue, safe in the knowledge the $1 does, indeed, have a non-empty value.
Use $#, the number of arguments. This provides the most consistent handling for empty arguments.
You might also see the use of "$*". It is similar to "$#", but it is expanded differently when there are multiple arguments.
a() {
for i in "$#"; do echo $i; done
}
b() {
for i in "$*"; do echo $i; done
}
c() {
echo $#
}
echo "a()"
a "1 2" 3
echo "b()"
b "1 2" 3
echo "c()"
c "1 2" 3
# Result:
a()
1 2
3
b()
1 2 3
c()
2

BASH - Refer to parameters using variable

I was trying to make a small script that calculates a binary number to decimal. It works like this:
-Gets '1' or '0' digits as SEPARATE parameters. e.g. "./bin2dec 1 0 0 0 1 1 1".
-For each parameter digit: if it is '1', multiplies it with the corresponding power of 2 (in the above case, the most left '1' will be 64), then adds it in a 'sum' variable.
Here's the code (it is wrong in the noted point):
#!/bin/bash
p=$((2**($#-1))) #Finds the power of two for the first parameter.
sum=0 #The sum variable, to be used for adding the powers of two in it.
for (( i=1; i<=$#; i++ )) #Counts all the way from 1, to the total number of parameters.
do
if [ $i -eq 1 ] # *THIS IS THE WRONG POINT* If parameter content equals '1'...
then
sum=$(($sum+$p)) #...add the current power of two in 'sum'.
fi
p=$(($p/2)) #Divides the power with 2, so to be used on the next parameter.
done
echo $sum #When finished show the 'sum' content, which is supposed to be the decimal equivalent.
My question is in the noted point (line #10, including blank lines). There, I’m trying to check if EACH parameter's content equals 1. How can I do this using a variable?
For example, $1 is the first parameter, $2 is the 2nd and so on. I want it to be like $i, where 'i' is the variable that is increased by one each time so that it matches the next parameter.
Among other things, I tried this: '$(echo "$"$i)' but didn't work.
I know my question is complicated and I tried hard to make it as clear as I could.
Any help?
How about this?
#!/usr/bin/bash
res=0
while [[ $# -gt 0 ]]
do
res=$(($res * 2 + $1))
shift
done
echo $res
$ ./bin2dec 1 0 0 1
9
shift is a command that moves every parameter passed to the script to the left, decreasing it's value by 1, so that the value of $2 is now at $1 after a shift. You can actually a number with shift as well, to indicate how far to shift the variables, so shift is like shift 1, and shift 2 would give $1 the value that used to be at $3.
How about this one?
#!/bin/bash
p=$((2**$#))
while(($#)); do
((sum+=$1*(p/=2)))
shift
done
echo "$sum"
or this one (that goes in the other direction):
#!/bin/bash
while(($#)); do
((sum=2*sum+$1))
shift
done
echo "$sum"
Note that there are no error checkings.
Please consider fedorqui's comment: use echo $((2# binary number )) to convert from binary to decimal.
Also note that this will overflow easily if you give too many arguments.
If you want something that doesn't overflow, consider using dc:
dc <<< "2i101010p"
(setting input radix to 2 with 2i and putting 101010 on the stack and printing it).
Or if you like bc better:
bc <<< "ibase=2;101010"
Note that these need the binary number to be entered as one argument, and not as you wanted with all digits separated. If you really need all digits to be separated, you could also use these funny methods:
Pure Bash.
#!/bin/bash
echo $((2#$(printf '%s' "$#")))
With dc.
#!/bin/bash
dc <<< "2i$(printf '%s' "$#")p"
With bc.
#!/bin/bash
bc <<< "ibase=2;$(printf '%s' "$#")"
Abusing IFS, with dc.
#!/bin/bash
( IFS=; echo "2i$*p" ) | dc
Abusing IFS, with bc.
#!/bin/bash
( IFS=; echo "ibase=2;$*" ) | bc
So we still haven't answered your question (the one in your comment of the OP): And to find out if and how can we refer to a parameter using a variable. It's done in Bash with indirect expansion. You can read about it in the Shell Parameter Expansion section of the Bash reference manual. The best thing is to show you in a terminal:
$ a="hello"
$ b=a
$ echo "$b"
a
$ echo "${!b}"
hello
neat, eh? It's similar with positional parameters:
$ set param1 param2 param3
$ echo "$1, $2, $3"
param1, param2, param3
$ i=2
$ echo "$i"
2
$ echo "${!i}"
param2
Hope this helps!
To reference variables indirectly by name, use ${!name}:
i=2
echo "${!i} is now equivalent to $2"
Try this one also:
#!/bin/bash -
( IFS=''; echo $((2#$*)) )

How do I find the number of arguments passed to a Bash script?

How do I find the number of arguments passed to a Bash script?
This is what I have currently:
#!/bin/bash
i=0
for var in "$#"
do
i=i+1
done
Are there other (better) ways of doing this?
The number of arguments is $#
Search for it on this page to learn more:
http://tldp.org/LDP/abs/html/internalvariables.html#ARGLIST
#!/bin/bash
echo "The number of arguments is: $#"
a=${#}
echo "The total length of all arguments is: ${#a}: "
count=0
for var in "$#"
do
echo "The length of argument '$var' is: ${#var}"
(( count++ ))
(( accum += ${#var} ))
done
echo "The counted number of arguments is: $count"
echo "The accumulated length of all arguments is: $accum"
to add the original reference:
You can get the number of arguments from the special parameter $#. Value of 0 means "no arguments". $# is read-only.
When used in conjunction with shift for argument processing, the special parameter $# is decremented each time Bash Builtin shift is executed.
see Bash Reference Manual in section 3.4.2 Special Parameters:
"The shell treats several parameters specially. These parameters may only be referenced"
and in this section for keyword $# "Expands to the number of positional parameters in decimal."
Below is the easy one -
cat countvariable.sh
echo "$#" | awk '{print NF}'
Output :
#./countvariable.sh 1 2 3 4 5 6
6
#./countvariable.sh 1 2 3 4 5 6 apple orange
8

Problem with function arguments and for loop in bash

Why doesn't this print all the passed arguments, in bash?
function abc() {
echo "$1" #prints the correct argument
for x in `seq 1 $#`; do
echo "$x" #doesn't print the 1st, 2nd, etc arguments, but instead 1, 2, ..
done
}
It is printing
1
2
3
4
...
instead.
I'll just add a couple more options to what everyone else has given. The closest to the way you're trying to write this is to use bash indirect expansion:
function abc() {
for x in `seq 1 $#`; do
echo "${!x}" # the ! adds a level of indirection
done
}
...another option if you want to operate on only some of the arguments, is to use array slicing with $#:
function def() {
for arg in "${#:2:3}"; do # arguments 2 through 4 (i.e. 3 args starting at number 2)
echo "$arg"
done
}
similarly, "${#:2}" will give you all arguments starting at number 2, "${#:$start:$((end-start+1))}" will give you arguments $start through $end (the $(( expression calculates how many arguments there are between $start and $end), etc...
Actually there is a special short-hand for this case:
function abc() {
for arg ; do
echo "$arg"
done
}
That is, if the in ... part is omitted, arg loops over the function's argument $#.
Incidentally if you have for arg ; ... outside of a function, it will iterate over the arguments given on the command line.
The seq command returns all numbers from start to stop. What you are calling here is seq 1 <number_of_arguments_to_abc>. For example, if you call abc alpha beta gamma, then the arguments would be seq 1 3, thus you get the numbers 1, 2 and 3.
If you want the arguments to abc instead, the expression is for x in "$#".
If you want to print all arguments try this
function abc() {
for arg in $#; do
echo "$arg"
done
}
The variable X holds the literal numbers. You're trying to do indirection - substitute $1 where there's a $x. Indirection warps the brain. $# provides a simpler mechanism for looping over the arguments - without any adverse effects on your psyche.
for x in "$#"; do
echo $x
done
See the bash man page for more details on $#.
You should use the for arg form that others have shown. However, to address some things in your question and comments, see the following:
In Bash, it's not necessary to use seq. You can use C-style for loops:
for ((i = 2; i <= $#; i++))
do
echo "${#:i:1}"
done
Which demonstrates array slicing which is another technique you can use in addition to direct iteration (for arg) or using shift.
An advantage of using either version of for is that the argument array is left intact, while shift modifies it. Also, with the C-style form with array slicing, you could skip any arguments you like. This is usually not done to the extent shown below, because it would rely on the arguments following a strict pattern.
for ((i = 2; i < $# - 2; i+=2))
That bit of craziness would start at the second argument, process every other one and stop before the last two or three (depending on whether $# is odd or even).

How to get the nth positional argument in bash?

How to get the nth positional argument in Bash, where n is variable?
Use Bash's indirection feature:
#!/bin/bash
n=3
echo ${!n}
Running that file:
$ ./ind apple banana cantaloupe dates
Produces:
cantaloupe
Edit:
You can also do array slicing:
echo ${#:$n:1}
but not array subscripts:
echo ${#[n]} # WON'T WORK
If N is saved in a variable, use
eval echo \${$N}
if it's a constant use
echo ${12}
since
echo $12
does not mean the same!
Read
Handling positional parameters
and
Parameter expansion
$0: the first positional parameter
$1 ... $9: the argument list elements from 1 to 9

Resources