web-dev-qa-db-fra.com

Exporter un tableau dans un script bash

Je ne peux pas exporter un tableau d'un script bash vers un autre script bash comme celui-ci:

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

Quand j'écris comme ça il n'y a pas de problème:

export myArray=("Hello" "World")

Pour plusieurs raisons, je dois initialiser mon tableau en plusieurs lignes. Avez-vous une solution?

47
Remiii

Les variables de tableau ne peuvent pas (encore) être exportées.

Depuis la page de manuel de bash version 4.1.5 sous ubuntu 10.04.

La déclaration suivante de Chet Ramey (mainteneur bash actuel en 2011) est probablement la documentation la plus officielle sur ce "bug":

Il n'y a pas vraiment de bon moyen d'encoder une variable de tableau dans l'environnement.

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

46
lesmana

TL; DR: les tableaux exportables ne sont pas directement pris en charge jusqu'à bash-4.3 inclus, mais vous pouvez (effectivement ) exporter des tableaux de deux manières:

  • une simple modification de la façon dont les scripts enfants sont appelés
  • utiliser une fonction exportée pour stocker l'initialisation du tableau, avec une simple modification des scripts enfants

Ou, vous pouvez attendre la sortie de bash-4.3 (en développement/état RC en février 2014, voir ARRAY_EXPORT dans le Changelog). Mise à jour: Cette fonctionnalité est pas activée dans 4.3. Si vous définissez ARRAY_EXPORT Lors de la génération, la génération échouera. L'auteur a déclaré il n'est pas prévu de terminer cette fonctionnalité.


La première chose à comprendre est que l'environnement bash (plus correctement environnement d'exécution de commande ) est différent du concept POSIX d'un environnement. environnement POSIX est une collection de paires name=value Non typées, et peut être passée d'un processus à ses enfants de diverses manières (en fait une forme limitée de IPC ).

L'environnement d'exécution bash en est en fait un surensemble, avec des variables typées, des indicateurs en lecture seule et exportables, des tableaux, des fonctions et plus encore. Cela explique en partie pourquoi la sortie de set (bash builtin) et env ou printenv diffère.

Lorsque vous invoquez un autre shell bash vous lancez un nouveau processus, vous perdez un état bash. Cependant, si vous dot-source un script, le script est exécuté dans le même environnement; ou si vous exécutez un sous-shell via ( ) l'environnement est également préservé (car bash bifurque, en préservant son état complet, plutôt que de réinitialiser en utilisant l'environnement de processus).


La limitation référencée dans la réponse de @ lesmana survient parce que l'environnement POSIX est simplement des paires name=value Sans signification supplémentaire, il n'y a donc pas de moyen convenu d'encoder ou de formater des variables typées, voir ci-dessous pour une bizarrerie bash intéressante concernant les fonctions , et un changement à venir dans bash-4.3(fonctionnalité de tableau proposée abandonnée).

Il existe quelques façons simples de le faire en utilisant declare -p (Intégré) pour sortir certains de l'environnement bash sous la forme d'un ensemble d'une ou plusieurs instructions declare qui peuvent être utilisées pour reconstruire le type et la valeur d'un "nom". C'est de base sérialisation , mais avec un peu moins de complexité certaines des autres réponses impliquent. declare -p Préserve les index des tableaux, les tableaux épars et les citations de valeurs gênantes. Pour la sérialisation simple d'un tableau, vous pouvez simplement vider les valeurs ligne par ligne et utiliser read -a myarray Pour le restaurer (fonctionne avec les tableaux contigus indexés 0, car read -a Attribue automatiquement les index).

Ces méthodes ne nécessitent aucune modification du ou des scripts auxquels vous transmettez les tableaux.

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

Les variations du formulaire bash -c "..." Ci-dessus sont parfois (mal) utilisées dans les crontabs pour définir des variables.

Les alternatives incluent:

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

Ou, en une ligne:

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

