Exportando uma matriz no script bash

Eu não posso exportar uma matriz de um script bash para outro script bash como este:

export myArray[0]="Hello" export myArray[1]="World" 

Quando escrevo assim não há problema:

 export myArray=("Hello" "World") 

Por vários motivos, preciso inicializar minha matriz em várias linhas. Você tem alguma solução?

Variáveis ​​de matriz não podem (ainda) ser exportadas.

A partir do manpage do bash versão 4.1.5 no Ubuntu 10.04.

A seguinte declaração de Chet Ramey (atual mantenedor do bash a partir de 2011) é provavelmente a documentação mais oficial sobre esse “bug”:

Não há realmente uma boa maneira de codificar uma variável de matriz no ambiente.

http://www.mail-archive.com/bug-bash@gnu.org/msg01774.html

TL; DR: matrizes exportáveis ​​não são suportadas diretamente até e incluindo bash-4.3, mas você pode (efetivamente) exportar matrizes de uma das duas maneiras:

  • uma simples modificação na forma como os scripts filhos são invocados
  • use uma function exportada para armazenar a boot da matriz, com uma simples modificação nos scripts filhos

Ou, você pode esperar até que o bash-4.3 seja lançado (no estado de desenvolvimento / RC a partir de fevereiro de 2014, consulte ARRAY_EXPORT no Changelog). Atualização: este recurso não está habilitado em 4.3. Se você definir ARRAY_EXPORT durante a construção, a construção falhará. O autor declarou que não é planejado para concluir este recurso.


A primeira coisa a entender é que o ambiente bash (mais apropriadamente o ambiente de execução de comandos ) é diferente do conceito POSIX de um ambiente. O ambiente POSIX é uma coleção de pares name=value não tipificados e pode ser passado de um processo para seus filhos de várias maneiras (efetivamente, uma forma limitada de IPC ).

O ambiente de execução do bash é efetivamente um superconjunto disso, com variables ​​tipadas, sinalizadores, matrizes, funções somente leitura e exportáveis ​​e mais. Isso explica em parte porque a saída de set (bash builtin) e env ou printenv diferente.

Quando você invoca outra shell bash você está iniciando um novo processo, você perde algum estado bash. No entanto, se você criar um script em um ponto, o script será executado no mesmo ambiente; ou se você executar um subshell via ( ) o ambiente também é preservado (porque os garfos bash preservam seu estado completo, em vez de reinicializar usando o ambiente de processo).


A limitação referenciada na resposta do @ lesmana surge porque o ambiente POSIX é simplesmente pares name=value sem nenhum significado extra, então não há maneira de codificar ou formatar variables ​​tipadas, veja abaixo uma interessante peculiaridade sobre funções , e uma mudança futura em bash-4.3 (recurso de matriz proposto abandonado).

Existem algumas maneiras simples de fazer isso usando declare -p (built-in) para produzir parte do ambiente bash como um conjunto de uma ou mais declarações declare que podem ser usadas para reconstruir o tipo e valor de um “nome”. Isso é serialização básica, mas com um pouco menos da complexidade que algumas das outras respostas implicam. declare -p preserva índices de array, sparse arrays e citando valores problemáticos. Para a serialização simples de um array, você poderia apenas descarregar os valores linha por linha, e usar read -a myarray para restaurá-los (funciona com matrizes indexadas com índice 0, já que read -a automaticamente atribui índices).

Esses methods não exigem nenhuma modificação do (s) script (s) para o qual você está passando os arrays.

 declare -p array1 array2 > .bash_arrays # serialise to an intermediate file bash -c ". .bash_arrays; . otherscript.sh" # source both in the same environment 

Variações no formulário acima bash -c "..." são algumas vezes (mal) usadas em crontabs para definir variables.

Alternativas incluem:

 declare -p array1 array2 > .bash_arrays # serialise to an intermediate file BASH_ENV=.bash_arrays otherscript.sh # non-interactive startup script 

Ou como um one-liner:

 BASH_ENV=<(declare -p array1 array2) otherscript.sh 

O último usa a substituição de processo para passar a saída do comando declare como um script rc. (Este método só funciona no bash-4.0 ou posterior: versões anteriores incondicionalmente fstat() rc e usam o tamanho retornado para read() o arquivo de uma só vez, um FIFO retorna um tamanho de 0, e assim não funcionará como esperou.)

Em um shell não interativo (ou seja, shell script), o arquivo apontado pela variável BASH_ENV é automaticamente originado . Você deve certificar-se de que bash é invocado corretamente, possivelmente usando um shebang para invocar "bash" explicitamente, e não #!/bin/sh como bash não irá honrar BASH_ENV quando estiver no modo histórico / POSIX.

