How to Pass an Array as Function Argument?
Struggling for a While Passing an Array as Argument but It's Not Working Anyway. I've Tried Like Below: #! /Bin/Bash Function copyFiles{ Arr="$1" for I in...
Struggling for a while passing an array as argument but it's not working anyway. I've tried like below:
#! /bin/bash
function copyFiles{
arr="$1"
for i in "${arr[@]}";
do
echo "$i"
done
}
array=("one" "two" "three")
copyFiles $array
An answer with explanation would be nice.
Edit: Basically, i will eventually call the function from another script file. Plz explain the constraints if possible.
10 Answers
Expanding an array without an index only gives the first element, use
copyFiles "${array[@]}"instead of
copyFiles $arrayUse a she-bang
#!/bin/bashUse the correct function syntax
Valid variants are
function copyFiles {…} function copyFiles(){…} function copyFiles() {…}instead of
function copyFiles{…}Use the right syntax to get the array parameter
arr=("$@")instead of
arr="$1"
Therefore
#!/bin/bash
function copyFiles() {
arr=("$@")
for i in "${arr[@]}";
do
echo "$i"
done
}
array=("one 1" "two 2" "three 3")
copyFiles "${array[@]}"
Output is (my script has the name foo)
$ ./foo
one 1
two 2
three 3
If you want to pass one or more arguments AND an array, I propose this change to the script of @A.B.
Array should be the last argument and only one array can be passed
#!/bin/bash
function copyFiles() {
local msg="$1" # Save first argument in a variable
shift # Shift all arguments to the left (original $1 gets lost)
local arr=("$@") # Rebuild the array with rest of arguments
for i in "${arr[@]}";
do
echo "$msg $i"
done
}
array=("one" "two" "three")
copyFiles "Copying" "${array[@]}"
Output:
$ ./foo
Copying one
Copying two
Copying three
You could also pass the array as a reference. i.e.:
#!/bin/bash
function copyFiles {
local -n arr=$1
for i in "${arr[@]}"
do
echo "$i"
done
}
array=("one" "two" "three")
copyFiles array
but note that any modifications to arr will be made to array.
There are couple of problems. Here is the working form :
#!/bin/bash
function copyFiles {
arr=( "$@" )
for i in "${arr[@]}";
do
echo "$i"
done
}
array=("one" "two" "three")
copyFiles "${array[@]}"
There need to be at least a space between function declaration and
{You can not use
$array, asarrayis an array not a variable. If you want to get all the values of an array use"${array[@]}"In you main function declaration you need
arr="$@"as"${array[@]}"will expand to the indexed values separated by spaces, if you use$1you would get only the first value. To get all the values usearr="${arr[@]}".
Here follows a slightly larger example. For explanation, see the comments in the code.
#!/bin/bash -u
# ==============================================================================
# Description
# -----------
# Show the content of an array by displaying each element separated by a
# vertical bar (|).
#
# Arg Description
# --- -----------
# 1 The array
# ==============================================================================
show_array()
{
declare -a arr=("${@}")
declare -i len=${#arr[@]}
# Show passed array
for ((n = 0; n < len; n++))
do
echo -en "|${arr[$n]}"
done
echo "|"
}
# ==============================================================================
# Description
# -----------
# This function takes two arrays as arguments together with their sizes and a
# name of an array which should be created and returned from this function.
#
# Arg Description
# --- -----------
# 1 Length of first array
# 2 First array
# 3 Length of second array
# 4 Second array
# 5 Name of returned array
# ==============================================================================
array_demo()
{
declare -a argv=("${@}") # All arguments in one big array
declare -i len_1=${argv[0]} # Length of first array passad
declare -a arr_1=("${argv[@]:1:$len_1}") # First array
declare -i len_2=${argv[(len_1 + 1)]} # Length of second array passad
declare -a arr_2=("${argv[@]:(len_1 + 2):$len_2}") # Second array
declare -i totlen=${#argv[@]} # Length of argv array (len_1+len_2+2)
declare __ret_array_name=${argv[(totlen - 1)]} # Name of array to be returned
# Show passed arrays
echo -en "Array 1: "; show_array "${arr_1[@]}"
echo -en "Array 2: "; show_array "${arr_2[@]}"
# Create array to be returned with given name (by concatenating passed arrays in opposite order)
eval ${__ret_array_name}='("${arr_2[@]}" "${arr_1[@]}")'
}
########################
##### Demo program #####
########################
declare -a array_1=(Only 1 word @ the time) # 6 elements
declare -a array_2=("Space separated words," sometimes using "string paretheses") # 4 elements
declare -a my_out # Will contain output from array_demo()
# A: Length of array_1
# B: First array, not necessary with string parentheses here
# C: Length of array_2
# D: Second array, necessary with string parentheses here
# E: Name of array that should be returned from function.
# A B C D E
array_demo ${#array_1[@]} ${array_1[@]} ${#array_2[@]} "${array_2[@]}" my_out
# Show that array_demo really returned specified array in my_out:
echo -en "Returns: "; show_array "${my_out[@]}"
The best way is to pass as position arguments. Nothing else. You may pass as string, but this way may cause some troubles. Example:
array=(one two three four five)
function show_passed_array(){
echo $@
}
or
function show_passed_array(){
while $# -gt 0;do
echo $1;shift
done
}
show_passed_array ${array[@]}
output:
one two three four five
You mean if array value has space symbols you must quote elements first before pass for accessing value by index in function use $1 $2 $3 ... position parameters. Where index 0 -> 1, 1 -> 2,... To iterate access it is best to use always $1 and after Shift. Nothing additional is needed. You may pass arguments without any array like this:
show_passed_array one two three four five
bash media automatically builds an array from passed arguments that passed them to function and then you have position arguments. Furthermore when you write ${array[2]} you really write consequent argument one two three four and passed them to the function. So those calls are equivalent.
As ugly as it is, here is a workaround that works as long as you aren't passing an array explicitly, but a variable corresponding to an array:
function passarray()
{
eval array_internally=("$(echo '${'$1'[@]}')")
# access array now via array_internally
echo "${array_internally[@]}"
#...
}
array=(0 1 2 3 4 5)
passarray array # echo's (0 1 2 3 4 5) as expected
I'm sure someone can come up with a cleaner implementation of the idea, but I've found this to be a better solution than passing an array as "{array[@]"} and then accessing it internally using array_inside=("$@"). This becomes complicated when there are other positional/getopts parameters. In these cases, I've had to first determine and then remove the parameters not associated with the array using some combination of shift and array element removal.
A purist perspective likely views this approach as a violation of the language, but pragmatically speaking, this approach has saved me a whole lot of grief. On a related topic, I also use eval to assign an internally constructed array to a variable named according to a parameter target_varname I pass to the function:
eval $target_varname=$"(${array_inside[@]})"
I propose to avoid any limitation about "passing array(s) args" to a function by... passing strings, and make them array INSIDE the function :
#!/bin/bash
false_array1='1 2 3 4 5'
false_array2='my super fake array to deceive my function'
my_normal_string='John'
function i_love_arrays(){
local myNumbers=("$1")
local mySentence=("$2")
local myName="$3"
echo "My favorite numbers are, for sure: "
for number in ${myNumbers[@]}
do
echo $number
done
echo "Let's make an ugly split of a sentence: "
for word in ${mySentence[@]}
do
echo $word
done
echo "Yeah, I know, glad to meet you too. I'm ${myName}."
}
i_love_arrays "${false_array1}" "${false_array2}" "${my_normal_string}"
Using references (easiest way; best and only way for passing associative arrays):
print()
{
[ "$1" = "arr" ] || { declare -n arr; arr="$1"; }
# The name of reference mustn't match the input coming to the function.
# If it does, use the name directly as array instead of reference.
echo "${arr[@]}"
}
print arr
For normal (non-associative) arrays:
Here is a function that copies the passed array in new (local) array for usage. Can be time-taking for long arrays.
copy_fn()
{
eval local newarr=\(\${$1[@]}\)
echo ${newarr[@]}
}
Further, like in C++ you can only pass an array pointer, but in python, as a shared variable which you can modify as well.
Here is the shared variables approach.. useful for long arrays.
modify_arr()
{
eval local ptr=$1 # works as if ptr is a pointer to actual "array-name" (arr) # the original array is, thus, still same, not a copy
eval $ptr+=(1) # modify the array
echo ${ptr[@]} # will print name of the array (arr)
eval echo \$\{$ptr[@]\} # will print the actual array (arr)
}
modify_arr a
echo ${a[@]}
-Himanshu
@SBF
Your idea sparked some thinking on a situation I needed where two arrays were to be compared.
You can pass multiple arrays using your technique with a minor modification. See below
Thank you!
(works in bash 3.3 on Debian)
#!/bin/bash
#
function TakeTwoArrays() {
local Var1="$1"
local Var2="$2"
local NumArray1="$3"
local NumArray2="$4"
shift 4
local -a AllArray=("$@")
local -a Array1="${AllArray[@]:0:${NumArray1}}"
local -a Array2="${AllArray[@]:${NumArray1}}"
echo "Var1 is ${Var1}"
echo "Var2 is ${Var2}"
echo "Array1 has ${NumArray1} entries:"
printf '%s\n' "${Array1[@]}"
echo "Array2 has ${NumArray2} entries:"
printf '%s\n' "${Array2[@]}"
}
FirstArg="So Far"
SecondArg="So Good"
Array1Arg=("But" "the" "real" "question" "is")
NumArray1Arg="${#Array1Arg[@]}"
Array2Arg=("What" "happens" "with" "the" "second" "set" "of" "elements")
NumArray2Arg="${#Array2Arg[@]}"
TakeTwoArrays "${FirstArg}" "${SecondArg}" "${NumArray1Arg}" "${NumArray2Arg}" "${Array1Arg[@]}" "${Array2Arg[@]}"