87

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.

129
  • 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
| improve this answer | |
  • thanks, but isn't function copyFiles{…} a correct syntax? Though i am a new bie i run some program successfully with the syntax. – Ahsanul Haque Sep 15 '15 at 11:11
  • 1
    Valid variants are copyFiles {…} and copyFiles(){…} and copyFiles() {…}, but not copyFiles{…}. Note the space in the variant without () – A.B. Sep 15 '15 at 11:14
  • among the variants, there's copyFiles() { ... } without a function keyword. Added benefit : also valid in POSIX (unix.stackexchange.com/questions/73750/…) – YoungFrog Mar 28 at 11:04
41

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
| improve this answer | |
  • 3
    +1 for learning about an array needing to be at the end and that only one should be sent – David 'the bald ginger' Sep 21 '18 at 11:03
  • 1
    Thanks for the shift usage. – Itachi Jan 8 '19 at 3:06
  • It's also useful to use shift's argument sometimes, so if you had 6 arguments before the array, you can use shift 6. – spinup Jul 4 '19 at 17:49
  • You convert "the rest of arguments" into arr. Is it possible to have an array parameter in the middle? Or even several arrays parameters? function copyAndMove() { msg1=$1 ; arr1=...?... ; msg2=? ; arr2=...?... ; msg3=? ; ... }. Like I would define it in python: def copyAndMove(msg1="foo", cpFiles=[], msg2="bar", mvFiles=[], msg3="baz"): .... Never mind, I found stackoverflow.com/a/4017175/472245 – towi Oct 25 '19 at 7:07
  • 1
    You can also achieve same behavior without using shift by doing this: local msg="$1"; local -a arr=( "${@:2}" ) – David Rissato Cruz May 28 at 1:32
22

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.

| improve this answer | |
  • 2
    Though, it wasn't exactly what i want, but it still nice to know how pass by reference work in bash. +1 :) – Ahsanul Haque Sep 15 '15 at 12:27
  • 6
    Requires bash 4.3+ – dtmland May 4 '19 at 18:47
  • @user448115: This is a bad idea, suppose you passed an array named n, you get a circular reference – Sapphire_Brick Apr 21 at 15:14
  • @user448115 Yes, but what if the input array is not ever supposed to be changed by the function in question. In effect, what if the passing of an array is only being done to get a result. Exposing your inputs to potentially unexpected data mutations is not wise. – Anthony Rutledge Aug 19 at 11:11
8

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

| improve this answer | |
  • You need arr=("$@") – A.B. Sep 15 '15 at 9:38
  • To see the difference, add a break below echo "$i". In your version you will still see all the elements. However, it should be three lines. – A.B. Sep 15 '15 at 9:43
  • @heemayl: small typo -- The { in your array of the second bullet went missing ... "${array[@]}" ... – Cbhihe Sep 15 '15 at 17:36
3

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[@]}"
| improve this answer | |
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.

| improve this answer | |
1

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[@]})"
| improve this answer | |
  • That is ugly and unncessary. If you want to pass the array by name, make array_internally an alias of it: declare -n array_internally=$1. And the rest of it about "becomes complicated" and "determine and then remove ..." applies to irrespective of how you pass the array, so I don't see what's the point of that. And evaling an array potentially containing special characters is just waiting for grief to happen at a later date. – muru Jul 12 '18 at 4:04
  • Just posted a proposition : pass one or multiple strings and make them an array inside the function. – tisc0 Feb 7 at 9:28
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}"
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.