Se todos os nomes de array tiverem um prefixo comum, você poderá declare -p ${!myprefix*} para expandir uma lista deles, em vez de enumerá-los.

Você provavelmente não deve tentar exportar e reimportar todo o ambiente bash usando este método, algumas variables ​​e matrizes bash são read-only, e pode haver outros efeitos colaterais ao modificar variables ​​especiais.

(Você também pode fazer algo ligeiramente desagradável, serializando a definição da matriz para uma variável exportável, e usando eval , mas não vamos encorajar o uso de eval ...

 $ array=([1]=a [10]="bc") $ export scalar_array=$(declare -p array) $ bash # start a new shell $ eval $scalar_array $ declare -p array declare -a array='([1]="a" [10]="bc")' 

)


Como mencionado acima, há uma peculiaridade interessante: suporte especial para exportar funções através do ambiente:

 function myfoo() { echo foo } 

com export -f ou set +a para habilitar esse comportamento, resultará no ambiente (process), visível com printenv :

 myfoo=() { echo foo } 

A variável é functionname (ou functioname() para compatibilidade com versões anteriores) e seu valor é () { functionbody } . Quando um processo bash subsequente for iniciado, ele recriará uma function de cada variável de ambiente. Se você olhar as variables.c arquivo de código bash-4.2, verá as variables ​​iniciadas com () { são tratadas especialmente. (Embora a criação de uma function usando esta syntax com declare -f seja proibida.) Atualização: O problema de segurança "shellshock" está relacionado a esse recurso, sistemas contemporâneos podem desabilitar a importação automática de function do ambiente como uma atenuação.

Se você continuar lendo, você verá um código de proteção #if 0 (ou #if ARRAY_EXPORT ) que verifica variables ​​iniciando com ([ e terminando com ) , e um comentário dizendo " Variáveis ​​de array ainda não podem ser exportadas ". A boa notícia é que, na versão atual de desenvolvimento bash-4.3rc2, a capacidade de exportar matrizes indexadas (não associativas) está ativada. Esse recurso provavelmente não será ativado, conforme observado acima.

Podemos usar isso para criar uma function que restaure todos os dados da matriz necessários:

 % function sharearray() { array1=(abcd) } % export -f sharearray % bash -c 'sharearray; echo ${array1[*]}' 

Portanto, semelhante à abordagem anterior, invoque o script filho com:

 bash -c "sharearray; . otherscript.sh" 

Ou, você pode invocar condicionalmente a function sharearray no script filho, adicionando em algum ponto apropriado:

 [ "`type -t sharearray`" = "function" ] && sharearray 

Note que não há declare -a na function sharearray , se você fizer isso a matriz é implicitamente local para a function, o que não é o que é desejado. O bash-4.2 suporta declare -g que explicitamente faz uma variável global, de modo que ( declare -ga ) possa ser usado então. (Como as matrizes associativas exigem declare -A você não poderá usar este método para matrizes associativas antes do bash-4.2.) A documentação parallel GNU possui uma variação útil sobre este método, veja a discussão sobre --env na página man .


Sua pergunta, conforme expressa, também indica que você pode ter problemas com export própria export . Você pode exportar um nome depois de criá-lo ou modificá-lo. "exportable" é um sinalizador ou propriedade de uma variável, por conveniência, você também pode definir e exportar em uma única instrução. Até a export bash-4.2 espera apenas um nome, uma variável simples (escalar) ou um nome de function são suportados.

Mesmo se você pudesse (no futuro) exportar matrizes, a exportação de índices selecionados (uma fatia) pode não ser suportada (embora, como as matrizes são esparsas, não há motivo para que não seja permitido). Embora bash também suporte a declare -a name[0] syntax declare -a name[0] , o índice é ignorado, e "nome" é simplesmente uma matriz indexada normal.

Nossa. Eu não sei porque as outras respostas tornaram isso tão complicado. Bash tem quase suporte embutido para isso.

No script de exportação:

 myArray=( ' foo"bar ' $'\n''\nbaz)' ) # an array with two nasty elements myArray="${myArray[@]@Q}" ./importing_script.sh 

(Observe que as aspas duplas são necessárias para o tratamento correto dos espaços em branco nos elementos da matriz.)

Na input para importing_script.sh , o valor da variável de ambiente myArray compreende estes exatos 26 bytes:

 ' foo"bar ' $'\n\\nbaz)' 

Então, o seguinte irá reconstituir o array:

 eval "myArray=( ${myArray} )" 

CUIDADO! Não faça uma avaliação como essa se você não puder confiar na origem da variável de ambiente myArray . Este truque exibe a vulnerabilidade “Little Bobby Tables” . Imagine se alguém configurasse o valor de myArray para ) ; rm -rf / # ) ; rm -rf / # .

