Passando arrays como parâmetros no bash

Como posso passar um array como parâmetro para uma function bash?

Nota: Depois de não encontrar uma resposta aqui no Stack Overflow, eu postei minha solução um pouco grosseira. Ele permite que apenas uma matriz seja passada e que seja o último elemento da lista de parâmetros. Na verdade, não está passando a matriz, mas uma lista de seus elementos, que são remontados em uma matriz por chamada_function (), mas funcionou para mim. Se alguém souber uma maneira melhor, sinta-se à vontade para adicioná-lo aqui.

   

Você pode passar vários arrays como argumentos usando algo assim:

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 

vai ecoar:

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

Nota: Esta é a solução um pouco grosseira que eu mesmo postei, depois de não encontrar uma resposta aqui no Stack Overflow. Ele permite que apenas uma matriz seja passada e que seja o último elemento da lista de parâmetros. Na verdade, não está passando a matriz, mas uma lista de seus elementos, que são remontados em uma matriz por chamada_function (), mas funcionou para mim. Um pouco depois Ken postou sua solução, mas eu mantive a minha aqui para referência “histórica”.

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

Melhorado por TheBonsai, obrigado.

Comentando sobre a solução de Ken Bertelson e respondendo a Jan Hettich:

Como funciona

the takes_ary_as_arg descTable[@] optsTable[@] na function try_with_local_arys() envia:

  1. Isso realmente cria uma cópia dos arrays optsTable e optsTable que são acessíveis para a function takes_ary_as_arg .
  2. takes_ary_as_arg() function takes_ary_as_arg() recebe descTable[@] e optsTable[@] como strings, o que significa $1 == descTable[@] e $2 == optsTable[@] .
  3. no início da function takes_ary_as_arg() ele usa a syntax ${!parameter} , que é chamada de referência indireta ou às vezes duplamente referenciada , isso significa que ao invés de usar o valor de $1 , usamos o valor expandido de $1 , exemplo :

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

    Da mesma forma por $2 .

  4. colocando isso em argAry1=("${!1}") cria argAry1 como um array (os parênteses seguintes = ) com o descTable[@] expandido descTable[@] , assim como escrevendo lá argAry1=("${descTable[@]}") diretamente. o declare não é necessário.

NB: Vale a pena mencionar que a boot da matriz usando este formulário de colchetes inicializa a nova matriz de acordo com o IFS ou o Separador de campo interno, que é por padrão tabulação , nova linha e espaço . nesse caso, uma vez que usou a notação [@] , cada elemento é visto por si só como se ele fosse citado (ao contrário de [*] ).

Minha reserva com isso

Em BASH , escopo de variável local é a function atual e toda function filha é chamada, isso se traduz no fato de que a function takes_ary_as_arg() “vê” os descTable[@] e optsTable[@] , portanto está funcionando (veja acima explicação).

Sendo assim, por que não olhar diretamente para essas variables? É como escrever lá:

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

Veja a explicação acima, que apenas copia os valores do array descTable[@] de acordo com o IFS atual.

Em suma

Isso está passando, em essência, nada por valor – como de costume.

Eu também quero enfatizar o comentário de Dennis Williamson acima: matrizes esparsas (matrizes sem todas as chaves definidas – com “buracos” nelas) não funcionarão como esperado – perderíamos as chaves e “condensaríamos” a matriz.

Dito isto, vejo o valor da generalização, portanto, as funções podem obter as matrizes (ou cópias) sem conhecer os nomes:

  • para ~ “cópias”: esta técnica é boa o suficiente, basta manter-se ciente de que os índices (chaves) desapareceram.
  • para cópias reais: podemos usar um eval para as chaves, por exemplo:

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

e, em seguida, um loop usando-os para criar uma cópia. Nota: aqui ! não é usado é a avaliação indireta / dupla anterior, mas sim no contexto da matriz retorna os índices da matriz (chaves).

  • e, claro, se optsTable cadeias optsTable e optsTable (sem [@] ), poderíamos usar o array em si (como por referência) com eval . para uma function genérica que aceita matrizes.

O problema básico aqui é que o (s) desenvolvedor (es) basico (s) que projetaram / implementaram matrizes realmente estragaram o vira-lata. Eles decidiram que ${array} era apenas uma mão curta para ${array[0]} , o que foi um grande erro. Especialmente quando você considera que ${array[0]} não tem significado e avalia a string vazia se o tipo de array é associativo.

Atribuir um array toma o formato array=(value1 ... valueN) onde o valor tem a syntax [subscript]=string array=(value1 ... valueN) [subscript]=string , atribuindo assim um valor diretamente a um índice particular no array. Isso faz com que possa haver dois tipos de matrizes, numericamente indexados e hash indexados (chamados de matrizes associativas na linguagem bash). Ele também faz com que você possa criar matrizes esparso numericamente indexadas. Deixar a parte [subscript]= é curta para uma matriz numericamente indexada, começando com o índice ordinal de 0 e incrementando com cada novo valor na declaração de atribuição.

