arrays - script - if bash




Passando arrays como parâmetros no bash (9)

Como posso passar um array como parâmetro para uma função 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 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_função (), mas funcionou para mim. Se alguém souber uma maneira melhor, sinta-se à vontade para adicioná-lo aqui.


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=("[email protected]")
  ...
}

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 função 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 função, então use eval:

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

function RUN_COMMANDS

Apenas meu 2


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

Como funciona

the takes_ary_as_arg descTable[@] optsTable[@] na função try_with_local_arys() envia:

  1. Isso realmente cria uma cópia dos arrays optsTable e optsTable que são acessíveis para a função takes_ary_as_arg .
  2. takes_ary_as_arg() função takes_ary_as_arg() recebe descTable[@] e optsTable[@] como strings, o que significa $1 == descTable[@] e $2 == optsTable[@] .
  3. no início da função takes_ary_as_arg() ele usa a sintaxe ${!parameter} , que é chamada de referência indireta ou às vezes dupla referência , isso significa que em vez 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. a declare não é necessária.

NB: Vale a pena mencionar que a inicialização 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 como se ele fosse citado (ao contrário de [*] ).

Minha reserva com isso

No BASH , escopo de variável local é a função atual e toda função filha é chamada, isso se traduz no fato de que a função 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 variáveis? É 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

Isto 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 (arrays sem todas as chaves definidas - com "buracos" nelas) não funcionarão como esperado - perderíamos as chaves e "condensaríamos" o array.

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 função genérica que aceita matrizes.

Este funciona mesmo com espaços:

format="\t%2s - %s\n"

function doAction
{
  local_array=("[email protected]")
  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[@]}"

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 sintaxe [subscript]=string array=(value1 ... valueN) [subscript]=string , assim atribuindo 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). Isso também faz com que você possa criar matrizes esparsas 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 ser avaliado para toda a matriz, índices e todos. Deve avaliar o inverso da declaração de atribuição. Qualquer estudante do terceiro ano 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 dita o resto da sintaxe da shell. 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 <file

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

Como tal, para passar um array para uma função, 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 função. Sim, a matriz que está sendo referenciada deve ter um escopo global, mas isso não deve ser um problema muito grande, considerando que isso é shell script. Passar uma matriz indexada associativa ou esparsa por valor para uma função 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 função para remontar a matriz.


Por mais feia que seja, aqui está uma solução 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 um array como "{array[@]"} e acessá-lo internamente usando array_inside=("[email protected]") . Isso se torna complicado quando há outros parâmetros posicionais / getopts . Nestes casos, eu tive que primeiro determinar e, em seguida, remover os parâmetros não associados ao array usando alguma combinação de shift e remoção de elemento de 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 eu passo para a função:

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

Espero que isso ajude alguém.


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

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 [email protected]; 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 

function aecho {
  set "$1[$2]"
  echo "${!1}"
}

Exemplo

$ foo=(dog cat bird)

$ aecho foo 1
cat






bash