Como relatado lesmana, você não pode exportar matrizes. Então você tem que serializá-los antes de passar pelo ambiente. Esta serialização também é útil em outros lugares onde apenas uma string se encheckbox (su-c ‘string’, ssh host ‘string’). O caminho de código mais curto para fazer isso é abusar de ‘getopt’

 # preserve_array(arguments). return in _RET a string that can be expanded # later to recreate positional arguments. They can be restored with: # eval set -- "$_RET" preserve_array() { _RET=$(getopt --shell sh --options "" -- -- "$@") && _RET=${_RET# --} } # restore_array(name, payload) restore_array() { local name="$1" payload="$2" eval set -- "$payload" eval "unset $name && $name=("\$@")" } 

Use assim:

 foo=("1: &&& - *" "2: two" "3: %# abc" ) preserve_array "${foo[@]}" foo_stuffed=${_RET} restore_array newfoo "$foo_stuffed" for elem in "${newfoo[@]}"; do echo "$elem"; done ## output: # 1: &&& - * # 2: two # 3: %# abc 

Isso não aborda matrizes unset / sparse. Você pode reduzir as chamadas 2 ‘eval’ em restore_array.

você (oi!) pode usar isso, não precisa escrever um arquivo, para o Ubuntu 12.04, bash 4.2.24

Além disso, sua matriz de múltiplas linhas pode ser exportada.

