Esportare una matrice nello script bash

Non riesco a esportare un array da uno script bash ad un altro script bash come questo:

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

Quando scrivo così non ci sono problemi:

 export myArray=("Hello" "World") 

Per diversi motivi ho bisogno di inizializzare il mio array su più righe. Avete qualche soluzione?

Le variabili dell’array non possono (ancora) essere esportate.

Dalla pagina di manuale di bash versione 4.1.5 sotto ubuntu 10.04.

La seguente dichiarazione di Chet Ramey (attuale manutentore di bash del 2011) è probabilmente la documentazione più ufficiale su questo “bug”:

Non c’è davvero un buon modo per codificare una variabile di matrice nell’ambiente.

http://www.mail-archive.com/[email protected]/msg01774.html

TL; DR: gli array esportabili non sono supportati direttamente fino a includere bash-4.3, ma è ansible (in modo efficace) esportare gli array in due modi:

  • una semplice modifica al modo in cui vengono invocati gli script figlio
  • utilizzare una funzione esportata per memorizzare l’inizializzazione dell’array, con una semplice modifica agli script figlio

Oppure, puoi aspettare fino al rilascio di bash-4.3 (in sviluppo / stato RC a partire da febbraio 2014, vedi ARRAY_EXPORT nel Changelog). Aggiornamento: questa funzione non è abilitata in 4.3. Se si definisce ARRAY_EXPORT durante la creazione, la generazione avrà esito negativo. L’autore ha dichiarato che non è previsto il completamento di questa funzione.


La prima cosa da capire è che l’ambiente bash (più propriamente l’ ambiente di esecuzione dei comandi ) è diverso dal concetto POSIX di un ambiente. L’ ambiente POSIX è un insieme di coppie name=value non tipizzate e può essere passato da un processo ai suoi figli in vari modi (in pratica una forma limitata di IPC ).

L’ambiente di esecuzione di bash è effettivamente un superset di questo, con variabili tipizzate, flag di sola lettura ed esportabili, matrici, funzioni e altro. Questo spiega in parte perché l’output di set (bash builtin) e di env o printenv differiscono.

Quando invochi un’altra shell bash, stai avviando un nuovo processo, perdi qualche stato bash. Tuttavia, se si esegue il dot-source di uno script, lo script viene eseguito nello stesso ambiente; oppure se si esegue una subshell tramite ( ) l’ambiente viene anche preservato (perché le fork di bash, preservando il suo stato completo, anziché reinizializzare usando l’ambiente di processo).


La limitazione a cui si fa riferimento nella risposta di @ lesmana deriva dal fatto che l’ambiente POSIX è semplicemente coppie name=value senza alcun significato aggiuntivo, quindi non esiste un modo concordato per codificare o formattare le variabili tipizzate, vedere di seguito per una curiosità di base sulle funzioni e un cambiamento imminente bash-4.3 (funzionalità di array proposta abbandonata).

Ci sono un paio di semplici modi per farlo usando declare -p (built-in) per produrre alcuni degli ambienti bash come un insieme di una o più declare dichiarative che possono essere usate per ribuild il tipo e il valore di un “nome”. Questa è una serializzazione di base, ma con una complessità piuttosto ridotta implicano alcune delle altre risposte. declare -p conserva indici di array, array sparsi e quotazioni di valori problematici. Per la serializzazione semplice di un array si può semplicemente scaricare i valori riga per riga e usare read -a myarray per ripristinarlo (funziona con gli array contigui a 0 indicizzati, dato che read -a assegna automaticamente gli indici).

Questi metodi non richiedono alcuna modifica degli script a cui si stanno passando gli array.

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

Le variazioni sulla forma bash -c "..." cui sopra sono a volte (errate) usate in crontabs per impostare variabili.

Le alternative includono:

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

Oppure, come monotero:

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

L'ultimo utilizza la sostituzione del processo per passare l'output del comando declare come script rc. (Questo metodo funziona solo in bash-4.0 o versioni successive: versioni precedenti incondizionatamente file fstat() rc e usa la dimensione restituita a read() il file in una volta sola: una FIFO restituisce una dimensione pari a 0, quindi non funzionerà come sperato.)

In una shell non intertriggers (es. Script di shell) il file indicato dalla variabile BASH_ENV viene automaticamente BASH_ENV . Devi assicurarti che bash sia invocato correttamente, possibilmente usando uno shebang per invocare "bash" esplicitamente, e non #!/bin/sh dato che bash non onorerà BASH_ENV quando è in modalità storica / POSIX.

Se tutti i nomi degli array hanno un prefisso comune, puoi usare declare -p ${!myprefix*} per espandere un elenco di essi, invece di enumerarli.

Probabilmente non dovresti tentare di esportare e reimportare l' intero ambiente bash usando questo metodo, alcune speciali variabili bash e gli array sono di sola lettura, e ci possono essere altri effetti collaterali durante la modifica di variabili speciali.