Le dernier utilise substitution de processus pour passer la sortie de la commande declare en tant que script rc. (Cette méthode ne fonctionne que dans bash-4.0 ou version ultérieure: les versions antérieures inconditionnellement fstat() fichiers rc et utilisent la taille retournée à read() le fichier en une seule fois; a FIFO renvoie une taille de 0 et ne fonctionnera donc pas comme prévu.)

Dans un Shell non interactif (c'est-à-dire un script Shell), le fichier pointé par la variable BASH_ENV Est provenant automatiquement . Vous devez vous assurer que bash est correctement invoqué, en utilisant éventuellement un Shebang pour appeler explicitement "bash", et non #!/bin/sh Car bash n'honorera pas BASH_ENV En mode historique/POSIX.

Si tous vos noms de tableaux ont un préfixe commun, vous pouvez utiliser declare -p ${!myprefix*} Pour développer une liste d'entre eux, au lieu de les énumérer.

Vous ne devriez probablement pas essayer d'exporter et de réimporter l'environnement entier bash en utilisant cette méthode, certaines variables et tableaux bash spéciaux sont en lecture seule, et il peut y avoir d'autres effets secondaires lors de la modification de spécial variables.

(Vous pouvez également faire quelque chose de légèrement désagréable en sérialisant la définition du tableau en une variable exportable et en utilisant eval, mais n'encourageons pas l'utilisation de eval ...

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

)


Comme mentionné ci-dessus, il y a une particularité intéressante: un support spécial pour l'exportation de fonctions via l'environnement:

function myfoo() {
    echo foo
}

avec export -f ou set +a pour activer ce comportement, entraînera cela dans l'environnement (de processus), visible avec printenv:

myfoo=() { echo foo
}

La variable est functionname (ou functioname() pour la compatibilité descendante) et sa valeur est () { functionbody }. Lorsqu'un processus bash suivant démarre, il recrée une fonction à partir de chacune de ces variables d'environnement. Si vous regardez dans le fichier source bash-4.2 variables.c, Vous verrez que les variables commençant par () { Sont traitées spécialement. (Bien que la création d'une fonction utilisant cette syntaxe avec declare -f Soit interdite.) Update: Le problème de sécurité " Shellshock" est lié à cette fonctionnalité, contemporaine les systèmes peuvent désactiver l'importation automatique de fonctions depuis l'environnement à titre d'atténuation.

Si vous continuez à lire, vous verrez un code de garde #if 0 (Ou #if ARRAY_EXPORT) Qui vérifie les variables commençant par ([ Et se terminant par ), Et un commentaire indiquant " Les variables de tableau ne peuvent pas encore être exportées". La bonne nouvelle est que dans la version de développement actuelle bash-4.3rc2, la possibilité d'exporter des tableaux indexés (non associatifs) est activée. Il est peu probable que cette fonctionnalité soit activée, comme indiqué ci-dessus.

Nous pouvons l'utiliser pour créer une fonction qui restaure toutes les données de tableau requises:

% function sharearray() {
    array1=(a b c d)
}

% export -f sharearray 

% bash -c 'sharearray; echo ${array1[*]}'

Donc, comme pour l'approche précédente, appelez le script enfant avec:

bash -c "sharearray; . otherscript.sh"

Ou, vous pouvez invoquer conditionnellement la fonction sharearray dans le script enfant en ajoutant à un moment approprié:

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

Notez qu'il n'y a pas de declare -a Dans la fonction sharearray, si vous faites cela, le tableau est implicitement local à la fonction, ce qui n'est pas ce qui est souhaité. bash-4.2 prend en charge declare -g qui rend explicitement une variable globale, afin que (declare -ga) puisse être utilisé alors. (Puisque les tableaux associatifs nécessitentdeclare -A Vous ne pourrez pas utiliser cette méthode pour les tableaux associatifs avant bash-4.2.) Le GNU = parallel la documentation a des variations utiles sur cette méthode, voir la discussion de --env dans la page de manuel .


Votre question, telle qu'elle est formulée, indique également que vous pouvez avoir des problèmes avec export lui-même. Vous pouvez exporter un nom après l'avoir créé ou modifié. "exportable" est un indicateur ou une propriété d'une variable, pour plus de commodité, vous pouvez également définir et exporter dans une seule instruction. Jusqu'à bash-4.2 export n'attend qu'un nom, soit une simple variable (scalaire) soit un nom de fonction sont pris en charge.

Même si vous pouviez (à l'avenir) exporter des tableaux, l'exportation d'index sélectionnés (une tranche) peut ne pas être prise en charge (bien que, comme les tableaux sont rares, il n'y a aucune raison pour que cela ne soit pas autorisé). Bien que bash supporte également la syntaxe declare -a name[0], L'indice est ignoré et "nom" est simplement un tableau indexé normal.

31
mr.spuratic

Bon sang. Je ne sais pas pourquoi les autres réponses ont rendu cela si compliqué. Bash a presque un support intégré pour cela.

Dans le script d'exportation:

myArray=( '  foo"bar  ' $'\n''\nbaz)' )  # an array with two nasty elements

myArray="${myArray[@]@Q}" ./importing_script.sh

(Remarque: les guillemets doubles sont nécessaires pour une gestion correcte des espaces blancs dans les éléments du tableau.)

À l'entrée de importing_script.sh, la valeur de la variable d'environnement myArray comprend ces 26 octets exacts:

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

Ensuite, ce qui suit reconstituera le tableau:

eval "myArray=( ${myArray} )"

ATTENTION! Ne pas eval comme ça si vous ne pouvez pas faire confiance à la source de la variable d'environnement myArray. Cette astuce présente la vulnérabilité "Little Bobby Tables" . Imaginez que quelqu'un définisse la valeur de myArray sur ) ; rm -rf / #.

8
Matt Whitlock

Comme l'a signalé lesmana, vous ne pouvez pas exporter de tableaux. Vous devez donc les sérialiser avant de traverser l'environnement. Cette sérialisation est également utile à d'autres endroits où ne tient qu'une chaîne (su -c 'chaîne', ssh Hôte 'chaîne'). Le moyen le plus court de le faire est d'abuser 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=("\$@")"
}

Utilisez-le comme ceci:

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

Cela ne concerne pas les tableaux non définis/clairsemés. Vous pourrez peut-être réduire les 2 appels "eval" dans restore_array.

1
smoser

vous (salut!) pouvez l'utiliser, vous n'avez pas besoin d'écrire un fichier, pour ubuntu 12.04, bash 4.2.24

En outre, votre tableau de plusieurs lignes peut être exporté.

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

testez cet exemple sur terminal bash (fonctionne avec bash 4.2.24):

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

Je peux l'améliorer ici

PS: si quelqu'un efface/améliore/makeItRunFaster je voudrais savoir/voir, merci! :RÉ

0
Aquarius Power

Beaucoup grâce à @ stéphane-chazelas qui a signalé tous les problèmes avec mes précédentes tentatives, cela semble maintenant fonctionner pour sérialiser un tableau en stdout ou en variable.

Cette technique n'analyse pas l'entrée en shell (contrairement à declare -a/declare -p) et est ainsi protégé contre l'insertion malveillante de métacaractères dans le texte sérialisé.

Remarque: les sauts de ligne ne sont pas échappés, car read supprime le \<newlines> paire de caractères, donc -d ... doit à la place être passé en lecture, puis les retours à la ligne non échappés sont conservés.

Tout cela est géré dans la fonction unserialise.

Deux caractères magiques sont utilisés, le séparateur de champ et le séparateur d'enregistrement (afin que plusieurs tableaux puissent être sérialisés dans le même flux).

Ces caractères peuvent être définis comme FS et RS mais aucun ne peut être défini comme newline car une nouvelle ligne échappée est supprimée par read.

Le caractère d'échappement doit être \ la barre oblique inverse, car c'est ce qui est utilisé par read pour éviter que le caractère ne soit reconnu comme IFS.

serialise sérialisera "$@" à stdout, serialise_to va sérialiser vers la variable nommée dans $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
}

