Passage de tableaux en tant que paramètres dans bash

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

Note: Après n'avoir pas trouvé de réponse ici sur Stack Overflow, j'ai posté moi-même ma solution un peu brute. Il permet qu'un seul tableau soit passé, et c'est le dernier élément de la liste des paramètres. En fait, il ne passe pas du tout le tableau, mais une liste de ses éléments, qui sont ré-assemblés dans un tableau par called_function(), mais cela a fonctionné pour moi. Si quelqu'un connaît une meilleure façon, n'hésitez pas à l'ajouter ici.

169
demandé sur tripleee 2009-06-30 16:21:40

12 réponses

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
195
répondu Ken Bertelson 2016-06-21 10:12:51

Note: c'est la solution un peu brute que je me suis posté, après ne pas trouver de réponse ici sur Stack Overflow. Il permet qu'un seul tableau soit passé, et c'est le dernier élément de la liste des paramètres. En fait, il ne passe pas du tout le tableau, mais une liste de ses éléments, qui sont ré-assemblés dans un tableau par called_function(), mais cela a fonctionné pour moi. Un peu plus tard, Ken a posté sa solution, mais j'ai gardé la mienne ici pour 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.

80
répondu DevSolar 2015-09-08 07:04:46

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. cela crée en fait une copie des tableaux descTable et optsTable qui sont accessibles à la fonction takes_ary_as_arg.
  2. takes_ary_as_arg() la fonction reçoit descTable[@] et optsTable[@] en tant que Chaînes, ce qui signifie $1 == descTable[@] et $2 == optsTable[@].
  3. Au début de la fonction takes_ary_as_arg(), Il utilise la syntaxe ${!parameter}, qui est appelée indirect référence, ou parfois double référencés, cela signifie que au lieu de l'aide de $1's valeur, nous utilisons la valeur de la élargi valeur de $1, exemple:

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

    De Même pour $2.

  4. mettre ceci dans argAry1=("${!1}") crée argAry1 comme un tableau (les crochets suivants =) avec le descTable[@] développé, tout comme écrire directement argAry1=("${descTable[@]}"). le declare, il n'est pas nécessaire.

N.B.: {[60] } il vaut la peine mentionner que l'initialisation du tableau à l'aide de cette forme de parenthèse initialise le nouveau tableau selon le IFS ou séparateur de champ interne qui est par défaut tab, nouvelle ligne et espace . dans ce cas, puisqu'il a utilisé la notation [@] chaque élément est vu par lui-même comme s'il était cité (contrairement à [*]).

Ma réservation avec elle

