Pasar arreglos como parámetros en bash

¿Cómo puedo pasar una matriz como parámetro a una función bash?

Nota: Después de no encontrar una respuesta aquí en Stack Overflow, publiqué mi solución un tanto cruda. Permite pasar solo una matriz y ser el último elemento de la lista de parámetros. En realidad, no está pasando la matriz en absoluto, sino una lista de sus elementos, que se vuelven a ensamblar en una matriz mediante called_function (), pero funcionó para mí. Si alguien conoce una mejor manera, siéntete libre de agregarla aquí.

Puede pasar varias matrices como argumentos usando algo como esto:

takes_ary_as_arg() { declare -a argAry1=("${!1}") echo "${argAry1[@]}" declare -a argAry2=("${!2}") echo "${argAry2[@]}" } try_with_local_arys() { # array variables could have local scope local descTable=( "sli4-iread" "sli4-iwrite" "sli3-iread" "sli3-iwrite" ) local optsTable=( "--msix --iread" "--msix --iwrite" "--msi --iread" "--msi --iwrite" ) takes_ary_as_arg descTable[@] optsTable[@] } try_with_local_arys 

hará eco:

 sli4-iread sli4-iwrite sli3-iread sli3-iwrite --msix --iread --msix --iwrite --msi --iread --msi --iwrite 

Nota: Esta es la solución algo cruda que publiqué, después de no encontrar una respuesta aquí en Stack Overflow. Permite pasar solo una matriz y ser el último elemento de la lista de parámetros. En realidad, no está pasando la matriz en absoluto, sino una lista de sus elementos, que se vuelven a ensamblar en una matriz mediante called_function (), pero funcionó para mí. Un poco más tarde, Ken publicó su solución, pero yo mantuve la mía aquí para referencia “histórica”.

 calling_function() { variable="a" array=( "x", "y", "z" ) called_function "${variable}" "${array[@]}" } called_function() { local_variable="${1}" shift local_array=("${@}") } 

Mejorado por TheBonsai, gracias.

Comentando sobre la solución de Ken Bertelson y respondiendo a Jan Hettich:

Cómo funciona

la línea takes_ary_as_arg descTable[@] optsTable[@] en la función try_with_local_arys() envía:

  1. Esto es en realidad crea una copia de las matrices descTable y optsTable que son accesibles para la función takes_ary_as_arg .
  2. takes_ary_as_arg() función takes_ary_as_arg() recibe descTable[@] y optsTable[@] como cadenas, lo que significa $1 == descTable[@] y $2 == optsTable[@] .
  3. al comienzo de la función takes_ary_as_arg() utiliza la syntax ${!parameter} , que se denomina referencia indirecta o, a veces, doble referencia , esto significa que, en lugar de usar el valor de $1 , usamos el valor expandido de $1 , por ejemplo :

     baba=booba variable=baba echo ${variable} # baba echo ${!variable} # booba 

    del mismo modo por $2 .

  4. poner esto en argAry1=("${!1}") crea argAry1 como una matriz (los paréntesis siguientes = ) con la descTable[@] expandida descTable[@] , al igual que escribir allí argAry1=("${descTable[@]}") directamente. el declare no se requiere

NB: Vale la pena mencionar que la inicialización de la matriz utilizando este formulario de paréntesis inicializa la nueva matriz de acuerdo con el IFS o el Separador de campo interno que es por defecto tab , newline y space . en ese caso, como usaba la notación [@] , cada elemento se ve por sí mismo como si fuera citado (contrario a [*] ).

Mi reserva con eso

En BASH , el ámbito variable local es la función actual y cada función secundaria llamada desde allí, esto se traduce en el hecho de que la función takes_ary_as_arg() “ve” las descTable[@] y optsTable[@] , por lo tanto está funcionando (ver arriba explicación).

Siendo ese el caso, ¿por qué no mirar directamente esas variables? Es como escribir allí:

 argAry1=("${descTable[@]}") 

Consulte la explicación anterior, que simplemente copia los valores de la matriz descTable[@] de acuerdo con el IFS actual.

En resumen

Esto está pasando, en esencia, nada por valor, como de costumbre.

También quiero enfatizar el comentario anterior de Dennis Williamson: las matrices dispersas (las matrices sin todas las claves definen – con “agujeros” en ellas) no funcionarán como se esperaba – perderíamos las claves y “condensaríamos” la matriz.

Dicho esto, veo el valor para la generalización, por lo tanto, las funciones pueden obtener las matrices (o copias) sin conocer los nombres:

  • para ~ “copias”: esta técnica es lo suficientemente buena, solo necesita mantenerse al tanto, que los índices (claves) se han ido.
  • para copias reales: podemos usar un eval para las claves, por ejemplo:

     eval local keys=(\${!$1}) 

y luego un bucle usándolos para crear una copia. Nota: ! aquí ! no se usa es una evaluación indirecta / doble previa, sino que en el contexto de matriz devuelve los índices de matriz (claves).

  • y, por supuesto, si tuviéramos que pasar las descTable y optsTable (sin [@] ), podríamos usar la matriz en sí (como por referencia) con eval . para una función genérica que acepta matrices.

El problema básico aquí es que los desarrolladores de bash que diseñaron / implementaron matrices realmente arruinaron al chucho. Decidieron que ${array} era short hand para ${array[0]} , lo que fue un grave error. Especialmente si se tiene en cuenta que ${array[0]} no tiene significado y evalúa la cadena vacía si el tipo de matriz es asociativo.

La asignación de una matriz toma la forma array=(value1 ... valueN) donde value tiene la syntax [subscript]=string , asignando así un valor directamente a un índice particular en la matriz. Esto lo hace para que haya dos tipos de matrices, indexadas numéricamente y hash indexadas (denominadas matrices asociativas en el lenguaje bash). También lo hace para que pueda crear matrices dispersas numéricamente dispersas. Dejar la parte [subscript]= es una mano corta para una matriz indexada numéricamente, comenzando con el índice ordinal de 0 e incrementando con cada nuevo valor en la statement de asignación.

Por lo tanto, ${array} debería evaluar a toda la matriz, índices y todo. Debe evaluar al inverso de la statement de asignación. Cualquier tercer año de CS major debería saberlo. En ese caso, este código funcionaría exactamente como lo esperaría:

 declare -A foo bar foo=${bar} 

Luego, pasar las matrices por valor a las funciones y asignar una matriz a otra funcionaría como dicta el rest de la syntax de la shell. Pero debido a que no lo hicieron bien, el operador de asignación = no funciona para las matrices, y las matrices no se pueden pasar por valor a las funciones o subcapas o salida en general ( echo ${array} ) sin código para masticar a través de todo.

Entonces, si se hubiera hecho bien, entonces el siguiente ejemplo mostraría cómo la utilidad de las matrices en bash podría ser sustancialmente mejor:

 simple=(first=one second=2 third=3) echo ${simple} 

el resultado resultante debe ser:

 (first=one second=2 third=3) 

Entonces, las matrices podrían usar el operador de asignación, y se pasarán por valor a las funciones e incluso a otros scripts de shell. Se almacena fácilmente al enviar a un archivo y se carga fácilmente desde un archivo a un script.

 declare -A foo read foo  

Por desgracia, nos ha decepcionado un equipo de desarrollo de bash superlativo.

Como tal, para pasar una matriz a una función, en realidad solo hay una opción, y es usar la función nameref:

 function funky() { local -n ARR ARR=$1 echo "indexes: ${!ARR[@]}" echo "values: ${ARR[@]}" } declare -A HASH HASH=([foo]=bar [zoom]=fast) funky HASH # notice that I'm just passing the word 'HASH' to the function 

dará como resultado la siguiente salida:

 indexes: foo zoom values: bar fast 

Como esto pasa por referencia, también puede asignarlo a la matriz en la función. Sí, la matriz a la que se hace referencia debe tener un scope global, pero eso no debería ser demasiado importante, teniendo en cuenta que esto es un script de shell. Pasar una matriz indexada dispersa o asociativa por valor a una función requiere arrojar todos los índices y los valores a la lista de argumentos (no demasiado útil si se trata de una matriz grande) como cadenas únicas como esta:

 funky "${!array[*]}" "${array[*]}" 

y luego escribir un montón de código dentro de la función para volver a armar la matriz.

La respuesta de DevSolar tiene un punto que no entiendo (tal vez tiene una razón específica para hacerlo, pero no puedo pensar en uno): establece el conjunto de parámetros posicionales elemento por elemento, iterativo.

Una aplicación más fácil sería

 called_function() { ... # do everything like shown by DevSolar ... # now get a copy of the positional parameters local_array=("$@") ... } 
 function aecho { set "$1[$2]" echo "${!1}" } 

Ejemplo

 $ foo=(dog cat bird) $ aecho foo 1 cat 

Una forma fácil de pasar varias matrices como parámetro es usar una cadena separada por caracteres. Puede llamar a su script de esta manera:

 ./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne" 

Entonces, puedes extraerlo en tu código así:

 myArray=$1 IFS=';' read -a myArray < << "$myArray" myOtherArray=$3 IFS=';' read -a myOtherArray <<< "$myOtherArray" 

De esta forma, puede pasar múltiples matrices como parámetros y no tiene que ser el último parámetro.

Este funciona incluso con espacios:

 format="\t%2s - %s\n" function doAction { local_array=("$@") for (( i = 0 ; i < ${#local_array[@]} ; i++ )) do printf "${format}" $i "${local_array[$i]}" done echo -n "Choose: " option="" read -n1 option echo ${local_array[option]} return } #the call: doAction "${tools[@]}" 

Con algunos trucos, puede pasar parámetros nombrados a las funciones, junto con las matrices.

El método que desarrollé te permite acceder a los parámetros pasados ​​a una función como esta:

 testPassingParams() { @var hello l=4 @array anArrayWithFourElements l=2 @array anotherArrayWithTwo @var anotherSingle @reference table # references only work in bash >=4.3 @params anArrayOfVariedSize test "$hello" = "$1" && echo correct # test "${anArrayWithFourElements[0]}" = "$2" && echo correct test "${anArrayWithFourElements[1]}" = "$3" && echo correct test "${anArrayWithFourElements[2]}" = "$4" && echo correct # etc... # test "${anotherArrayWithTwo[0]}" = "$6" && echo correct test "${anotherArrayWithTwo[1]}" = "$7" && echo correct # test "$anotherSingle" = "$8" && echo correct # test "${table[test]}" = "works" table[inside]="adding a new value" # # I'm using * just in this example: test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct } fourElements=( a1 a2 "a3 with spaces" a4 ) twoElements=( b1 b2 ) declare -A assocArray assocArray[test]="works" testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." test "${assocArray[inside]}" = "adding a new value" 

En otras palabras, no solo puede llamar sus parámetros por sus nombres (lo que representa un núcleo más legible), sino que puede pasar matrices (y referencias a variables; ¡esta característica solo funciona en bash 4.3)! Además, las variables asignadas están todas en el ámbito local, al igual que $ 1 (y otros).

El código que hace este trabajo es bastante ligero y funciona tanto en bash 3 como en bash 4 (estas son las únicas versiones con las que lo he probado). Si te interesan más trucos como este que hacen que el desarrollo con bash sea mucho más agradable y fácil, puedes echar un vistazo a mi Bash Infinity Framework , el siguiente código fue desarrollado para ese propósito.

 Function.AssignParamLocally() { local commandWithArgs=( $1 ) local command="${commandWithArgs[0]}" shift if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]] then paramNo+=-1 return 0 fi if [[ "$command" != "local" ]] then assignNormalCodeStarted=true fi local varDeclaration="${commandWithArgs[1]}" if [[ $varDeclaration == '-n' ]] then varDeclaration="${commandWithArgs[2]}" fi local varName="${varDeclaration%%=*}" # var value is only important if making an object later on from it local varValue="${varDeclaration#*=}" if [[ ! -z $assignVarType ]] then local previousParamNo=$(expr $paramNo - 1) if [[ "$assignVarType" == "array" ]] then # passing array: execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )" eval "$execute" paramNo+=$(expr $assignArrLength - 1) unset assignArrLength elif [[ "$assignVarType" == "params" ]] then execute="$assignVarName=( \"\${@:$previousParamNo}\" )" eval "$execute" elif [[ "$assignVarType" == "reference" ]] then execute="$assignVarName=\"\$$previousParamNo\"" eval "$execute" elif [[ ! -z "${!previousParamNo}" ]] then execute="$assignVarName=\"\$$previousParamNo\"" eval "$execute" fi fi assignVarType="$__capture_type" assignVarName="$varName" assignArrLength="$__capture_arrLength" } Function.CaptureParams() { __capture_type="$_type" __capture_arrLength="$l" } alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; ' alias @param='@trapAssign local' alias @reference='_type=reference @trapAssign local -n' alias @var='_type=var @param' alias @params='_type=params @param' alias @array='_type=array @param' 

Solo para agregar a la respuesta aceptada, ya que encontré que no funciona bien si los contenidos de la matriz son algo así como:

 RUN_COMMANDS=( "command1 param1... paramN" "command2 param1... paramN" ) 

En este caso, cada miembro de la matriz se divide, por lo que la matriz ve la función es equivalente a:

 RUN_COMMANDS=( "command1" "param1" ... "command2" ... ) 

Para que funcione este caso, la forma que encontré es pasar el nombre de la variable a la función, luego usar eval:

 function () { eval 'COMMANDS=( "${'"$1"'[@]}" )' for COMMAND in "${COMMANDS[@]}"; do echo $COMMAND done } function RUN_COMMANDS 

Solo mi 2 ©

A pesar de lo feo que es, aquí hay una solución que funciona siempre que no esté pasando una matriz de forma explícita, 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 clara de la idea, pero he encontrado que esta es una mejor solución que pasar un arreglo como "{array[@]"} y luego acceder a él internamente usando array_inside=("$@") . Esto se vuelve complicado cuando hay otros parámetros posicionales / getopts . En estos casos, primero tuve que determinar y luego eliminar los parámetros no asociados con la matriz usando alguna combinación de shift y eliminación de elemento de matriz.

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

eval $target_varname=$"(${array_inside[@]})"

Espero que esto ayude a alguien.

Requisito : Función para encontrar una cadena en una matriz.
Esta es una pequeña simplificación de la solución DevSolar en que utiliza los argumentos pasados ​​en lugar de copiarlos.

 myarray=('foobar' 'foxbat') function isInArray() { local item=$1 shift for one in $@; do if [ $one = $item ]; then return 0 # found fi done return 1 # not found } var='foobar' if isInArray $var ${myarray[@]}; then echo "$var found in array" else echo "$var not found in array" fi