et désérialiser avec:

unserialise data # read from stdin

ou

unserialise data "$serialised_data" # from args

par exemple.

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

(sans retour à la ligne)

relisez-le:

$ 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

read de Bash respecte le caractère d'échappement \ (sauf si vous passez l'indicateur -r) pour supprimer la signification spéciale des caractères tels que la séparation des champs de saisie ou la délimitation des lignes.

Si vous voulez sérialiser un tableau au lieu d'une simple liste d'arguments, passez simplement votre tableau comme liste d'arguments:

serialise_array "${my_array[@]}"

Vous pouvez utiliser unserialise dans une boucle comme vous le feriez read car il s'agit simplement d'une lecture encapsulée - mais n'oubliez pas que le flux n'est pas séparé par des sauts de ligne:

while unserialise array
do ...
done
0
Sam Liddicott

Pour les tableaux avec des valeurs sans espaces, j'ai utilisé un ensemble simple de fonctions pour parcourir chaque élément du tableau et concaténer le tableau:

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

La première fonction transforme le tableau en chaîne en ajoutant les parenthèses ouvrantes et fermantes et en échappant à tous les guillemets doubles. La deuxième fonction supprimera les guillemets et les parenthèses et les placera dans un tableau factice.

Pour exporter le tableau, vous devez passer tous les éléments du tableau d'origine:

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