Dans BASH, la portée de la variable locale est la fonction actuelle et chaque fonction enfant appelée à partir de celle-ci, cette traduit par le fait que la fonction takes_ary_as_arg() "voit" ces tableaux descTable[@] et optsTable[@], donc cela fonctionne (voir l'explication ci-dessus).

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

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

Voir l'explication ci-dessus, qui copie simplement les valeurs du tableau descTable[@] en fonction du courant IFS.

En résumé

Cela passe, en substance, rien par valeur-comme d'habitude.

Moi aussi vous voulez souligner le commentaire de Dennis Williamson ci - dessus: les tableaux clairsemés (les tableaux sans toutes les clés définissent - avec des "trous" en eux) ne fonctionneront pas comme prévu-nous perdrions les clés et "condenserions" le tableau.

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

  • Pour ~"copies": cette technique est assez bonne, juste besoin de garder conscience, que les indices (clés) sont disparu.
  • Pour de vraies copies: nous pouvons 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é c'est une évaluation indirecte/double précédente, mais plutôt dans le contexte du tableau, il renvoie les indices du tableau (clés).

  • et, bien sûr, si nous devions nous passer descTable et optsTable cordes (sans [@]), on pourra utiliser le tableau lui-même (par référence) avec eval. pour une fonction générique qui accepte les tableaux.
34
répondu TheWizard 2016-09-07 11:26:38

Le problème de base ici est que le développeur bash qui a conçu / implémenté des tableaux a vraiment vissé le chien. Ils ont décidé que ${array} était juste une main courte pour ${array[0]}, ce qui était une mauvaise erreur. Surtout quand vous considérez que ${array[0]} n'a aucune signification 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ù value a la syntaxe [subscript]=string, assignant ainsi une valeur directement à un index particulier dans le tableau. De ce fait, il il peut donc y avoir deux types de tableaux, indexés numériquement et indexés par hachage (appelés tableaux associatifs dans le langage bash). Il permet également de créer des tableaux indexés numériquement clairsemés. Laisser la partie [subscript]= est une main courte pour un tableau indexé numériquement, en commençant par l'index ordinal de 0 et en incrémentant avec chaque nouvelle valeur dans l'instruction d'affectation.

Par conséquent, {[7] } devrait évaluer le tableau entier , les index et tout. Il devrait évaluer à la inverse de la déclaration d'affectation. Toute troisième année CS majeur devrait savoir que. Dans ce cas, ce code fonctionnerait exactement comme vous pouvez vous y attendre:

declare -A foo bar
foo=${bar}

Ensuite, passer des tableaux par valeur aux fonctions et assigner un tableau à un autre fonctionnerait comme le dicte le reste de la syntaxe du shell. Mais comme ils ne l'ont pas fait correctement, l'opérateur d'affectation = ne fonctionne pas pour les tableaux, et les tableaux ne peuvent pas être passés par valeur aux fonctions ou aux sous-ensembles ou à la sortie en général (echo ${array}) sans code à mâcher à travers tout cela.

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

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

La sortie résultante 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 à d'Autres scripts shell. Facilement stocké en sortant dans un fichier, et facilement chargé à partir d'un fichier dans un script.

declare -A foo
read foo <file

Hélas, nous avons été lâché par un superlatif bash équipe de développement.

En tant que tel, pour passer un tableau à une fonction, il n'y a vraiment qu'une seule option, et c'est d'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

Donnera la sortie suivante:

indexes: foo zoom
values: bar fast

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

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

Puis en écrivant un tas de code dans la fonction pour réassembler le tableau.

19
répondu tigerand 2017-05-11 16:25:49

La réponse de DevSolar a un point que je ne comprends pas (peut-être qu'il a une raison spécifique de le faire, mais je ne peux pas penser à un): il définit le tableau à partir des paramètres de position Élément par élément, itératif.

Un approuch plus facile serait

called_function()
{
  ...
  # do everything like shown by DevSolar
  ...

  # now get a copy of the positional parameters
  local_array=("$@")
  ...
}
5
répondu TheBonsai 2009-06-30 13:15:15
function aecho {
  set "$1[$2]"
  echo "${!1}"
}

Exemple

$ foo=(dog cat bird)

$ aecho foo 1
cat
3
répondu Steven Penny 2015-07-15 01:11:11

Un moyen facile de passer plusieurs tableaux en paramètre est d'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 paramètres et il n'est pas nécessaire que ce soit les derniers paramètres.

2
répondu Remy Cilia 2016-12-23 17:13:17

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[@]}"
1
répondu humbleSapiens 2011-05-25 06:55:11

, Avec quelques astuces, vous pouvez passer des paramètres nommés à des fonctions, ainsi que des tableaux.

La méthode que j'ai développée vous permet d'accéder aux paramètres passés à une fonction comme celle-ci:

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 paramètres par leurs noms (ce qui compense un noyau plus lisible), vous pouvez réellement 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 portée 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 Plus de trucs comme celui - ci qui rendent le développement avec bash beaucoup plus agréable et plus facile, vous pouvez jeter un oeil à mon Bash Infinity Framework, le code ci-dessous a été développé à cette fin.

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'
1
répondu niieani 2015-05-04 16:58:38

Juste pour ajouter à la réponse acceptée, comme je l'ai trouvé, cela ne fonctionne pas bien si le contenu du tableau est quelque chose comme:

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, la façon dont j'ai trouvé est 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 mes 2©

1
répondu AlvaroGMJ 2017-07-31 20:32:53

Aussi moche soit-il, voici une solution de contournement qui fonctionne tant que vous ne passez 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é lorsqu'il existe d'autres paramètres positionnels / getopts. Dans ces cas, j'ai dû d'abord déterminer, puis supprimer les paramètres non associés au tableau en utilisant une combinaison de shift et la suppression des éléments du tableau.

Une perspective puriste considère probablement cette approche comme une violation de la langue, mais pragmatiquement parlant, cette approche m'a sauvé beaucoup de chagrin. Sur un sujet connexe, j'utilise également eval pour assigner un tableau construit en interne à une variable nommée selon un paramètre target_varname je passe à la fonction:

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

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

1
répondu Blake Schultze 2018-07-11 22:06:07

Exigence: Fonction pour rechercher une chaîne dans un tableau.
C'est une légère simplification de la solution de DevSolar en ce sens qu'elle utilise les arguments passés 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 
0
répondu Andre 2017-04-07 00:22:43