(Si potrebbe anche fare qualcosa di leggermente spiacevole serializzando la definizione dell'array su una variabile esportabile e usando eval , ma non incoraggiamo l'uso di 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")' 

)


Come indicato sopra, c'è una stranezza interessante: supporto speciale per esportare le funzioni attraverso l'ambiente:

 function myfoo() { echo foo } 

con export -f o set +a per abilitare questo comportamento, ciò si verificherà nell'ambiente (processo), visibile con printenv :

 myfoo=() { echo foo } 

La variabile è functionname (o functioname() per retrocompatibilità) e il suo valore è () { functionbody } . Quando viene avviato un processo bash successivo, verrà ricreata una funzione da ciascuna di tali variabili d'ambiente. Se sbirciate nelle variables.c file sorgente di bash-4.2, vedrete che le variabili che iniziano con () { sono gestite in modo speciale. (Anche se la creazione di una funzione utilizzando questa syntax con declare -f è vietata.) Aggiornamento: il problema di sicurezza " shellshock" è correlato a questa funzionalità, i sistemi contemporanei possono disabilitare l'importazione di funzioni automatiche dall'ambiente come una mitigazione.

Se continui a leggere, vedrai un codice di protezione #if 0 (o #if ARRAY_EXPORT ) che controlla le variabili che iniziano con ([ e finiscono con ) , e un commento che afferma che "Le variabili dell'array non possono ancora essere esportate ". La buona notizia è che nella versione di sviluppo corrente bash-4.3rc2 è abilitata la possibilità di esportare array indicizzati (non associativi). È improbabile che questa funzione sia abilitata, come indicato sopra.

Possiamo usarlo per creare una funzione che ripristina i dati di array richiesti:

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

Quindi, simile all'approccio precedente, invoca lo script figlio con:

 bash -c "sharearray; . otherscript.sh" 

Oppure, puoi invocare condizionatamente la funzione sharearray nello script figlio aggiungendo ad un punto appropriato:

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

Nota che non esiste alcuna declare -a nella funzione sharearray , se lo fai la matrice è implicitamente locale alla funzione, che non è ciò che è voluto. bash-4.2 supporta declare -g che rende esplicitamente una variabile globale, così che ( declare -ga ) possa essere usato allora. (Poiché gli array associativi richiedono la declare -A non sarà ansible utilizzare questo metodo per gli array associativi prima di bash-4.2.) La documentazione parallel GNU ha una variazione utile su questo metodo, vedere la discussione di --env nella pagina man .


La tua domanda come formulata indica anche che potresti avere problemi con l' export stessa. Puoi esportare un nome dopo averlo creato o modificato. "esportabile" è un flag o una proprietà di una variabile, per comodità puoi anche impostare ed esportare in una singola istruzione. L' export fino a bash-4.2 si aspetta solo un nome, sono supportati sia una variabile (scalare) semplice che un nome di funzione.

Anche se potresti (in futuro) esportare array, esportare gli indici selezionati (una sezione) potrebbe non essere supportato (anche se poiché gli array sono sparsi, non c'è motivo per cui non possa essere consentito). Sebbene bash supporti anche la syntax declare -a name[0] , il pedice viene ignorato e "name" è semplicemente un normale array indicizzato.

Jeez. Non so perché le altre risposte l’abbiano reso così complicato. Bash ha un supporto quasi integrato per questo.

Nello script di esportazione:

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

(Nota, le doppie virgolette sono necessarie per la corretta gestione degli spazi bianchi all’interno degli elementi dell’array.)

All’ingresso in importing_script.sh , il valore della variabile d’ambiente myArray comprende questi esatti 26 byte:

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

Quindi il seguente ricostituirà l’array:

 eval "myArray=( ${myArray} )" 

ATTENZIONE! Non eval questo modo se non ci si può fidare della fonte della variabile d’ambiente myArray . Questo trucco mostra la vulnerabilità “Little Bobby Tables” . Immagina se qualcuno dovesse impostare il valore di myArray su ) ; rm -rf / # ) ; rm -rf / # .