gato >> exportArray.sh

 function FUNCarrayRestore() { local l_arrayName=$1 local l_exportedArrayName=${l_arrayName}_exportedArray # if set, recover its value to array if eval '[[ -n ${'$l_exportedArrayName'+dummy} ]]'; then eval $l_arrayName'='`eval 'echo $'$l_exportedArrayName` #do not put export here! fi } export -f FUNCarrayRestore function FUNCarrayFakeExport() { local l_arrayName=$1 local l_exportedArrayName=${l_arrayName}_exportedArray # prepare to be shown with export -p eval 'export '$l_arrayName # collect exportable array in string mode local l_export=`export -p \ |grep "^declare -ax $l_arrayName=" \ |sed 's"^declare -ax '$l_arrayName'"export '$l_exportedArrayName'"'` # creates exportable non array variable (at child shell) eval "$l_export" } export -f FUNCarrayFakeExport 

teste este exemplo no terminal bash (funciona com o bash 4.2.24):

 source exportArray.sh list=(abc) FUNCarrayFakeExport list bash echo ${list[@]} #empty :( FUNCarrayRestore list echo ${list[@]} #profit! :D 

Eu posso melhorar aqui

PS .: se alguém limpar / melhorar / makeItRunFaster gostaria de saber / ver, thx! : D

Eu estava editando um post diferente e cometi um erro. Augh De qualquer forma, talvez isso possa ajudar? https://stackoverflow.com/a/11944320/1594168

Observe que, como o formato da matriz do shell não está documentado no bash ou em qualquer outro lado do shell, é muito difícil retornar um array de shell de maneira independente da plataforma. Você teria que verificar a versão e também criar um script simples que concatena todos os arrays de shell em um arquivo no qual outros processos possam ser resolvidos.

No entanto, se você souber o nome da matriz que você quer levar para casa, então há um caminho, enquanto um pouco sujo.

Vamos dizer que tenho

 MyAry[42]="whatever-stuff"; MyAry[55]="foo"; MyAry[99]="bar"; 

Então eu quero levar para casa

 name_of_child=MyAry take_me_home="`declare -p ${name_of_child}`"; export take_me_home="${take_me_home/#declare -a ${name_of_child}=/}" 

Podemos ver isso sendo exportado, verificando de um subprocess

 echo ""|awk '{print "from awk =["ENVIRON["take_me_home"]"]"; }' 

Resultado:

 from awk =['([42]="whatever-stuff" [55]="foo" [99]="bar")'] 

Se for absolutamente necessário, use o env var para despejá-lo.

 env > some_tmp_file 

Então

Antes de executar o outro script,

 # This is the magic that does it all source some_tmp_file 

Para arrays com valores sem espaços, tenho usado um conjunto simples de funções para percorrer cada elemento da matriz e concatenar a matriz:

 _arrayToStr(){ array=($@) arrayString="" for (( i=0; i<${#array[@]}; i++ )); do if [[ $i == 0 ]]; then arrayString="\"${array[i]}\"" else arrayString="${arrayString} \"${array[i]}\"" fi done export arrayString="(${arrayString})" } _strToArray(){ str=$1 array=${str//\"/} array=(${array//[()]/""}) export array=${array[@]} } 

A primeira function com transformar a matriz em uma seqüência de caracteres, adicionando os parênteses de abertura e fechamento e escaping todas as aspas duplas. A segunda function removerá as aspas e os parênteses e os colocará em uma matriz fictícia.

Para exportar o array, você passaria todos os elementos do array original:

 array=(foo bar) _arrayToStr ${array[@]} 

Neste ponto, o array foi exportado para o valor $ arrayString. Para importar a matriz no arquivo de destino, renomeie a matriz e faça a conversão oposta:

 _strToArray "$arrayName" newArray=(${array[@]}) 

Muito obrigado a @ stéphane-chazelas, que apontou todos os problemas com minhas tentativas anteriores, isso agora parece funcionar para serializar uma matriz para stdout ou em uma variável.

Essa técnica não analisa a input do shell (diferentemente de declare -a / declare -p ) e, portanto, é segura contra inserção maliciosa de metacaracteres no texto serializado.

Nota: as linhas novas não são ignoradas, porque read exclui o par de caracteres \ , portanto, -d ... deve ser passado para leitura e as novas linhas sem escape são preservadas.

Tudo isso é gerenciado na function unserialise .

Dois caracteres mágicos são usados, o separador de campos e o separador de registros (para que vários arrays possam ser serializados no mesmo stream).

Esses caracteres podem ser definidos como FS e RS mas nenhum deles pode ser definido como caractere de newline , porque uma nova linha de escape é excluída pela read .

O caractere de escape deve ser \ a barra invertida, pois é isso que é usado pela read para evitar que o caractere seja reconhecido como um caractere IFS .

serialise serializará "$@" para stdout, serialise_to será serializado para a varable nomeada em $1

 serialise() { set -- "${@//\\/\\\\}" # \ set -- "${@//${FS:-;}/\\${FS:-;}}" # ; - our field separator set -- "${@//${RS:-:}/\\${RS:-:}}" # ; - our record separator local IFS="${FS:-;}" printf ${SERIALIZE_TARGET:+-v"$SERIALIZE_TARGET"} "%s" "$*${RS:-:}" } serialise_to() { SERIALIZE_TARGET="$1" serialise "${@:2}" } unserialise() { local IFS="${FS:-;}" if test -n "$2" then read -d "${RS:-:}" -a "$1" <<<"${*:2}" else read -d "${RS:-:}" -a "$1" fi } 

e não serializar com:

 unserialise data # read from stdin 

ou

 unserialise data "$serialised_data" # from args 

por exemplo

 $ serialise "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty' Now is the time;For all good men;To drink $drink;At the `party`;Party Party Party: 

(sem uma nova linha)

leia de volta:

 $ serialise_to s "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty' $ unserialise array "$s" $ echo "${array[@]/#/$'\n'}" Now is the time For all good men To drink $drink At the `party` Party Party Party 

ou

 unserialise array # read from stdin 

A read de Bash respeita o caractere de escape \ (a menos que você passe o sinalizador -r) para remover o significado especial de caracteres, como para a separação de campo de input ou a delimitação de linha.

Se você quiser serializar uma matriz em vez de uma simples lista de argumentos, apenas passe sua matriz como a lista de argumentos:

 serialise_array "${my_array[@]}" 

Você pode usar unserialise em um loop como você read porque é apenas uma leitura empacotada - mas lembre-se de que o stream não é separado por nova linha:

 while unserialise array do ... done 

Com base no uso @ mr.spuratic de BASH_ENV , aqui eu túnel $@ através do script -f -c

script -c pode ser usado para executar um comando dentro de outro pty (e grupo de processos), mas não pode passar nenhum argumento estruturado para .

Em vez disso, é uma cadeia simples para ser um argumento para a chamada da biblioteca do system .

Eu preciso tunelizar $@ do bash externo em $@ do bash chamado pelo script.

Como declare -p não pode tomar @ , aqui eu uso a variável bash magic _ (com um primeiro valor de array falso que será sobrescrito pelo bash). Isso me poupa de atropelar quaisquer variables ​​importantes:

Prova de conceito: BASH_ENV=<( declare -a _=("" "$@") && declare -p _ ) bash -c 'set -- "${_[@]:1}" && echo "$@"'

"Mas," você diz, "você está passando argumentos para bater - e de fato eu sou, mas estes são uma simples cadeia de caracteres conhecidos. Aqui está o uso por script

SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "$@") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "$@"' /tmp/logfile

que me dá essa function wrapper in_pty :

in_pty() { SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "$@") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "$@"' /tmp/logfile }

ou este wrapper sem function como uma string composta para Makefiles:

in_pty=bash -c 'SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "$$@") && declare -p _ && echo '"'"'set -- "$${_[@]:1}"'"'"') script -qfc '"'"'"$$@"'"'"' /tmp/logfile' --

...

$(in_pty) test --verbose $@ $^