Existe-t-il un moyen de sérialiser une variable shell? Supposons que j'ai une variable $VAR
et que je souhaite pouvoir l'enregistrer dans un fichier ou autre chose, puis la relire plus tard pour obtenir la même valeur?
Existe-t-il un moyen portable de le faire? (Je ne pense pas)
Existe-t-il un moyen de le faire en bash ou zsh?
Réponses:
Avertissement: avec l'une de ces solutions, vous devez être conscient que vous faites confiance à l'intégrité des fichiers de données pour être sûrs car ils seront exécutés en tant que code shell dans votre script. Les sécuriser est primordial pour la sécurité de votre script!
Implémentation en ligne simple pour sérialiser une ou plusieurs variables
Oui, à la fois dans bash et zsh, vous pouvez sérialiser le contenu d'une variable d'une manière qui est facile à récupérer à l'aide de la fonction
typeset
intégrée et de l'-p
argument. Le format de sortie est tel que vous pouvez simplementsource
la sortie pour récupérer vos trucs.Vous pouvez récupérer vos informations comme ceci plus tard dans votre script ou dans un autre script:
Cela fonctionnera pour bash, zsh et ksh, y compris le passage de données entre différents shells. Bash traduira cela dans sa
declare
fonction intégrée tandis que zsh l'implémentera avectypeset
mais comme bash a un alias pour que cela fonctionne dans les deux cas car nous utilisonstypeset
ici pour la compatibilité avec ksh.Implémentation généralisée plus complexe à l'aide de fonctions
L'implémentation ci-dessus est vraiment simple, mais si vous l'appelez fréquemment, vous voudrez peut-être vous donner une fonction utilitaire pour le rendre plus facile. De plus, si jamais vous essayez d'inclure les fonctions personnalisées ci-dessus, vous rencontrerez des problèmes avec la portée des variables. Cette version devrait éliminer ces problèmes.
Remarque pour tous ces éléments, afin de maintenir la compatibilité croisée bash / zsh, nous allons corriger les deux cas de
typeset
etdeclare
donc le code devrait fonctionner dans l'un ou les deux shells. Cela ajoute du volume et des dégâts qui pourraient être éliminés si vous ne faisiez cela que pour un shell ou un autre.Le principal problème lié à l'utilisation de fonctions à cet effet (ou à l'inclusion du code dans d'autres fonctions) est que la
typeset
fonction génère du code qui, lorsqu'il est renvoyé dans un script depuis l'intérieur d'une fonction, par défaut, crée une variable locale plutôt que globale.Cela peut être résolu avec l'un des nombreux hacks. Ma tentative initiale de résoudre ce problème a été d'analyser la sortie du processus de sérialisation
sed
pour ajouter l'-g
indicateur afin que le code créé définisse une variable globale lorsqu'il est récupéré.Notez que l'
sed
expression funky doit correspondre uniquement à la première occurrence de 'typeset' ou 'declare' et l'ajouter-g
comme premier argument. Il est nécessaire de ne faire correspondre que la première occurrence parce que, comme Stéphane Chazelas l'a souligné à juste titre dans les commentaires, sinon, il correspondra également aux cas où la chaîne sérialisée contient des retours à la ligne littéraux suivis du mot declare ou typeset.En plus de corriger mon faux pas d' analyse initial , Stéphane a également suggéré une façon moins cassante de pirater cela qui non seulement contourne les problèmes avec l'analyse des chaînes, mais pourrait être un crochet utile pour ajouter des fonctionnalités supplémentaires en utilisant une fonction wrapper pour redéfinir les actions lors de la récupération des données. Cela suppose que vous ne jouez à aucun autre jeu avec les commandes declare ou typeset, mais cette technique serait plus facile à mettre en œuvre dans une situation où vous incluiez cette fonctionnalité dans le cadre d'une autre fonction de votre choix ou vous ne contrôliez pas les données en cours d'écriture et si le
-g
drapeau avait été ajouté ou non . Quelque chose de similaire pourrait également être fait avec les alias, voir la réponse de Gilles pour une implémentation.Pour rendre le résultat encore plus utile, nous pouvons itérer sur plusieurs variables passées à nos fonctions en supposant que chaque mot du tableau d'arguments est un nom de variable. Le résultat devient quelque chose comme ceci:
Avec l'une ou l'autre solution, l'utilisation ressemblerait à ceci:
la source
declare
est l'bash
équivalent deksh
« stypeset
.bash
, prendzsh
également en chargetypeset
à cet égard,typeset
est plus portable.export -p
est POSIX, mais il ne prend aucun argument et sa sortie dépend du shell (bien qu'il soit bien spécifié pour les shells POSIX, donc par exemple lorsque bash ou ksh est appelé assh
). N'oubliez pas de citer vos variables; l'utilisation de l'opérateur split + glob ici n'a aucun sens.-E
ne se trouve que dans certains BSDsed
. Les valeurs des variables peuvent contenir des caractères de nouvelle ligne, ilsed 's/^.../.../'
n'est donc pas garanti que le fonctionne correctement.a=$'foo\ndeclare bar' bash -c 'declare -p a'
pour l'installation produira une ligne qui commence pardeclare
. Il vaut probablement mieux fairedeclare() { builtin declare -g "$@"; }
avant d'appelersource
(et désarmé après)shopt -s expandalias
lorsqu'il n'est pas interactif. Avec les fonctions, vous pouvez également améliorer ledeclare
wrapper pour qu'il ne restaure que les variables que vous spécifiez.Utilisez la redirection, la substitution de commandes et l'expansion des paramètres. Des guillemets doubles sont nécessaires pour conserver les espaces et les caractères spéciaux. La fin
x
enregistre les nouvelles lignes de fin qui seraient autrement supprimées dans la substitution de commande.la source
Sérialiser tout - POSIX
Dans n'importe quel shell POSIX, vous pouvez sérialiser toutes les variables d'environnement avec
export -p
. Cela n'inclut pas les variables shell non exportées. La sortie est correctement citée afin que vous puissiez la relire dans le même shell et obtenir exactement les mêmes valeurs de variable. La sortie peut ne pas être lisible dans un autre shell, par exemple ksh utilise la$'…'
syntaxe non POSIX .Sérialiser tout ou partie - ksh, bash, zsh
Ksh (à la fois pdksh / mksh et ATT ksh), bash et zsh offrent une meilleure facilité avec le
typeset
builtin.typeset -p
affiche toutes les variables définies et leurs valeurs (zsh omet les valeurs des variables qui ont été masquées avectypeset -H
). La sortie contient une déclaration appropriée pour que les variables d'environnement soient exportées lors de la lecture (mais si une variable est déjà exportée lors de la lecture, elle ne sera pas exportée), afin que les tableaux soient lus en tant que tableaux, etc. Ici aussi, la sortie est correctement cité mais n'est garanti que pour être lisible dans le même shell. Vous pouvez passer un ensemble de variables à sérialiser sur la ligne de commande; si vous ne transmettez aucune variable, tous sont sérialisés.En bash et zsh, la restauration ne peut pas être effectuée à partir d'une fonction car les
typeset
instructions à l'intérieur d'une fonction sont étendues à cette fonction. Vous devez exécuter. ./some_vars
dans le contexte où vous souhaitez utiliser les valeurs des variables, en veillant à ce que les variables qui étaient globales lors de l'exportation soient redéclarées comme globales. Si vous souhaitez relire les valeurs d'une fonction et les exporter, vous pouvez déclarer un alias ou une fonction temporaire. En zsh:En bash (qui utilise
declare
plutôt quetypeset
):Dans ksh,
typeset
déclare les variables locales dans les fonctions définies avecfunction function_name { … }
et les variables globales dans les fonctions définies avecfunction_name () { … }
.Sérialiser certains - POSIX
Si vous souhaitez plus de contrôle, vous pouvez exporter manuellement le contenu d'une variable. Pour imprimer le contenu d'une variable exactement dans un fichier, utilisez la fonction
printf
intégrée (echo
a quelques cas spéciaux commeecho -n
sur certains shells et ajoute une nouvelle ligne):Vous pouvez relire ceci avec
$(cat VAR.content)
, sauf que la substitution de commande supprime les retours à la ligne en fin de ligne. Pour éviter ce pli, faites en sorte que la sortie ne se termine jamais par une nouvelle ligne.Si vous souhaitez imprimer plusieurs variables, vous pouvez les citer avec des guillemets simples et remplacer tous les guillemets simples incorporés par
'\''
. Cette forme de citation peut être relue dans n'importe quel shell de style Bourne / POSIX. L'extrait suivant fonctionne dans n'importe quel shell POSIX. Il ne fonctionne que pour les variables de chaîne (et les variables numériques dans les shells qui les ont, bien qu'elles soient lues en tant que chaînes), il n'essaie pas de traiter les variables de tableau dans les shells qui les ont.Voici une autre approche qui ne bifurque pas un sous-processus mais est plus lourde sur la manipulation de chaînes.
Notez que sur les shells qui autorisent les variables en lecture seule, vous obtiendrez une erreur si vous essayez de relire une variable en lecture seule.
la source
$PWD
et$_
- veuillez voir vos propres commentaires ci-dessous.typeset
un aliastypeset -g
?Un grand merci à @ stéphane-chazelas qui a souligné 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 en shell l'entrée (contrairement à
declare -a
/declare -p
) et est donc sûre 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 la\<newlines>
paire de caractères, ils-d ...
doivent donc être transmis à la lecture, puis les retours à la ligne non échappés sont conservés.Tout cela est géré dans la
unserialise
fonction.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
etRS
mais aucun ne peut être défini commenewline
caractère car une nouvelle ligne échappée est supprimée parread
.Le caractère d'échappement doit être
\
la barre oblique inverse, car c'est ce qui est utilisé parread
pour éviter que le caractère soit reconnu comme unIFS
caractère.serialise
sérialisera"$@"
vers stdout,serialise_to
sérialisera vers la variable nommée dans$1
et désérialiser avec:
ou
par exemple
(sans retour à la ligne)
relisez-le:
ou
Bash's
read
respecte le caractère d'échappement\
(à moins que vous ne passiez l'indicateur -r) pour supprimer la signification spéciale des caractères tels que la séparation du champ d'entrée ou la délimitation de ligne.Si vous voulez sérialiser un tableau au lieu d'une simple liste d'arguments, passez simplement votre tableau comme liste d'arguments:
Vous pouvez utiliser
unserialise
dans une boucle comme vous le feriezread
parce que c'est juste une lecture encapsulée - mais rappelez-vous que le flux n'est pas séparé par des sauts de ligne:la source
bash
et leszsh
rendent sous$'\xxx'
. Essayez avecbash -c $'printf "%q\n" "\t"'
oubash -c $'printf "%q\n" "\u0378"'
$IFS
sa non-modification et ne parvient plus à restaurer correctement les éléments de tableau vides. En fait, il serait plus judicieux d'utiliser une valeur différente d'IFS et d'utiliser-d ''
pour éviter d'avoir à échapper à la nouvelle ligne. Par exemple, utilisez:
comme séparateur de champ et n'échappez qu'à cela et à la barre oblique inverse et utilisezIFS=: read -ad '' array
pour importer.read
. backslash-newline forread
est un moyen de continuer une ligne logique sur une autre ligne physique. Edit: ah je vois que vous mentionnez déjà le problème avec la nouvelle ligne.Vous pouvez utiliser
base64
:la source
Une autre façon de le faire est de vous assurer de gérer tous les
'
guillemets comme celui-ci:Ou avec
export
:Les première et deuxième options fonctionnent dans n'importe quel shell POSIX, en supposant que la valeur de la variable ne contient pas la chaîne:
La troisième option devrait fonctionner pour n'importe quel shell POSIX mais peut tenter de définir d'autres variables telles que
_
ouPWD
. La vérité est cependant que les seules variables qu'il pourrait essayer de définir sont définies et maintenues par le shell lui-même - et donc même si vous importezexport
la valeur de l'un d'entre eux - comme$PWD
par exemple - le shell les réinitialisera simplement la valeur correcte immédiatement de toute façon - essayez de fairePWD=any_value
et voyez par vous-même.Et parce que - au moins avec GNU
bash
- la sortie de débogage est automatiquement citée en toute sécurité pour une nouvelle entrée dans le shell, cela fonctionne quel que soit le nombre de'
guillemets dans"$VAR"
:$VAR
peut être défini ultérieurement sur la valeur enregistrée dans tout script dans lequel le chemin suivant est valide avec:la source
$$
est le PID du shell en cours d'exécution\$
. L'approche de base de l'utilisation d'un document ici pourrait fonctionner, mais c'est un matériau délicat et non à une ligne: quel que soit le marqueur de fin, vous devez choisir quelque chose qui n'apparaît pas dans la chaîne.$VAR
contient%
. La troisième commande ne fonctionne pas toujours avec des valeurs contenant plusieurs lignes (même après l'ajout des guillemets doubles évidemment manquants).env
. Je suis toujours curieux de savoir ce que vous voulez dire sur les lignes multiples -sed
supprime chaque ligne jusqu'à la rencontreVAR=
jusqu'à la dernière - donc toutes les lignes$VAR
sont transmises. Pouvez-vous s'il vous plaît fournir un exemple qui le casse?VAR
) n'est pas changéPWD
ou_
ou peut - être d' autres que quelques coquilles définissent. La deuxième méthode nécessite bash; le format de sortie de-v
n'est pas standardisé (aucun de dash, ksh93, mksh et zsh ne fonctionne).Presque identique mais un peu différent:
De votre script:
Cette fois ci-dessus est testé.
la source
'
,*
, etc.echo "$LVALUE=\"$RVALUE\""
est censé conserver les nouvelles lignes également et le résultat dans le fichier cfg devrait être comme: MY_VAR1 = "Line1 \ nLine 2" Ainsi, lors de l'évaluation de MY_VAR1, il contiendra également les nouvelles lignes. Bien sûr, vous pourriez avoir des problèmes si votre valeur stockée contient elle-même"
char. Mais cela pourrait également être pris en charge.