how to use array as function variable in bash - bash

I want to pass an array parameter to a function in bash, and writing some testing code as:
#!/bin/sh
function foo {
a=$1;
for i in ${a[#]} ; do
echo $i
done
}
names=(jim jerry jeff)
foo ${names[#]}
the above code just show jim, rather than the three j*. so my question is:
why my code doesn't work
what's the right way to do it

#!/bin/bash
function foo {
a=($*)
for i in ${a[#]}
do
echo $i
done
}
names=(jim jerry jeff)
foo ${names[#]}
Your code did not show jim to me, but "names", literally. You have to pass the whole array. And you have to recapture it with a=$($).
The manpage part in bash about Arrays is rather long. I only cite one sentence:
Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0.

You're fairly close; the biggest problem was the command a=$1, which assigns only the first parameter ($1) to a, while you want to assign the entire list of parameters ($#), and assign it as an array rather than as a string. Other things I corrected: you should use double-quotes around variables whenever you use them to avoid confusion with special characters (e.g. spaces); and start the script with #!/bin/bash, since arrays are a bash extension, not always available in a brand-X shell.
#!/bin/bash
function foo {
a=("$#")
for i in "${a[#]}" ; do
echo "$i"
done
}
names=(jim jerry jeff "jim bob")
foo "${names[#]}"

For example like this:
my_array[0]="jim"
my_array[1]="jerry"
function foo
{
#get the size of the array
n=${#my_array[*]}
for (( Idx = 0; Idx < $n; ++Idx )); do
echo "${my_array[$Idx]}"
done
}

Related

How to use/call variables from another function in bash

I am learning to play around with functions in bash. I have the first function read_file() that reads /etc/file and replaces ':'with a space between words (e.g root:x:0:0:root ... becomes root x 0 0 root ... ). I then want to be able to manipulate output from individual words in each of the lines.
My second function- display__user_shell() prints our the shell for each corresponding users as is in the /etc/file.
My problem is figuring out how to call the first function read_file() and using its variables in the display__user_shell function.
I have been able to do the above when using input from a single line rather than reading from a file.
i just called new_data -i.e $new_data from the display__user_shell() function
read_file() {
read -p "Enter file" file
while read line
do
newlin=$(echo $line | tr ":" " ")
echo newlin
done
}
oldIFS=$IFS
IFS=" "
ct=0
display__user_shell() {
readfile
for item in $newlin;
do
[ $ct -eq 0 ] && name="$item";
[ $ct -eq 6 ] && name="$item";
done
echo "$user's shell is $shell"
}
IFS=$oldIFS
display__user_shell
the first line of the output should be..
root's shell is /bin/bash
Irrespective of the implementation there is an interesting question here: how to reference variables from one function in another function. The short answer is that you can:
$ a() { aye=bee; }
$ b() { echo "$aye"; }
$ a
$ b
bee
But this is a very bad idea - Bash has "unfortunate" scoping rules different from safer languages like Java, Python, or Ruby, and code like this is very hard to follow. Instead there are several patterns you can use to produce more readable code:
Print the value in the inner function and assign that to a value in the outer function:
a() {
echo 'bee'
}
b() {
aye="$(a)"
echo "$aye"
}
b # Prints "bee"
Call and assign to a variable the first function in the outer scope and use it in the second function:
a() {
echo 'bee'
}
aye="$(a)"
b() {
echo "$aye"
}
b # Prints "bee"
Treat the first and second functions as a pipeline, passing standard output of the first one to the standard input of the second one (read is a slow way to process a large file, but it'll serve as an example):
a() {
echo 'bee'
}
b() {
while read -r line
do
echo "$line"
done
}
a | b # Prints "bee"
Which one you choose depends on things like what else you intend to do with what a returns and whether a produces huge amounts of output.

copy the value of the variable rather than reference bash script

I am a bit new to the bash scripting. So please bear with me. I am trying to create a table and assign the values in for loop like this:
packages=("foo" "bar" "foobar")
packageMap=()
function test() {
i=0;
for package in "${packages[#]}"
do
echo $i
packageMap[$package]=$i
i=$(expr $i + 1)
done
}
test
echo the first value is ${packageMap["foo"]}
The output for this is:
0
1
2
the first value is 2
While my expected output is:
0
1
2
the first value is 0
So basically the variable's reference is being assigned to this rather than the value. How to solve this?
My bash version:
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
TIA
bash 3.2 only has indexed arrays, so packageMap[$package] only works as intended if $package is an integer, not an arbitrary string.
(What you are observing is $package being evaluated in an arithmetic context, where foo, bar, and foobar are recursively expanded until you get an integer value. Undefined variables expand to 0, so packageMap[foo] is equivalent to packageMap[0].)
If you were using bash 4 or later, you could use an associative array:
packages=("foo" "bar" "foobar")
declare -A packageMap
test () {
i=0
for package in "${packages[#]}"
do
echo $i
packageMap[$package]=$i
i=$(($i + 1))
done
}
Given that i is the same as the index of each element of packages, you could also write
test () {
for i in "${!packages[#]}"; do
package=${packages[i]}
packageMap[$package]=$i
done
}
instead of explicitly incrementing i.
As chep says, there are no associative arrays in bash 3. That said, if you don't mind wasting a bit of CPU, you can use functions to similar effect:
#!/bin/bash
packages=("foo" "bar" "foobar")
function packagemap () {
local i
for i in "${!packages[#]}"; do
[[ ${packages[$i]} = $1 ]] && echo "$i" && return
done
echo "unknown"
}
echo "the first value is $(packagemap "foo")"
The ${!array[#]} construct expands to the set of indices for the array, which for a normally non-associative array consist of incrementing integers starting at 0. But array members can be removed without the indices being renumbered (i.e. unset packages[1]), so it's important to be able to refer to actual indices rather than assuming they're sequential with for loop that simply counts.
And I note that you're using Darwin. Remember that you really need it, you can install bash 4 using Homebrew or MacPorts.

Bash variable within varaible [duplicate]

x=1
c1=string1
c2=string2
c3=string3
echo $c1
string1
I'd like to have the output be string1 by using something like:
echo $(c($x))
So later in the script I can increment the value of x and have it output string1, then string2 and string3.
Can anyone point me in the right direction?
See the Bash FAQ: How can I use variable variables (indirect variables, pointers, references) or associative arrays?
To quote their example:
realvariable=contents
ref=realvariable
echo "${!ref}" # prints the contents of the real variable
To show how this is useful for your example:
get_c() { local tmp; tmp="c$x"; printf %s "${!tmp}"; }
x=1
c1=string1
c2=string2
c3=string3
echo "$(get_c)"
If, of course, you want to do it the Right Way and just use an array:
c=( "string1" "string2" "string3" )
x=1
echo "${c[$x]}"
Note that these arrays are zero-indexed, so with x=1 it prints string2; if you want string1, you'll need x=0.
Try this:
eval echo \$c$x
Like others said, it makes more sense to use array in this case.
if you have bash 4.0, you can use associative arrays.. Or you can just use arrays. Another tool you can use is awk
eg
awk 'BEGIN{
c[1]="string1"
c[2]="string2"
c[3]="string3"
for(x=1;x<=3;x++){
print c[x]
}
}'

Create associative array in bash 3

After thoroughly searching for a way to create an associative array in bash, I found that declare -A array will do the trick. But the problem is, it is only for bash version 4 and the bash version the server has in our system is 3.2.16.
How can I achieve some sort of associative array-like hack in bash 3? The values will be passed to a script like
ARG=array[key];
./script.sh ${ARG}
EDIT: I know that I can do this in awk, or other tools but strict bash is needed for the scenario I am trying to solve.
Bash 3 has no associative arrays, so you're going to have to use some other language feature(s) for your purpose. Note that even under bash 4, the code you wrote doesn't do what you claim it does: ./script.sh ${ARG} does not pass the associative array to the child script, because ${ARG} expands to nothing when ARG is an associative array. You cannot pass an associative array to a child process, you need to encode it anyway.
You need to define some argument passing protocol between the parent script and the child script. A common one is to pass arguments in the form key=value. This assumes that the character = does not appear in keys.
You also need to figure out how to represent the associative array in the parent script and in the child script. They need not use the same representation.
A common method to represent an associative array is to use separate variables for each element, with a common naming prefix. This requires that the key name only consists of ASCII letters (of either case), digits and underscores. For example, instead of ${myarray[key]}, write ${myarray__key}. If the key is determined at run time, you need a round of expansion first: instead of ${myarray[$key]}, write
n=myarray__${key}; echo ${!n}
For an assignment, use printf -v. Note the %s format to printf to use the specified value. Do not write printf -v "myarray__${key}" %s "$value" since that would treat $value as a format and perform printf % expansion on it.
printf -v "myarray__${key}" %s "$value"
If you need to pass an associative array represented like this to a child process with the key=value argument representation, you can use ${!myarray__*} to enumerate over all the variables whose name begins with myarray__.
args=()
for k in ${!myarray__*}; do
n=$k
args+=("$k=${!n}")
done
In the child process, to convert arguments of the form key=value to separate variables with a prefix:
for x; do
if [[ $x != *=* ]]; then echo 1>&2 "KEY=VALUE expected, but got $x"; exit 120; fi
printf -v "myarray__${x%%=*}" %s "${x#*=}"
done
By the way, are you sure that this is what you need? Instead of calling a bash script from another bash script, you might want to run the child script in a subshell instead. That way it would inherit from all the variables of the parent.
Here is another post/explanation on associative arrays in bash 3 and older using parameter expansion:
https://stackoverflow.com/a/4444841
Gilles' method has a nice if statement to catch delimiter issues, sanitize oddball input ...etc. Use that.
If you are somewhat familiar with parameter expansion:
http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
To use in your scenario [ as stated: sending to script ]:
Script 1:
sending_array.sh
# A pretend Python dictionary with bash 3
ARRAY=( "cow:moo"
"dinosaur:roar"
"bird:chirp"
"bash:rock" )
bash ./receive_arr.sh "${ARRAY[#]}"
Script 2: receive_arr.sh
argAry1=("$#")
function process_arr () {
declare -a hash=("${!1}")
for animal in "${hash[#]}"; do
echo "Key: ${animal%%:*}"
echo "Value: ${animal#*:}"
done
}
process_arr argAry1[#]
exit 0
Method 2, sourcing the second script:
Script 1:
sending_array.sh
source ./receive_arr.sh
# A pretend Python dictionary with bash 3
ARRAY=( "cow:moo"
"dinosaur:roar"
"bird:chirp"
"bash:rock" )
process_arr ARRAY[#]
Script 2: receive_arr.sh
function process_arr () {
declare -a hash=("${!1}")
for animal in "${hash[#]}"; do
echo "Key: ${animal%%:*}"
echo "Value: ${animal#*:}"
done
}
References:
Passing arrays as parameters in bash
If you don't want to handle a lot of variables, or keys are simply invalid variable identifiers, and your array is guaranteed to have less than 256 items, you can abuse function return values. This solution does not require any subshell as the value is readily available as a variable, nor any iteration so that performance screams. Also it's very readable, almost like the Bash 4 version.
Here's the most basic version:
hash_index() {
case $1 in
'foo') return 0;;
'bar') return 1;;
'baz') return 2;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo"
echo ${hash_vals[$?]}
More details and variants in this answer
You can write the key-value pairs to a file and then grep by key. If you use a pattern like
key=value
then you can egrep for ^key= which makes this pretty safe.
To "overwrite" a value, just append the new value at the end of the file and use tail -1 to get just the last result of egrep
Alternatively, you can put this information into a normal array using key=value as value for the array and then iterator over the array to find the value.
This turns out to be ridiculously easy. I had to convert a bash 4 script that used a bunch of associative arrays to bash 3. These two helper functions did it all:
array_exp() {
exp=${#//[/__}
eval "${exp//]}"
}
array_clear() {
unset $(array_exp "echo \${!$1__*}")
}
I'm flabbergasted that this actually works, but that's the beauty of bash.
E.g.
((all[ping_lo] += counts[ping_lo]))
becomes
array_exp '((all[ping_lo] += counts[ping_lo]))'
Or this print statement:
printf "%3d" ${counts[ping_lo]} >> $return
becomes
array_exp 'printf "%3d" ${counts[ping_lo]}' >> $return
The only syntax that changes is clearing. This:
counts=()
becomes
array_clear counts
and you're set. You could easily tell array_exp to recognize expressions like "=()" and handle them by rewriting them as array_clear expressions, but I prefer the simplicity of the above two functions.

set values to elements of an array in a function

In a bash script, I would like to put the following code that assigns values to each element of several arrays into a function
for (( i=0 ; i < ${#themes[#]} ; i+=1 )); do
c_bit_mins[i]=-5
c_bit_maxs[i]=15
gamma_bit_mins[i]=-15
gamma_bit_maxs[i]=3
done
i.e. something like
function set_values()
{
for (( i=0 ; i < ${#themes[#]} ; i+=1 )); do
c_bit_mins[i]=-5
c_bit_maxs[i]=15
gamma_bit_mins[i]=-15
gamma_bit_maxs[i]=3
done
}
How to do it? Especially when these arrays are not seen as global inside the function.
Thanks and regards!
You can make a variable local by using the local command:
local c_bit_mins c_bit_maxs gamma_bit_mins gamma_bit_maxs
However, you can't "return" an array out of a shell function. The return value of a shell function is always an integer. Non-integer values are typically "returned" by echoing them and reading them back in using $(...) in the surrounding program. But that will be completely weird to do with arrays and four of them.
The arrays are global unless you declare them to be local.
$ unset a
$ test() { echo ${a[3]}; a[4]=456; }
$ a[3]=123
$ test
123
$ echo ${a[4]}
456
$ echo ${a[3]}
123

Resources