Passer des tableaux en tant que parameters dans bash

Comment passer un tableau en paramètre à une fonction bash?

Note: Après ne pas avoir trouvé de réponse ici sur Stack Overflow, j’ai moi-même posté ma solution quelque peu grossière. Il ne permet qu’un seul tableau passé, et c’est le dernier élément de la liste de parameters. En fait, il ne passe pas du tout le tableau, mais une liste de ses éléments, qui sont ré-assemblés en un tableau par called_function (), mais cela a fonctionné pour moi. Si quelqu’un connaît mieux, n’hésitez pas à l’append ici.

Vous pouvez passer plusieurs tableaux comme arguments en utilisant quelque chose comme ceci:

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 

fera écho:

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

Note: Ceci est la solution quelque peu grossière que je me suis posté, après ne pas avoir trouvé de réponse ici sur Stack Overflow. Il ne permet qu’un seul tableau passé, et c’est le dernier élément de la liste de parameters. En fait, il ne passe pas du tout le tableau, mais une liste de ses éléments, qui sont ré-assemblés en un tableau par called_function (), mais cela a fonctionné pour moi. Quelque temps plus tard, Ken a posté sa solution, mais j’ai gardé la mienne ici pour une référence “historique”.

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

Amélioré par TheBonsai, merci.

Commentant la solution de Ken Bertelson et répondant à Jan Hettich:

Comment ça marche

la ligne takes_ary_as_arg descTable[@] optsTable[@] dans la fonction try_with_local_arys() envoie:

  1. Ceci crée en fait une copie des tableaux descTable et optsTable accessibles à la fonction takes_ary_as_arg .
  2. takes_ary_as_arg() fonction takes_ary_as_arg() reçoit les descTable[@] et optsTable[@] , ce qui signifie $1 == descTable[@] et $2 == optsTable[@] .
  3. au début de la fonction takes_ary_as_arg() , elle utilise la syntaxe ${!parameter} , appelée référence indirecte ou parfois à double référence , ce qui signifie qu’au lieu d’utiliser la valeur de $1 , nous utilisons la valeur de $1 , par exemple :

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

    de même pour $2 .

  4. en plaçant ceci dans argAry1=("${!1}") crée argAry1 tant que tableau (les parenthèses suivant = ) avec le descTable[@] étendu descTable[@] , tout comme en y écrivant argAry1=("${descTable[@]}") directement. la declare il n’y a pas besoin.

NB: Il convient de mentionner que l’initialisation du tableau à l’aide de cette forme de crochet initialise le nouveau tableau en fonction du séparateur de champs IFS ou interne, qui est par défaut tabulation , nouvelle ligne et espace . dans ce cas, comme il utilise la notation [@] , chaque élément est vu par lui-même comme s’il était cité (contrairement à [*] ).

Ma réservation avec

Dans BASH , la scope de la variable locale est la fonction courante et chaque fonction enfant appelée à partir de celle-ci, cela se traduit par le fait que la fonction takes_ary_as_arg() “voit” les descTable[@] et optsTable[@] explication).

Dans ce cas, pourquoi ne pas directement regarder ces variables elles-mêmes? C’est comme écrire ici:

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

Voir l’explication ci-dessus, qui ne fait que copier les valeurs du tableau descTable[@] selon l’ IFS .

En résumé

Cela ne fait, en gros, rien passer, comme d’habitude.

Je tiens également à souligner le commentaire de Dennis Williamson ci-dessus: les tableaux épars (les tableaux sans toutes les clés – avec des “trous”) ne fonctionneront pas comme prévu – nous perdrions les clés et “condenserions” le tableau.

Cela étant dit, je vois la valeur de la généralisation, les fonctions peuvent donc obtenir les tableaux (ou copies) sans connaître les noms:

  • pour ~ “copies”: cette technique est suffisante, il faut juste savoir que les index (clés) ont disparu.
  • pour les copies réelles: on peut utiliser un eval pour les clés, par exemple:

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

puis une boucle les utilisant pour créer une copie. Note: ici ! n’est pas utilisé dans sa précédente évaluation indirecte / double, mais plutôt dans le contexte du tableau, il renvoie les indices du tableau (clés).

  • et, bien sûr, si nous descTable passer des descTable et optsTable (sans [@] ), nous pourrions utiliser le tableau lui-même (comme dans la référence) avec eval . pour une fonction générique qui accepte les tableaux.

Le problème de base ici est que le ou les développeurs bash qui ont conçu / implémenté des tableaux ont vraiment vissé le chien. Ils ont décidé que ${array} était juste un raccourci pour ${array[0]} , ce qui était une mauvaise erreur. Surtout quand on considère que ${array[0]} n’a pas de sens et évalue à la chaîne vide si le type de tableau est associatif.

L’affectation d’un tableau prend la forme array=(value1 ... valueN) où valeur a la syntaxe [subscript]=ssortingng , affectant ainsi une valeur directement à un index particulier du tableau. Cela fait qu’il peut y avoir deux types de tableaux, indexés numériquement et indexés (appelés tableaux associatifs dans le langage bash). Cela permet également de créer des tableaux indexés numériquement. Laissant la partie [subscript]= est une main courte pour un tableau indexé numériquement, en commençant par l’index ordinal de 0 et en l’incrémentant à chaque nouvelle valeur dans l’instruction d’affectation.

