Error while passing array as function parameter in bash - 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

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
...

Bash passing array as reference to function gets clobbered

I am using GNU bash version 4.4.12 and coming across an unusual situation. I am trying to pass an array by reference to a function. So the following code works as expected.
function test() {
local -n varK=${2}
local varJ=$(( ${1} + 10 ))
echo "${varJ}, ${varK[#]}"
}
varI=( 1 2 )
varJ=3
echo "result = '$( test 1 varI )'" # result = '11, 1 2'
echo "varI = '${varI[#]}'" # varI = '1 2'
echo "varJ = '${varJ}'" # varJ = '3'
The weird situation is that if I use the variable varI within the function, even if I define it as a local variable, then the variable varI gets clobbered.
function test() (
local -n varK=${2}
local varI=$(( ${1} + 10 ))
echo "${varI}, ${varK[#]}"
)
varI=( 1 2 )
varJ=3
echo "result = '$( test 1 varI )'" # result = '11, 11'
echo "varI = '${varI[#]}'" # varI = '1 2'
echo "varJ = '${varJ}'" # varJ = '3'
Does not the command local -n varK=${2} make a local copy of the array passed by reference? Also, if I am running the function in a subshell (and calling it as a subshell, should it not affect the parent process as all the documents claim?
The nameref doesn't know anything about the name it refers to; it is a simple alias. local -n varK=varI simply states that whenever you use the parameter name varK, pretend it is really varI, whatever that name means. By the time you use varK, varI is a local variable with the value 11, so that's what varK is as well. (Part of the confusion may also lie in how bash treats a non-array name used with array syntax; ${varI[#]} and ${varI} are equivalent whether or not the array attribute is set on the name.)
So in order to use varI within my function and still have access to referenced array, I made a local copy of the reference array:
local varJ=( "${varK[#]}" )

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

Using functions in Bash

Is this the correct syntax for parameterized functions?
#!/bin/bash
twoPow()
{
prod=1
for((i=0;i<$1;i++));
do
prod=$prod*2
done
return prod
}
echo "Enter a number"
read num
echo `twoPow $num`
Output:
bash sample.sh
Enter a number
3
sample.sh: line 10: return: prod: numeric argument required
Part 2:
I removed the return, but what should I do if I want to run multiple times and store results like below? How can I make this work?
#!/bin/bash
tp1=1
tp2=1
twoPow()
{
for((i=0;i<$1;i++));
do
$2=$(($prod*2))
done
}
twoPow 3 tp1
twoPow 2 tp2
echo $tp1+$tp2
In Bash scripts you can't return values to the calling code.
The simplest way to emulate "returning" a value as you would in other languages is to set a global variable to the intended result.
Fortunately in bash all variables are global by default. Just try outputting the value of prod after calling that function.
A sample Bash function definition and call with a few parameters and return values. It may be useful and it works.
#!/bin/sh
## Define function
function sum()
{
val1=$1
val2=$2
val3=`expr $val1 + $val2`
echo $val3
}
# Call function with two parameters and it returns one parameter.
ret_val=$(sum 10 20)
echo $ret_val

Is it possible to get the function name in function body? [duplicate]

This question already has answers here:
How to determine function name from inside a function
(5 answers)
Closed 5 years ago.
In BASH, is it possible to get the function name in function body? Taking following codes as example, I want to print the function name "Test" in its body, but "$0" seems to refer to the script name instead of the function name. So how to get the function name?
#!/bin/bash
function Test
{
if [ $# -lt 1 ]
then
# how to get the function name here?
echo "$0 num" 1>&2
exit 1
fi
local num="${1}"
echo "${num}"
}
# the correct function
Test 100
# missing argument, the function should exit with error
Test
exit 0
Try ${FUNCNAME[0]}. This array contains the current call stack. To quote the man page:
FUNCNAME
An array variable containing the names of all shell functions
currently in the execution call stack. The element with index 0
is the name of any currently-executing shell function. The bot‐
tom-most element is "main". This variable exists only when a
shell function is executing. Assignments to FUNCNAME have no
effect and return an error status. If FUNCNAME is unset, it
loses its special properties, even if it is subsequently reset.
The name of the function is in ${FUNCNAME[ 0 ]} FUNCNAME is an array containing all the names of the functions in the call stack, so:
$ ./sample
foo
bar
$ cat sample
#!/bin/bash
foo() {
echo ${FUNCNAME[ 0 ]} # prints 'foo'
echo ${FUNCNAME[ 1 ]} # prints 'bar'
}
bar() { foo; }
bar

Resources