¿Cómo pasar una matriz como argumento de función?

57

Luchando durante un tiempo pasando una matriz como argumento, pero de todos modos no funciona. He intentado como a continuación:

#! /bin/bash

function copyFiles{
   arr="$1"
   for i in "${arr[@]}";
      do
          echo "$i"
      done

}

array=("one" "two" "three")

copyFiles $array

Una respuesta con explicación estaría bien.

Editar: Básicamente, eventualmente llamaré a la función desde otro archivo de script. Por favor explique las restricciones si es posible.

Ahsanul Haque
fuente

Respuestas:

85
  • Expandir una matriz sin un índice solo da el primer elemento, use

    copyFiles "${array[@]}"

    en lugar de

    copyFiles $array
  • Usa un she-bang

    #!/bin/bash
  • Use la sintaxis de función correcta

    Las variantes válidas son

    function copyFiles {…}
    function copyFiles(){…}
    function copyFiles() {…}

    en lugar de

    function copyFiles{…}
  • Use la sintaxis correcta para obtener el parámetro de matriz

    arr=("$@")

    en lugar de

    arr="$1"

Por lo tanto

#!/bin/bash
function copyFiles() {
   arr=("$@")
   for i in "${arr[@]}";
      do
          echo "$i"
      done

}

array=("one" "two" "three")

copyFiles "${array[@]}"

La salida es (mi script tiene el nombre foo)

$ ./foo   
one
two
three
AB
fuente
gracias, pero ¿no es la función copyFiles {...} una sintaxis correcta? Aunque soy nuevo, ejecuto algún programa con éxito con la sintaxis.
Ahsanul Haque
Las variantes válidas son copyFiles {…}y copyFiles(){…}y copyFiles() {…}, pero no copyFiles{…}. Tenga en cuenta el espacio en la variante sin()
AB
19

También puede pasar la matriz como referencia. es decir:

#!/bin/bash

function copyFiles {
   local -n arr=$1

   for i in "${arr[@]}"
   do
      echo "$i"
   done
}

array=("one" "two" "three")

copyFiles array

pero tenga en cuenta que cualquier modificación a arr se realizará en la matriz.


fuente
2
Sin embargo, no era exactamente lo que quería, pero aún así es bueno saber cómo pasar por referencia funciona en bash. +1 :)
Ahsanul Haque
44
Requiere bash 4.3+
dtmland
19

Si desea pasar uno o más argumentos Y una matriz, propongo que este cambio en el script de @AB
Array sea el último argumento y solo se pueda pasar una matriz

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

Salida:

$ ./foo   
Copying one
Copying two
Copying three
SBF
fuente
2
+1 para aprender acerca de una matriz que debe estar al final y que solo se debe enviar uno
David 'the jengibre calvo'
1
Gracias por el shiftuso
Itachi
También es útil usar el argumento shift a veces, por lo que si tenía 6 argumentos antes de la matriz, puede usarlo shift 6.
spinup
Convierte "el resto de argumentos" en arr. ¿Es posible tener un parámetro de matriz en el medio? ¿O incluso varios parámetros de matrices? function copyAndMove() { msg1=$1 ; arr1=...?... ; msg2=? ; arr2=...?... ; msg3=? ; ... }. Al igual que lo definiría en Python: def copyAndMove(msg1="foo", cpFiles=[], msg2="bar", mvFiles=[], msg3="baz"): .... No importa, encontré stackoverflow.com/a/4017175/472245
towi
8

Hay un par de problemas Aquí está la forma de trabajo:

#!/bin/bash
function copyFiles {
   arr=( "$@" )
   for i in "${arr[@]}";
      do
          echo "$i"
      done

}

array=("one" "two" "three")
copyFiles "${array[@]}"
  • Debe haber al menos un espacio entre la declaración de función y {

  • No se puede usar $array, ya que arrayes una matriz, no una variable. Si desea obtener todos los valores de una matriz, use"${array[@]}"

  • En su declaración de función principal que necesita, arr="$@"ya que "${array[@]}"se expandirá a los valores indexados separados por espacios, si usa $1solo obtendría el primer valor. Para obtener todos los valores use arr="$arr[@]}".

heemayl
fuente
Necesitasarr=("$@")
AB
Para ver la diferencia, agregue un a breakcontinuación echo "$i". En su versión aún verá todos los elementos. Sin embargo, debe ser de tres líneas.
AB
@heemayl: pequeño error tipográfico - El {en su conjunto de la segunda viñeta se perdió ... "$ {array [@]}" ...
Cbhihe
3

Aquí sigue un ejemplo un poco más grande. Para una explicación, vea los comentarios en el código.

#!/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[@]}"
Ulf Oreborn
fuente
1

La mejor manera es pasar como argumentos de posición. Nada más. Puede pasar como cadena, pero de esta manera puede causar algunos problemas. Ejemplo:

array=(one two three four five)

function show_passed_array(){
  echo $@
}

o

function show_passed_array(){
  while $# -gt 0;do
    echo $1;shift
  done
}

    show_passed_array ${array[@]}

salida:

  one two three four five

Quiere decir que si el valor de la matriz tiene símbolos de espacio, debe citar los elementos antes de pasar para acceder al valor por índice en la función, use los parámetros de posición $ 1 $ 2 $ 3 ... Donde índice 0 -> 1, 1 -> 2, ... Para iterar el acceso, es mejor usar siempre $ 1 y después de Shift. No se necesita nada adicional. Puede pasar argumentos sin una matriz como esta:

show_passed_array one two three four five

bash media crea automáticamente una matriz a partir de argumentos pasados ​​que los pasaron a funcionar y luego tiene argumentos de posición. Además, cuando escribe $ {array [2]}, realmente escribe el argumento consecuente uno dos tres cuatro y los pasa a la función. Entonces esas llamadas son equivalentes.

Anatoly
fuente
1

Tan feo como es, aquí hay una solución alternativa que funciona siempre que no esté pasando una matriz explícitamente, sino una variable correspondiente a una matriz:

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

Estoy seguro de que alguien puede llegar a una implementación más limpia de la idea, pero he encontrado que esta es una mejor solución que pasar una matriz como "{array[@]"}y luego acceder internamente usando array_inside=("$@"). Esto se vuelve complicado cuando hay otros posicionales / getoptsparámetros. En estos casos, primero tuve que determinar y luego eliminar los parámetros no asociados con la matriz usando alguna combinación de shifty eliminación de elementos de la matriz.

Una perspectiva purista probablemente ve este enfoque como una violación del lenguaje, pero hablando pragmáticamente, este enfoque me ha ahorrado mucho dolor. En un tema relacionado, también uso evalpara asignar una matriz construida internamente a una variable nombrada de acuerdo con un parámetro target_varnameque paso a la función:

eval $target_varname=$"(${array_inside[@]})"
Blake Schultze
fuente
Eso es feo e innecesario. Si desea pasar la matriz por su nombre, hacer array_internallyun alias de la misma: declare -n array_internally=$1. Y el resto de "se vuelve complicado" y "determinar y luego eliminar ..." se aplica independientemente de cómo se pasa la matriz, por lo que no veo cuál es el punto de eso. Y evalcrear una matriz que potencialmente contenga caracteres especiales está esperando que ocurra el dolor en una fecha posterior.
muru