Par conséquent, ${array} devrait évaluer l’ ensemble du tableau, les index et tout le rest. Il devrait être évalué à l’inverse de la déclaration d’affectation. Tout majeur de troisième année devrait le savoir. Dans ce cas, ce code fonctionnerait exactement comme prévu:

 declare -A foo bar foo=${bar} 

Ensuite, passer des tableaux par valeur aux fonctions et affecter un tableau à un autre fonctionnerait comme le rest de la syntaxe du shell. Mais parce qu’ils ne l’ont pas fait correctement, l’opérateur d’assignation = ne fonctionne pas pour les tableaux, et les tableaux ne peuvent pas être transmis par valeur aux fonctions ou aux sous-couches ou à la sortie en général ( echo ${array} ) sans code à travers tout.

Donc, si cela avait été fait correctement, l’exemple suivant montrerait comment l’utilité des tableaux dans bash pourrait être sensiblement meilleure:

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

le résultat résultant devrait être:

 (first=one second=2 third=3) 

Ensuite, les tableaux peuvent utiliser l’opérateur d’affectation et être transmis par valeur aux fonctions et même aux autres scripts shell. Facilement stocké en sortie dans un fichier, et facilement chargé depuis un fichier dans un script.

 declare -A foo read foo  

Hélas, nous avons été déçus par une équipe de développement Baslative autrement superlative.

En tant que tel, pour passer un tableau à une fonction, il n’ya qu’une seule option, à savoir utiliser la fonctionnalité 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 

se traduira par la sortie suivante:

 indexes: foo zoom values: bar fast 

Étant donné que cela passe par référence, vous pouvez également affecter le tableau dans la fonction. Oui, le tableau référencé doit avoir une scope globale, mais cela ne devrait pas être trop important, étant donné qu'il s'agit de scripts shell. Passer un tableau indexé ou associatif par valeur à une fonction nécessite de lancer tous les index et les valeurs dans la liste des arguments (pas trop utile si c'est un grand tableau) comme des chaînes simples comme ceci:

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

et ensuite écrire un tas de code dans la fonction pour réassembler le tableau.

La réponse de DevSolar a un point que je ne comprends pas (peut-être at-il une raison précise de le faire, mais je ne peux pas en penser): il définit le tableau à partir des parameters de position élément par élément, itératif.

Une approbation plus facile serait

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

Exemple

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

Un moyen simple de passer plusieurs tableaux en paramètre consiste à utiliser une chaîne séparée par des caractères. Vous pouvez appeler votre script comme ceci:

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

Ensuite, vous pouvez l’extraire dans votre code comme ceci:

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

De cette façon, vous pouvez réellement passer plusieurs tableaux en tant que parameters et il ne doit pas nécessairement s'agir des derniers parameters.

Celui-ci fonctionne même avec des espaces:

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

Avec quelques astuces, vous pouvez réellement passer des parameters nommés aux fonctions, ainsi que des tableaux.

La méthode que j’ai développée vous permet d’accéder aux parameters passés à une fonction comme ceci:

 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 d’autres termes, non seulement vous pouvez appeler vos parameters par leur nom (ce qui constitue un kernel plus lisible), mais vous pouvez en fait passer des tableaux (et des références à des variables – cette fonctionnalité ne fonctionne que dans bash 4.3)! De plus, les variables mappées sont toutes dans la scope locale, tout comme $ 1 (et d’autres).

Le code qui fait ce travail est assez léger et fonctionne à la fois dans bash 3 et bash 4 (ce sont les seules versions avec lesquelles je l’ai testé). Si vous êtes intéressé par d’autres trucs comme celui-ci qui rendent le développement avec beaucoup plus agréable et plus facile, vous pouvez consulter mon framework Bash Infinity , le code ci-dessous a été développé à cet effet.

 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' 

Juste pour append à la réponse acceptée, comme je l’ai trouvé, cela ne fonctionne pas bien si le contenu du tableau ressemble à:

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

Dans ce cas, chaque membre du tableau est divisé, de sorte que le tableau que la fonction voit est équivalent à:

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

Pour que ce cas fonctionne, j’ai trouvé le moyen de passer le nom de la variable à la fonction, puis d’utiliser eval:

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

Juste mon 2 ©

Aussi moche qu’elle soit, voici une solution de contournement qui fonctionne tant que vous ne transmettez pas explicitement un tableau, mais une variable correspondant à un tableau:

 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 

Je suis sûr que quelqu’un peut proposer une implémentation plus claire de l’idée, mais j’ai trouvé que c’était une meilleure solution que de passer un tableau en tant que "{array[@]"} et d’y accéder en interne en utilisant array_inside=("$@") . Cela devient compliqué quand il y a d’autres parameters de position / getopts . Dans ces cas, j’ai d’abord dû déterminer, puis supprimer les parameters non associés au tableau en utilisant une combinaison de suppression des éléments shift et array.

Une perspective puriste considère probablement cette approche comme une violation du langage, mais de manière pragmatique, cette approche m’a sauvé beaucoup de chagrin. Sur un sujet connexe, j’utilise également eval pour affecter un tableau construit en interne à une variable nommée selon un paramètre target_varname je transmets à la fonction:

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

J’espère que cela aide quelqu’un.

Condition préalable : Fonction permettant de rechercher une chaîne dans un tableau.
Ceci est une légère simplification de la solution de DevSolar dans la mesure où elle utilise les arguments transmis plutôt que de les copier.

 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