bash 4.x assoc array loses keys in receiving function - bash

I am trying to pass an associative array from one function to another, and am losing named index keys (e.g., filepath, search in example below) though the array passed in can access its elements correctly using indexes 0, 1. I must be doing something slightly wrong with bash syntax, but can't quite figure out where. Any help appreciated.
Using GNU bash, version 4.3.8 from Ubuntu 14.04
Just below is bash code example, and at bottom is output
#! /bin/bash
function test_function {
func_data=("${#}")
# without brackets above cannot access func_data[1]
# local ${!func_data}
# the above local statement does not seem to help either way
echo ""
for K in "${!func_data[#]}"; do echo $K; done
echo ""
echo "func_data : ${func_data}"
echo "func_data[filepath] : ${func_data[filepath]}"
echo "func_data[search] : ${func_data[search]}"
# all three echos above output first element of array,
# which is 'style "default" {' during first loop
# Can access array elements 0, 1 but no longer via filepath, search
echo "func_data[0] : ${func_data[0]}"
echo "func_data[1] : ${func_data[1]}"
echo "!func_data[#] : ${!func_data[#]}"
# echo above outputs '0 1' so indexes now are now zero based?
echo "func_data[#] : ${func_data[#]}"
# echo above outputs all array elements 'style "default" { ~/.gtkrc-2.0'
}
# In BASH, local variable scope is the current function and every
# child function called from it, so provide a function main to
# make it possible to utilize variable scope to fix issues
function main {
echo ""
declare -A gtkrc2=()
gtkrc2[filepath]="~/.gtkrc-2.0"
gtkrc2[search]="style \"default\" {"
echo "gtkrc2 filepath : ${gtkrc2[filepath]}"
echo "gtkrc2 search : ${gtkrc2[search]}"
test_function "${gtkrc2[#]}"
echo ""
declare -A gtkcss=()
gtkcss[filepath]="~/.config/gtk-3.0/gtk.css"
gtkcss[search]=".scrollbar {"
echo "gtkcss filepath : ${gtkcss[filepath]}"
echo "gtkcss search : ${gtkcss[search]}"
test_function "${gtkcss[#]}"
}
main
---------- OUTPUT ----------
gtkrc2 filepath : ~/.gtkrc-2.0
gtkrc2 search : style "default" {
func_data : style "default" {
func_data[filepath] : style "default" {
func_data[search] : style "default" {
func_data[0] : style "default" {
func_data[1] : ~/.gtkrc-2.0
!func_data[#] : 0 1
func_data[#] : style "default" { ~/.gtkrc-2.0
gtkcss filepath : ~/.config/gtk-3.0/gtk.css
gtkcss search : .scrollbar {
func_data : .scrollbar {
func_data[filepath] : .scrollbar {
func_data[search] : .scrollbar {
func_data[0] : .scrollbar {
func_data[1] : ~/.config/gtk-3.0/gtk.css
!func_data[#] : 0 1
func_data[#] : .scrollbar { ~/.config/gtk-3.0/gtk.css

This may or may not be the "correct" way to do this but this is the best I can figure out. Any suggestions from others are welcome:
function test_function {
arrname=$1
idxlist="$2"
echo ""
echo "Array passed=$arrname"
for idx in $idxlist; do
elemname=$arrname[$idx]
echo "idx=$idx, elem=${!elemname}"
done
}
# In BASH, local variable scope is the current function and every
# child function called from it, so provide a function main to
# make it possible to utilize variable scope to fix issues
function main {
echo ""
declare -A gtkrc2=()
gtkrc2[filepath]="~/.gtkrc-2.0"
gtkrc2[search]="style \"default\" {"
echo "gtkrc2 filepath : ${gtkrc2[filepath]}"
echo "gtkrc2 search : ${gtkrc2[search]}"
test_function gtkrc2 "${!gtkrc2[*]}"
echo ""
declare -A gtkcss=()
gtkcss[filepath]="~/.config/gtk-3.0/gtk.css"
gtkcss[search]=".scrollbar {"
echo "gtkcss filepath : ${gtkcss[filepath]}"
echo "gtkcss search : ${gtkcss[search]}"
test_function gtkcss "${!gtkcss[*]}"
}
main
In particular:
To pass each associative array to the function, we pass both the name of the array, and its list of indices
Inside the function, the array name and index list are taken from the positional parameters
We may then loop over the list of indices and obtain the corresponding value of each element. This is done by first generating the name of the element, and then using the ! indirection modifier to get the actual value.
This technique of indirection of arrays is described in this question, but only addresses indexed arrays, and not associative arrays; passing the index list is one way I can think of to get this to work for associative arrays.

Related

bash recursion automatically ends after single level

Why is this make_request function ending just after a single traversal?
make_request(){
path="${1//' '/'%20'}"
echo $path
mkdir -p $HOME/"$1"
$(curl --output $HOME/"$1"/$FILE_NAME -v -X GET $BASE_URL"/"$API_METHOD"/"$path &> /dev/null)
# sample response from curl
# {
# "count":2,
# "items": [
# {"path": "somepath1", "type": "folder"},
# {"path": "somepath2", "type": "folder"},
# ]
# }
count=$(jq ".count" $HOME/"$1"/$FILE_NAME)
for (( c=0; c<$count; c++ ))
do
child=$(jq -r ".items[$c] .path" $HOME/"$1"/$FILE_NAME);
fileType=$(jq -r ".items[$c] .type" $HOME/"$1"/$FILE_NAME);
if [ "$fileType" == "folder" ]; then
make_request "$child"
fi
done
}
make_request "/"
make_request "/" should give the following output:
/folder
/folder/folder1-1
/folder/folder1-1/folder1-2
/folder/foler2-1
/folder/folder2-1/folder2-2
/folder/folder2-1/folder2-3 ...
but I am getting the following:
/folder
/folder/folder1-1
/folder/folder1-1/folder1-2
You are using global variables everywhere. Therefore, the inner call changes the loop variables c and count of the outer call, resulting in bogus.
Minimal example:
f() {
this_is_global=$1
echo "enter $1: $this_is_global"
((RANDOM%2)) && f "$(($1+1))"
echo "exit $1: $this_is_global"
}
Running f 1 prints something like
enter 1: 1
enter 2: 2
enter 3: 3
exit 3: 3
exit 2: 3
exit 1: 3
Solution: Make the variables local by writing local count=$(...) and so on. For your loop, you have to put an additional statement local c above the for.
As currently written all variables have global scope; this means that all function calls are overwriting and referencing the same set of variables, this in turn means that when a child function returns to the parent the parent will find its variables have been overwritten by the child, this in turn leads to the type of behavior seen here.
In this particular case the loop variable c leaves the last child process with a value of c=$count and all parent loops now see c=$count and thus exit; it actually gets a bit more interesting because count is also changing with each function call. The previous comment to add set -x (aka enable debug mode) before the first function call should show what's going on with each variable at each function call.
What OP wants to do is insure each function is working with a local copy of a variable. The easiest approach is to add a local <variable_list> at the top of the function, making sure to list all variables that should be treated as 'local', eg:
local path count c child fileType
change variables to have local scope instead of global.
...
local count; # <------ VARIABLE MADE LOCAL
count=$(jq ".count" $HOME/"$1"/$FILE_NAME)
local c; # <------ VARIABLE MADE LOCAL
for (( c=0; c<$count; c++ ))
do
....
done
...

How to give an empty array to function in bash? [duplicate]

This question already has answers here:
How to pass array as an argument to a function in Bash
(8 answers)
Closed 6 years ago.
I am learning bash.
Now I would like to give an empty array to a function. However, it does not work. Please refer to following,
function display_empty_array_func() {
local -a aug1=$1
local -a aug2=${2:-no input}
echo "array aug1 = ${aug1[#]}"
echo "array aug2 = ${aug2[#]}"
}
declare -a empty_array=()
display_empty_array_func "${empty_array[#]}" "1"
The outputs are following,
array aug1 = 1 # I expected it is empty
array aug2 = no input # I expected it is 1
In my understanding, quoting variable allow us to give an empty variable,
like following,
function display_empty_variable_func() {
local aug1=$1
local aug2=${2:-no input}
echo "variable aug1 = ${aug1}"
echo "variable aug2 = ${aug2}"
}
display_empty_variable_func "" "1"
And its outputs are following
variable aug1 = # it is empty as expected
variable aug2 = 1 # it is 1 as expected
I don't know what is the problem with passing am empty array.
Someone who knows mechanism or solutions. Please let me know it.
Thank you very much.
enter image description hereIf positional parameter is empty, shell script & shell function will not consider that value and instead it took the next non empty value as its value(positional parameter value).
If we want to take the empty value, must we have to put that value in quotes.
Ex: I'm having small script like
cat test_1.sh
#!/bin/bash
echo "First Parameter is :"$1;
echo "Second Parameter is :"$2;
case -1
If i executed this script as
sh test_1.sh
First Parameter is :
Second Parameter is :
Above two lines are empty because i have not given positional parameter values to script.
case-2
If i executed this script as
sh test_1.sh 1 2
First Parameter is :1
Second Parameter is :2
case-2
If i executed this script as
sh test_1.sh 2 **# here i given two spaces in between .sh and 2 and i thought 1st param is space and second param is 2 but**
First Parameter is :2
Second Parameter is :
The output looks like above. My first statement will apply here.
case-4
If i executed this script as
sh test_1.sh " " 2
First Parameter is :
Second Parameter is :2
Here i kept space in quotes. Now i'm able to access the space value.
**This will be help full for you. But for your requirement please use below code.**
In this you have **quote** the space value(""${empty_array[#]}"") which is coming from **an empty array**("${empty_array[#]}"). So you have to use additional quote to an empty array.
function display_empty_array_func() {
local -a aug1=$1
local -a aug2=${2:-no input}
echo "array aug1 = ${aug1[#]}"
echo "array aug2 = ${aug2[#]}"
}
declare -a empty_array=()
display_empty_array_func ""${empty_array[#]}"" "1"
The output is:
array aug1 =
array aug2 = 1

Returning a Dictionary from a Bash Function

I want to have a function in bash, which create a Dictionary as a local variable. Fill the Dictionary with one element and then return this dictionary as output.
Is the following code correct?
function Dictionary_Builder ()
{
local The_Dictionary
unset The_Dictionary
declare -A The_Dictionary
The_Dictionary+=(["A_Key"]="A_Word")
return $The_Dictionary
}
How can I access to the output of the function above? Can I use the following command in bash?
The_Output_Dictionary=Dictionary_Builder()
To capture output of a command or function, use command substitution:
The_Output_Dictionary=$(Dictionary_Builder)
and output the value to return, i.e. replace return with echo. You can't easily return a structure, though, but you might try returning a string that declares it (see below).
There's no need to use local and unset in the function. declare creates a local variable inside a function unless told otherwise by -g. The newly created variable is always empty.
To add a single element to an empty variable, you can assign it directly, no + is needed:
The_Dictionary=([A_Key]=A_Word)
In other words
#!/bin/bash
Dictionary_Builder () {
declare -A The_Dictionary=([A_Key]=A_Word)
echo "([${!The_Dictionary[#]}]=${The_Dictionary[#]})"
}
declare -A The_Output_Dictionary="$(Dictionary_Builder)"
echo key: ${!The_Output_Dictionary[#]}
echo value: ${The_Output_Dictionary[#]}
For multiple keys and values, you need to loop over the dictionary:
Dictionary_Builder () {
declare -A The_Dictionary=([A_Key]=A_Word
[Second]=Third)
echo '('
for key in "${!The_Dictionary[#]}" ; do
echo "[$key]=${The_Dictionary[$key]}"
done
echo ')'
}
declare -A The_Output_Dictionary="$(Dictionary_Builder)"
for key in "${!The_Output_Dictionary[#]}" ; do
echo key: $key, value: ${The_Output_Dictionary[$key]}
done
The answer by #choroba is what I was looking for. However, my dictionary values also had white spaces in them and the above answer didn't work outright. What worked was a minor variation of the above answer.
#!/bin/bash
function Dictionary_Builder() {
declare -A dict=(['title']="Title of the song"
['artist']="Artist of the song"
['album']="Album of the song"
)
echo '('
for key in "${!dict[#]}" ; do
echo "['$key']='${dict[$key]}'"
done
echo ')'
}
declare -A Output_Dictionary="$(Dictionary_Builder)"
for key in "${!Output_Dictionary[#]}" ; do
echo "${key}: '"${Output_Dictionary[$key]}"'"
done
Note the extra single quotes on the 2nd echo line which made it possible to output values with whitespaces in them.

Error while passing array as function parameter in bash

This is the code I am dealing with:
function execute {
task="$1"
servername="$2"
"$task" "${servername[#]}"
}
function someOtherThing {
val=$1
echo "$val"
}
function makeNecessaryDirectory {
arr=("$#")
echo "${arr[#]}"
}
dem=(1 2 3 4 5)
execute someOtherThing 1
execute makeNecessaryDirectory "${dem[#]}"
Output:
1
1
Expected output:
1
1 2 3 4 5
How to achieve this? I found no error logically.
Side question:
Is it safe to always receive 2nd parameter as an array inside execute so that it can deal with both of the dependent functions, or i should have an explicit check inside execute?
As explained in my comment
You are passing the array as individual args to execute and then only passing the first one the makeNecessaryDirectory, so $# is just the single argument passed which is 1.
I would do it this way, I have added comments to the parts i have changed.
It is only minor changes but should hopefully work for you.
#!/bin/bash
function execute {
task="$1"
servername="$2"
"$task" "$servername"
#No longer pass array here just pass servername as the name of array
}
function someOtherThing {
val=$1
echo "$val"
}
function makeNecessaryDirectory {
local arr=("${!1}")
#Indirect reference to the first arg passed to function which is now the
#name of the array
for i in "${arr[#]}";
do
echo "$i"
done
}
dem=(1 2 3 4 5)
execute someOtherThing 1
execute makeNecessaryDirectory 'dem[#]' #Pass the array name instead of it's contents

idioms for returning multiple values in shell scripting

Are there any idioms for returning multiple values from a bash function within a script?
http://tldp.org/LDP/abs/html/assortedtips.html describes how to echo multiple values and process the results (e.g., example 35-17), but that gets tricky if some of the returned values are strings with spaces in.
A more structured way to return would be to assign to global variables, like
foo () {
FOO_RV1="bob"
FOO_RV2="bill"
}
foo
echo "foo returned ${FOO_RV1} and ${FOO_RV2}"
I realize that if I need re-entrancy in a shell script I'm probably doing it wrong, but I still feel very uncomfortable throwing global variables around just to hold return values.
Is there a better way? I would prefer portability, but it's probably not a real limitation if I have to specify #!/bin/bash.
In the special case where your values never contain spaces, this read trick can be a simple solution:
get_vars () {
#...
echo "value1" "value2"
}
read var1 var2 < <(get_vars)
echo "var1='$var1', var2='$var2'"
But of course, it breaks as soon as there is a space in one of the values. You could modify IFS and use a special separator in your function's echo, but then the result is not really simpler than the other suggested solutions.
This question was posted 5 years ago, but I have some interesting answer to post. I have just started learning bash, and I also encounter to the same problem as you did. I think this trick might be helpful:
#!/bin/sh
foo=""
bar=""
my_func(){
echo 'foo="a"; bar="b"'
}
eval $(my_func)
echo $foo $bar
# result: a b
This trick is also useful for solving a problem when a child process can not send back a value to its parent process.
Much as I love shell, it's probably the case that as soon as you're throwing arbitrary structured data around, Unix bourne/posix shell is not the right choice.
If there are characters which do not occur inside fields, then separate with one of those. The classic example is /etc/passwd, /etc/group and various other files which use a colon as a field separator.
If using a shell which can handle a NUL character inside strings, then joining on the NUL and separating on it (via $IFS or whatever) can work well. But several common shells, including bash, break on NUL. A test would be an old .sig of mine:
foo=$'a\0b'; [ ${#foo} -eq 3 ] && echo "$0 rocks"
Even if that would work for you, you've just reached one of the warning signs that it's time to switch to a more structured language (Python, Perl, Ruby, Lua, Javascript ... pick your preferred poison). Your code is likely to become hard to maintain; even if you can, there's a smaller pool of people who'll understand it well enough to maintain it.
Yet another way:
function get_tuple()
{
echo -e "Value1\nValue2"
}
IFS=$'\n' read -d '' -ra VALUES < <(get_tuple)
echo "${VALUES[0]}" # Value1
echo "${VALUES[1]}" # Value2
In order version of Bash which doesn't support nameref (introduced in Bash 4.3-alpha) I may define helper function in which the return value is assigned to the given variable. It's sort of like using eval to do the same kind of variable assignment.
Example 1
## Add two complex numbers and returns it.
## re: real part, im: imaginary part.
##
## Helper function named by the 5th positional parameter
## have to have been defined before the function is called.
complexAdd()
{
local re1="$1" im1="$2" re2="$3" im2="$4" fnName="$5" sumRe sumIm
sumRe=$(($re1 + $re2))
sumIm=$(($im1 + $im2))
## Call the function and return 2 values.
"$fnName" "$sumRe" "$sumIm"
}
main()
{
local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
## Define the function to receive mutiple return values
## before calling complexAdd().
retValAssign() { bazRe="$1"; bazIm="$2"; }
## Call comlexAdd() for the first time.
complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 'retValAssign'
## Redefine the function to receive mutiple return values.
retValAssign() { quxRe="$1"; quxIm="$2"; }
## Call comlexAdd() for the second time.
complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 'retValAssign'
echo "foo = $fooRe + $fooIm i"
echo "bar = $barRe + $barIm i"
echo "baz = foo + bar = $bazRe + $bazIm i"
echo "qux = bar + baz = $quxRe + $quxIm i"
}
main
Example 2
## Add two complex numbers and returns it.
## re: real part, im: imaginary part.
##
## Helper functions
## getRetRe(), getRetIm(), setRetRe() and setRetIm()
## have to have been defined before the function is called.
complexAdd()
{
local re1="$1" im1="$2" re2="$3" im2="$4"
setRetRe "$re1"
setRetRe $(($(getRetRe) + $re2))
setRetIm $(($im1 + $im2))
}
main()
{
local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
## Define getter and setter functions before calling complexAdd().
getRetRe() { echo "$bazRe"; }
getRetIm() { echo "$bazIm"; }
setRetRe() { bazRe="$1"; }
setRetIm() { bazIm="$1"; }
## Call comlexAdd() for the first time.
complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm"
## Redefine getter and setter functions.
getRetRe() { echo "$quxRe"; }
getRetIm() { echo "$quxIm"; }
setRetRe() { quxRe="$1"; }
setRetIm() { quxIm="$1"; }
## Call comlexAdd() for the second time.
complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm"
echo "foo = $fooRe + $fooIm i"
echo "bar = $barRe + $barIm i"
echo "baz = foo + bar = $bazRe + $bazIm i"
echo "qux = bar + baz = $quxRe + $quxIm i"
}
main
you can make use of associative arrays with you have bash 4 eg
declare -A ARR
function foo(){
...
ARR["foo_return_value_1"]="VAR1"
ARR["foo_return_value_2"]="VAR2"
}
you can combine them as strings.
function foo(){
...
echo "$var1|$var2|$var3"
}
then whenever you need to use those return values,
ret="$(foo)"
IFS="|"
set -- $ret
echo "var1 one is: $1"
echo "var2 one is: $2"
echo "var3 one is: $3"
I would go for the solution I suggested here, but using an array variable instead. Older bash:es don't support associative arrays.
E.g.,
function some_func() # ARRVAR args...
{
local _retvar=$1 # I use underscore to avoid clashes with return variable names
local -a _out
# ... some processing ... (_out[2]=xxx etc.)
eval $_retvar='("${_out[#]}")'
}
Calling site:
function caller()
{
local -a tuple_ret # Do not use leading '_' here.
# ...
some_func tuple_ret "arg1"
printf " %s\n" "${tuple_ret[#]}" # Print tuple members on separate lines
}
Later version of Bash supports nameref. Use declare -n var_name to give var_name the nameref attribute. nameref gives your function the ability to "pass by reference" which is commonly used in C++ functions to return multiple values. According to Bash man page:
A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced or assigned to, the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to the function.
The following are some interactive command line examples.
Example 1:
$ unset xx yy
$ xx=16
$ yy=xx
$ echo "[$xx] [$yy]"
[16] [xx]
$ declare -n yy
$ echo "[$xx] [$yy]"
[16] [16]
$ xx=80
$ echo "[$xx] [$yy]"
[80] [80]
$ yy=2016
$ echo "[$xx] [$yy]"
[2016] [2016]
$ declare +n yy # Use -n to add and +n to remove nameref attribute.
$ echo "[$xx] [$yy]"
[2016] [xx]
Example 2:
$ func()
> {
> local arg1="$1" arg2="$2"
> local -n arg3ref="$3" arg4ref="$4"
>
> echo ''
> echo 'Local variables:'
> echo " arg1='$arg1'"
> echo " arg2='$arg2'"
> echo " arg3ref='$arg3ref'"
> echo " arg4ref='$arg4ref'"
> echo ''
>
> arg1='1st value of local assignment'
> arg2='2st value of local assignment'
> arg3ref='1st return value'
> arg4ref='2nd return value'
> }
$
$ unset foo bar baz qux
$
$ foo='value of foo'
$ bar='value of bar'
$ baz='value of baz'
$ qux='value of qux'
$
$ func foo bar baz qux
Local variables:
arg1='foo'
arg2='bar'
arg3ref='value of baz'
arg4ref='value of qux'
$
$ {
> echo ''
> echo '2 values are returned after the function call:'
> echo " foo='$foo'"
> echo " bar='$bar'"
> echo " baz='$baz'"
> echo " qux='$qux'"
> }
2 values are returned after the function call:
foo='value of foo'
bar='value of bar'
baz='1st return value'
qux='2nd return value'
I am new to bash, But found this code helping.
function return_multiple_values() {
eval "$1='What is your name'"
eval "$2='my name is: BASH'"
}
return_var=''
res2=''
return_multiple_values return_var res2
echo $return_var
echo $res2
Shell script functions can only return the exit status of last command executed or the exit status of that function specified explicitly by a return statement.
To return some string one way may be this:
function fun()
{
echo "a+b"
}
var=`fun` # Invoke the function in a new child shell and capture the results
echo $var # use the stored result
This may reduce your discomfort although it adds the overhead of creation of a new shell and hence would be marginally slower.

Resources