91

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.

138
  • 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
  • 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
  • 1
    among the variants, there's copyFiles() { ... } without a function keyword. Added benefit : also valid in POSIX (unix.stackexchange.com/questions/73750/…) – YoungFrog Mar 28 '20 at 11:04
  • In this particular case you can do for i; do ...; done to iterate over $@. Also, this wasn't asked, but if you need to pass a couple of arrays, you probably want to pass by reference. – x-yuri Jan 12 at 19:18
47

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
  • 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 '20 at 1:32
26

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.

  • 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
  • 8
    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 '20 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 '20 at 11:11
  • Bash is not Haskell. You've got to compromise. On a different note, it makes sense to do [ "$1" = arr ] || local -n arr=$1 in place of just local -n arr=$1. That way the argument and the parameter names can coincide. As far as I can tell, the scope in bash is dynamic. – x-yuri Jan 12 at 19:13
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[@]}".

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

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[@]})"
  • 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 '20 at 9:28
0

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}"
0

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

  • What I like about your answer is the [ "$1" = arr ] || local -n arr=$1 part. The rest... I don't think you put enough effort for it to be readable. Also, your examples lack quoting. And I doubt the rest of the ideas are practical. – x-yuri Jan 12 at 18:54
  • Thanks for feedback. I'll consider improvements in my further answers. I put too much unnecessary effort in explaining everything I guess. – Himanshu Tanwar 4 hours ago

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.