À ce stade, le tableau a été exporté dans la valeur $ arrayString. Pour importer le tableau dans le fichier de destination, renommez le tableau et effectuez la conversion opposée:

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

J'étais en train de modifier un autre post et j'ai fait une erreur. Augh. Quoi qu'il en soit, cela pourrait peut-être aider? https://stackoverflow.com/a/11944320/1594168

Notez que parce que le format de tableau de Shell n'est pas documenté sur bash ou tout autre côté de Shell, il est très difficile de renvoyer un tableau de Shell de manière indépendante de la plateforme. Vous devez vérifier la version et créer également un script simple qui concatrie tous les tableaux Shell dans un fichier dans lequel d'autres processus peuvent être résolus.

Cependant, si vous connaissez le nom du tableau que vous souhaitez ramener à la maison , il existe un moyen, tout en étant un peu sale.

Disons que j'ai

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

Je veux donc le ramener à la maison

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

On peut le voir s'exporter, en vérifiant à partir d'un sous-processus

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

Résultat :

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

Si nous devons absolument, utilisez le var env pour le vider.

env > some_tmp_file

Alors

Avant d'exécuter un autre script,

# This is the magic that does it all
source some_tmp_file
0
GreenFox

Basé sur @ mr.spuratic use of BASH_ENV, Ici je tunnel $@ À travers script -f -c

script -c <command> <logfile> Peut être utilisé pour exécuter une commande dans un autre pty (et groupe de processus) mais il ne peut pas passer d'arguments structurés à <command>.

Au lieu de cela, <command> Est une simple chaîne devant servir d'argument à l'appel de bibliothèque system.

J'ai besoin de tunneler $@ Du bash externe dans $@ Du bash invoqué par le script.

Comme declare -p Ne peut pas prendre @, J'utilise ici la variable magic bash _ (Avec une première valeur de tableau factice car elle sera écrasée par bash). Cela m'évite de piétiner toutes les variables importantes:

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

"Mais," dites-vous, "vous passez des arguments à bash - et en effet je le suis, mais ce sont une simple chaîne de caractères connus. Voici l'utilisation par script

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

ce qui me donne cette fonction wrapper in_pty:

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

ou ce wrapper sans fonction en tant que chaîne composable pour 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 $@ $^

0
Sam Liddicott