186

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.

11 Answers11

242
  • Expanding an array without an index only gives the first element, use

    copyFiles "${array[@]}"
    

    instead of

    copyFiles $array
    
  • Use a she-bang

    #!/bin/bash
    
  • Use 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
kbenoit
  • 2,370
A.B.
  • 92,125
120

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
SBF
  • 1,301
49

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.

13

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, as array is 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 $1 you would get only the first value. To get all the values use arr="${arr[@]}".

heemayl
  • 93,925
4

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[@]}"
2

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[@]})"
Stephen Rauch
  • 1,156
  • 6
  • 15
  • 21
1

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}"
tisc0
  • 138
1

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 &quot;${arr[@]}&quot;

}

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

1

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.

karel
  • 122,292
  • 133
  • 301
  • 332
Anatoly
  • 11
0

@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 &quot;Var1 is ${Var1}&quot;
echo &quot;Var2 is ${Var2}&quot;
echo &quot;Array1 has ${NumArray1} entries:&quot;
printf '%s\n' &quot;${Array1[@]}&quot;
echo &quot;Array2 has ${NumArray2} entries:&quot;
printf '%s\n' &quot;${Array2[@]}&quot;

} 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[@]}"

0

No you cannot pass arguments to a function in Bash. Arrays are not really first class objects and all of the solutions above just end up being workarounds with lots of leaky abstractions.

The key issue you will find is that the array is expanded and all indexes are lost when passed. So for instance, having an array with empty strings is not possible to pass into another function: they will all be lost on expansion.

There are workaround, like adding separators, and this Unix Stackexchange answer has some more background, but essentially the answer is that Bash does not support it directly.

oligofren
  • 679
  • 1
  • 9
  • 24