Come riportato da lesmana, non è ansible esportare gli array. Quindi devi serializzarli prima di passare attraverso l’ambiente. Questa serializzazione è utile anche in altre posizioni in cui solo una stringa si adatta (su -c ‘string’, ssh host ‘string’). Il modo più breve per farlo è abusare di ‘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 "" -- -- "[email protected]") && _RET=${_RET# --} } # restore_array(name, payload) restore_array() { local name="$1" payload="$2" eval set -- "$payload" eval "unset $name && $name=("\[email protected]")" } 

Usalo in questo modo:

 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 

Questo non risolve gli array nonset / sparsi. Potresti riuscire a ridurre le 2 chiamate “eval” in restore_array.

tu (ciao!) puoi usare questo, non ho bisogno di scrivere un file, per ubuntu 12.04, bash 4.2.24

Inoltre, è ansible esportare l’array di più righe.

cat >> 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 

prova questo esempio sul terminale bash (funziona con bash 4.2.24):

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

Potrei migliorarlo qui

PS: se qualcuno cancella / migliora / makeItRunFaster mi piacerebbe sapere / vedere, grazie! : D

Stavo modificando un post diverso e ho fatto un errore. Augh. Ad ogni modo, forse questo potrebbe aiutare? https://stackoverflow.com/a/11944320/1594168

Si noti che poiché il formato dell’array della shell non è documentato su bash o su qualsiasi altro lato della shell, è molto difficile restituire un array di shell in modo indipendente dalla piattaforma. Dovresti controllare la versione e creare un semplice script che concateni tutti gli array di shell in un file in cui altri processi possano essere risolti.

Tuttavia, se conosci il nome dell’array che vuoi portare a casa, allora c’è un modo, anche se un po ‘sporco.

Diciamo che ho

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

Quindi voglio portarlo a 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}=/}" 

Possiamo vederlo esportato, controllando da un processo secondario

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

Risultato:

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

Se assolutamente dobbiamo, usare env var per scaricarlo.

 env > some_tmp_file 

Poi

Prima di eseguire l’altro script,

 # This is the magic that does it all source some_tmp_file 

Per gli array con valori senza spazi, ho utilizzato un semplice insieme di funzioni per scorrere ogni elemento dell’array e concatenare l’array:

 _arrayToStr(){ array=([email protected]) 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[@]} } 

La prima funzione con trasformare la matrice in una stringa aggiungendo le parentesi di apertura e chiusura e sfuggire tutte le doppie virgolette. La seconda funzione rimuoverà le virgolette e le parentesi e le posizionerà in una matrice fittizia.

Per esportare l'array, si passa a tutti gli elementi dell'array originale:

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

A questo punto, l'array è stato esportato nel valore $ arrayString. Per importare la matrice nel file di destinazione, rinominare la matrice ed eseguire la conversione opposta:

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

Molto grazie a @ stéphane-chazelas che ha evidenziato tutti i problemi con i miei precedenti tentativi, ora sembra che funzioni per serializzare un array su stdout o su una variabile.

Questa tecnica non analizza shell l’input (diversamente da declare -a / declare -p ) e quindi è sicuro contro l’inserimento malizioso di metacaratteri nel testo serializzato.

Nota: i newline non sono sfuggiti, poiché read cancella la coppia di caratteri \ , quindi -d ... deve essere passato per leggere, e quindi i newline senza escape non vengono salvati.

Tutto questo è gestito nella funzione unserialise .

Vengono utilizzati due caratteri magici, il separatore di campo e il separatore di record (in modo che più array possano essere serializzati nello stesso stream).

Questi caratteri possono essere definiti come FS e RS ma nessuno dei due può essere definito come carattere di newline perché una nuova riga di escape viene cancellata dalla read .

Il carattere di escape deve essere \ il backslash, in quanto è ciò che viene utilizzato da read per evitare che il personaggio venga riconosciuto come un carattere IFS .

serialise serializzerà "[email protected]" su stdout, serialise_to verrà serializzato sul varabile chiamato in $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 unserialise con:

 unserialise data # read from stdin 

o

 unserialise data "$serialised_data" # from args 

per esempio

 $ 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: 

(senza una nuova riga finale)

rileggilo:

 $ 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 

o

 unserialise array # read from stdin 

La read di Bash rispetta il carattere di escape \ (a meno che non si passi il flag -r) per rimuovere il significato speciale di caratteri come la separazione dei campi di input o la delimitazione delle righe.

Se vuoi serializzare un array invece di un semplice elenco di argomenti, passa semplicemente l'array come elenco di argomenti:

 serialise_array "${my_array[@]}" 

Puoi usare unserialise in un ciclo come quello che read perché è solo una lettura avvolta, ma ricorda che lo stream non è separato da una nuova riga:

 while unserialise array do ... done 

Basato sull’uso @ mr.spuratic di BASH_ENV , qui tunnel [email protected] tramite script -f -c

script -c può essere usato per eseguire un comando all’interno di un altro pty (e gruppo di processi) ma non può passare alcun argomento strutturato a .

Invece è una semplice stringa per essere un argomento alla chiamata della libreria di system .

Ho bisogno di scavare [email protected] della bash esterna in [email protected] della bash invocata dallo script.

Dato che declare -p non può prendere @ , qui uso la variabile magic bash _ (con un valore fittizio primo array che verrà sovrascritto da bash). Questo mi evita di calpestare qualsiasi variabile importante:

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

"Ma," tu dici, "stai trasmettendo argomenti a bash - e in effetti lo sono, ma questi sono una semplice stringa di caratteri noti.

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

che mi dà questa funzione wrapper in_pty :

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

o questo wrapper senza funzione come stringa componibile per Makefile:

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

...

$(in_pty) test --verbose [email protected] $^