Portanto, ${array} deve avaliar todo o array, índices e todos. Deve avaliar o inverso da declaração de atribuição. Qualquer terceiro ano do CS deve saber disso. Nesse caso, esse código funcionaria exatamente como você espera:

 declare -A foo bar foo=${bar} 

Então, passar matrizes por valor para funções e atribuir uma matriz a outra funcionaria como o restante da syntax da shell dita. Mas como eles não fizeram isso corretamente, a atribuição operator = não funciona para arrays e arrays não podem ser passados ​​por valor para funções ou para subshells ou saída em geral ( echo ${array} ) sem código para mastigar Através de tudo.

Então, se tivesse sido feito corretamente, o exemplo a seguir mostraria como a utilidade dos arrays no bash poderia ser substancialmente melhor:

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

a saída resultante deve ser:

 (first=one second=2 third=3) 

Em seguida, os arrays podem usar o operador de atribuição e ser passados ​​por valor para funções e até mesmo outros scripts de shell. Facilmente armazenado, saindo para um arquivo, e facilmente carregado de um arquivo em um script.

 declare -A foo read foo  

Infelizmente, fomos decepcionados por uma equipe de desenvolvimento bash superlativa.

Como tal, para passar um array para uma function, existe realmente apenas uma opção, que é usar o recurso 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 

resultará na seguinte saída:

 indexes: foo zoom values: bar fast 

Como isso está passando por referência, você também pode atribuir ao array na function. Sim, o array que está sendo referenciado tem que ter um escopo global, mas isso não deve ser um grande negócio, considerando que isso é shell script. Passar uma matriz indexada associativa ou esparsa por valor para uma function requer lançar todos os índices e valores na lista de argumentos (não muito útil se for uma matriz grande) como cadeias únicas como esta:

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

e, em seguida, escrevendo um monte de código dentro da function para remontar a matriz.

A resposta do DevSolar tem um ponto que eu não entendo (talvez ele tenha uma razão específica para isso, mas não consigo pensar em um): Ele define o array a partir do elemento de parâmetros posicionais por elemento, iterativo.

Uma aprovação mais fácil seria

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

Exemplo

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

Uma maneira fácil de passar vários arrays como parâmetro é usar uma string separada por caracteres. Você pode chamar seu script assim:

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

Então, você pode extraí-lo em seu código assim:

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

Dessa forma, você pode passar vários arrays como parâmetros e não precisa ser o último parâmetro.

Este funciona mesmo com espaços:

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

Com alguns truques, você pode realmente passar parâmetros nomeados para funções, junto com matrizes.

O método que desenvolvi permite que você acesse parâmetros passados ​​para uma function 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" 

Em outras palavras, não só você pode chamar seus parâmetros por seus nomes (o que compõe um núcleo mais legível), você pode realmente passar matrizes (e referências a variables ​​- este recurso funciona apenas no bash 4.3)! Além disso, as variables ​​mapeadas estão todas no escopo local, exatamente como $ 1 (e outros).

O código que faz este trabalho é bastante leve e funciona tanto no bash 3 quanto no bash 4 (essas são as únicas versões com as quais testei). Se você estiver interessado em mais truques como este que tornam o desenvolvimento com o bash muito mais agradável e mais fácil, você pode dar uma olhada no meu Bash Infinity Framework , o código abaixo foi desenvolvido para essa finalidade.

 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' 

Apenas para adicionar a resposta aceita, como eu achei que não funciona bem se o conteúdo da matriz é algo como:

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

Nesse caso, cada membro da matriz é dividido, portanto, a matriz que a function vê é equivalente a:

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

Para que este caso funcione, a maneira que eu encontrei é passar o nome da variável para a function, então use eval:

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

Apenas meu 2

Por mais feia que seja, aqui está uma solução alternativa que funciona contanto que você não esteja passando uma matriz explicitamente, mas uma variável correspondente a uma 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 

Tenho certeza de que alguém pode sugerir uma implementação mais clara da ideia, mas descobri que essa é uma solução melhor do que transmitir uma matriz como "{array[@]"} e acessá-la internamente usando array_inside=("$@") . Isso se torna complicado quando há outros parâmetros posicionais / getopts . Nesses casos, tive que primeiro determinar e depois remover os parâmetros não associados ao array usando alguma combinação de remoção de elemento de shift e matriz.

Uma perspectiva purista provavelmente vê essa abordagem como uma violação da linguagem, mas, pragmaticamente falando, essa abordagem me salvou um monte de tristeza. Em um tópico relacionado, também uso o eval para designar uma matriz construída internamente para uma variável nomeada de acordo com um parâmetro target_varname que passo para a function:

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

Espero que isso ajude alguém.

Requisito : Função para encontrar uma string em uma matriz.
Esta é uma ligeira simplificação da solução do DevSolar, na medida em que usa os argumentos passados ​​em vez de copiá-los.

 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