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.
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
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.
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:
- cela crée en fait une copie des tableaux
descTable
etoptsTable
qui sont accessibles à la fonctiontakes_ary_as_arg
. -
takes_ary_as_arg()
la fonction reçoitdescTable[@]
etoptsTable[@]
en tant que Chaînes, ce qui signifie$1 == descTable[@]
et$2 == optsTable[@]
. -
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
. - mettre ceci dans
argAry1=("${!1}")
créeargAry1
comme un tableau (les crochets suivants=
) avec ledescTable[@]
développé, tout comme écrire directementargAry1=("${descTable[@]}")
. ledeclare
, 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
etoptsTable
cordes (sans[@]
), on pourra utiliser le tableau lui-même (par référence) aveceval
. pour une fonction générique qui accepte les tableaux.
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.
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=("$@")
...
}
function aecho {
set "$1[$2]"
echo "${!1}"
}
Exemple
$ foo=(dog cat bird)
$ aecho foo 1
cat
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.
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 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'
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©
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.
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