Shell-Variable in Bash oder Zsh serialisieren

Shell-Variable in Bash oder Zsh serialisieren

Gibt es eine Möglichkeit, eine Shell-Variable zu serialisieren? Angenommen, ich habe eine Variable $VARund möchte sie in einer Datei oder was auch immer speichern und sie später wieder lesen können, um denselben Wert zurückzuerhalten?

Gibt es eine portable Möglichkeit, dies zu tun? (Ich glaube nicht)

Gibt es eine Möglichkeit, dies in Bash oder Zsh zu tun?

Antwort1

Warnung:Bei jeder dieser Lösungen müssen Sie sich darüber im Klaren sein, dass Sie auf die Integrität der Datendateien vertrauen, da diese als Shell-Code in Ihrem Skript ausgeführt werden. Ihre Sicherung ist für die Sicherheit Ihres Skripts von größter Bedeutung!

Einfache Inline-Implementierung zum Serialisieren einer oder mehrerer Variablen

Ja, sowohl in Bash als auch in Zsh können Sie den Inhalt einer Variable so serialisieren, dass er mithilfe des typesetintegrierten Befehls und des -pArguments leicht abgerufen werden kann. Das Ausgabeformat ist so, dass Sie einfach sourcedie Ausgabe verwenden können, um Ihre Daten zurückzuerhalten.

 # You have variable(s) $FOO and $BAR already with your stuff
 typeset -p FOO BAR > ./serialized_data.sh

Sie können Ihre Daten entweder später in Ihrem Skript oder in einem ganz anderen Skript wie folgt zurückerhalten:

# Load up the serialized data back into the current shell
source serialized_data.sh

Dies funktioniert für Bash, Zsh und Ksh, einschließlich der Datenübertragung zwischen verschiedenen Shells. Bash übersetzt dies in seine integrierte declareFunktion, während Zsh dies mit implementiert, typesetaber da Bash einen Alias ​​dafür hat, der in beiden Fällen funktioniert, verwenden wir typesethier aus Gründen der Ksh-Kompatibilität.

Komplexere verallgemeinerte Implementierung mit Funktionen

Die obige Implementierung ist wirklich einfach, aber wenn Sie sie häufig aufrufen, möchten Sie sich vielleicht eine Hilfsfunktion zulegen, um es einfacher zu machen. Wenn Sie außerdem jemals versuchen, das Obige in benutzerdefinierte Funktionen einzubinden, werden Sie auf Probleme mit dem Variablenbereich stoßen. Diese Version sollte diese Probleme beseitigen.

Beachten Sie bei all dem, dass wir zur Aufrechterhaltung der Bash/Zsh-Kompatibilität beide Fälle beheben werden, typesetsodass declareder Code in einer oder beiden Shells funktionieren sollte. Dies führt zu etwas mehr Masse und Unordnung, die vermieden werden könnte, wenn Sie dies nur für die eine oder andere Shell tun würden.

Das Hauptproblem bei der Verwendung von Funktionen hierfür (oder beim Einfügen des Codes in andere Funktionen) besteht darin, dass die typesetFunktion Code generiert, der, wenn er aus einer Funktion heraus wieder in ein Skript eingebunden wird, standardmäßig eine lokale statt einer globalen Variable erstellt.

Dies kann mit einem von mehreren Hacks behoben werden. Mein erster Versuch, dies zu beheben, bestand darin, die Ausgabe des Serialisierungsprozesses zu analysieren, um seddas Flag hinzuzufügen, -gsodass der erstellte Code beim Zurückholen eine globale Variable definiert.

serialize() {
    typeset -p "$1" | sed -E '0,/^(typeset|declare)/{s/ / -g /}' > "./serialized_$1.sh"
}
deserialize() {
    source "./serialized_$1.sh"
}

Beachten Sie, dass der Funky- sedAusdruck nur das erste Vorkommen von entweder 'typeset' oder 'declare' abgleichen und -gals erstes Argument hinzufügen soll. Es ist notwendig, nur das erste Vorkommen abzugleichen, daStéphane Chazelasin Kommentaren zu Recht darauf hingewiesen werden, da es sonst auch auf Fälle zutrifft, in denen die serialisierte Zeichenfolge wörtliche Zeilenumbrüche enthält, auf die die Wörter „declare“ oder „typeset“ folgen.

Zusätzlich zur Korrektur meiner ursprünglichen AnalyseFauxpas, Stéphane auchempfohleneine weniger brüchige Möglichkeit, dies zu hacken, die nicht nur die Probleme beim Parsen der Zeichenfolgen umgeht, sondern auch ein nützlicher Haken sein könnte, um zusätzliche Funktionalität hinzuzufügen, indem eine Wrapper-Funktion verwendet wird, um die Aktionen neu zu definieren, die beim Zurückholen der Daten ausgeführt werden. Dies setzt voraus, dass Sie keine anderen Spiele mit den Befehlen „declare“ oder „typeset“ spielen, aber diese Technik wäre einfacher zu implementieren, wenn Sie diese Funktionalität als Teil einer anderen eigenen Funktion einbinden oder keine Kontrolle über die geschriebenen Daten und darüber haben, ob das -gFlag hinzugefügt wurde oder nicht. Etwas Ähnliches könnte auch mit Aliasen gemacht werden, sieheGilles' Antwortfür eine Implementierung.

Um das Ergebnis noch nützlicher zu machen, können wir über mehrere Variablen iterieren, die an unsere Funktionen übergeben werden, indem wir annehmen, dass jedes Wort im Argumentarray ein Variablenname ist. Das Ergebnis sieht ungefähr so ​​aus:

serialize() {
    for var in $@; do
        typeset -p "$var" > "./serialized_$var.sh"
    done
}

deserialize() {
    declare() { builtin declare -g "$@"; }
    typeset() { builtin typeset -g "$@"; }
    for var in $@; do
        source "./serialized_$var.sh"
    done
    unset -f declare typeset
}

Bei beiden Lösungen würde die Nutzung folgendermaßen aussehen:

# Load some test data into variables
FOO=(an array or something)
BAR=$(uptime)

# Save it out to our serialized data files
serialize FOO BAR

# For testing purposes unset the variables to we know if it worked
unset FOO BAR

# Load  the data back in from out data files
deserialize FOO BAR

echo "FOO: $FOO\nBAR: $BAR"

Antwort2

Verwenden Sie Umleitung, Befehlsersetzung und Parametererweiterung. Doppelte Anführungszeichen sind erforderlich, um Leerzeichen und Sonderzeichen beizubehalten. Das Anhängen xspeichert die nachfolgenden Zeilenumbrüche, die sonst bei der Befehlsersetzung entfernt würden.

#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}

Antwort3

Alles serialisieren – POSIX

In jeder POSIX-Shell können Sie alle Umgebungsvariablen serialisieren mitexport -p. Nicht exportierte Shell-Variablen sind hiervon nicht betroffen. Die Ausgabe wird richtig in Anführungszeichen gesetzt, sodass Sie sie in derselben Shell wieder lesen und genau dieselben Variablenwerte erhalten können. Die Ausgabe ist möglicherweise in einer anderen Shell nicht lesbar, da ksh beispielsweise die Nicht-POSIX- $'…'Syntax verwendet.

save_environment () {
  export -p >my_environment
}
restore_environment () {
  . ./my_environment
}

Einige oder alle serialisieren – ksh, bash, zsh

Ksh (sowohl pdksh/mksh als auch ATT ksh), bash und zsh bieten eine bessere Möglichkeit mit demtypeseteingebaut. typeset -pgibt alle definierten Variablen und ihre Werte aus (zsh lässt die Werte von Variablen aus, die mit ausgeblendet wurden typeset -H). Die Ausgabe enthält eine ordnungsgemäße Deklaration, sodass Umgebungsvariablen beim Rücklesen exportiert werden (wenn eine Variable beim Rücklesen jedoch bereits exportiert ist, wird sie nicht rückgängig gemacht), sodass Arrays als Arrays zurückgelesen werden usw. Auch hier ist die Ausgabe ordnungsgemäß in Anführungszeichen gesetzt, aber es ist nur garantiert, dass sie in derselben Shell lesbar ist. Sie können eine Reihe von Variablen zur Serialisierung auf der Befehlszeile übergeben. Wenn Sie keine Variable übergeben, werden alle serialisiert.

save_some_variables () {
  typeset -p VAR OTHER_VAR >some_vars
}

In Bash und Zsh kann die Wiederherstellung nicht aus einer Funktion heraus erfolgen, da typesetAnweisungen innerhalb einer Funktion auf diese Funktion beschränkt sind. Sie müssen . ./some_varsin dem Kontext ausführen, in dem Sie die Variablenwerte verwenden möchten, und dabei darauf achten, dass Variablen, die beim Export global waren, als global neu deklariert werden. Wenn Sie die Werte innerhalb einer Funktion zurücklesen und exportieren möchten, können Sie einen temporären Alias ​​oder eine temporäre Funktion deklarieren. In Zsh:

restore_and_make_all_global () {
  alias typeset='typeset -g'
  . ./some_vars
  unalias typeset
}

In Bash (das declareanstelle von verwendet typeset):

restore_and_make_all_global () {
  alias declare='declare -g'
  shopt -s expand_aliases
  . ./some_vars
  unalias declare
}

Deklariert in ksh typesetlokale Variablen in mit definierten Funktionen function function_name { … }und globale Variablen in mit definierten Funktionen function_name () { … }.

Einige serialisieren – POSIX

Wenn Sie mehr Kontrolle möchten, können Sie den Inhalt einer Variablen manuell exportieren. Um den Inhalt einer Variablen exakt in eine Datei zu drucken, verwenden Sie das printfintegrierte Feature ( echohat einige Sonderfälle, wie z. B. echo -nauf einigen Shells, und fügt eine neue Zeile hinzu):

printf %s "$VAR" >VAR.content

Sie können dies mit zurücklesen $(cat VAR.content), mit der Ausnahme, dass die Befehlsersetzung nachfolgende Zeilenumbrüche entfernt. Um dieses Problem zu vermeiden, sorgen Sie dafür, dass die Ausgabe nie mit einem Zeilenumbruch endet.

VAR=$(cat VAR.content && echo a)
if [ $? -ne 0 ]; then echo 1>&2 "Error reading back VAR"; exit 2; fi
VAR=${VAR%?}

Wenn Sie mehrere Variablen drucken möchten, können Sie diese mit einfachen Anführungszeichen versehen und alle eingebetteten einfachen Anführungszeichen durch ersetzen '\''. Diese Form der Anführungszeichen kann in jede Shell im Bourne/POSIX-Stil zurückgelesen werden. Der folgende Codeausschnitt funktioniert in jeder POSIX-Shell. Er funktioniert nur für Stringvariablen (und numerische Variablen in Shells, die diese haben, obwohl sie als Strings zurückgelesen werden); er versucht nicht, mit Arrayvariablen in Shells umzugehen, die diese haben.

serialize_variables () {
  for __serialize_variables_x do
    eval "printf $__serialize_variables_x=\\'%s\\'\\\\n \"\$${__serialize_variables_x}\"" |
    sed -e "s/'/'\\\\''/g" -e '1 s/=.../=/' -e '$ s/...$//'
  done
}

Hier ist ein anderer Ansatz, der keinen Unterprozess aufspaltet, aber stärker auf Zeichenfolgenmanipulation setzt.

serialize_variables () {
  for __serialize_variables_var do
    eval "__serialize_variables_tail=\${$__serialize_variables_var}"
    while __serialize_variables_quoted="$__serialize_variables_quoted${__serialize_variables_tail%%\'*}"
          [ "${__serialize_variables_tail%%\'*}" != "$__serialize_variables_tail" ]; do
      __serialize_variables_tail="${__serialize_variables_tail#*\'}"
      __serialize_variables_quoted="${__serialize_variables_quoted}'\\''"
    done
    printf "$__serialize_variables_var='%s'\n" "$__serialize_variables_quoted"
  done
}

Beachten Sie, dass bei Shells, die schreibgeschützte Variablen zulassen, eine Fehlermeldung auftritt, wenn Sie versuchen, eine schreibgeschützte Variable zurückzulesen.

Antwort4

printf 'VAR=$(cat <<\'$$VAR$$'\n%s\n'$$VAR$$'\n)' "$VAR" >./VAR.file

Eine andere Möglichkeit besteht darin, alle 'Hardquotes folgendermaßen zu behandeln:

sed '"s/'"'/&"&"&/g;H;1h;$!d;g;'"s/.*/VAR='&'/" <<$$VAR$$ >./VAR.file
$VAR
$$VAR$$

Oder mit export:

env - "VAR=$VAR" sh -c 'export -p' >./VAR.file 

Die erste und zweite Option funktionieren in jeder POSIX-Shell, vorausgesetzt, dass der Wert der Variablen nicht die Zeichenfolge enthält:

"\n${CURRENT_SHELLS_PID}VAR${CURRENT_SHELLS_PID}\n" 

Die dritte Option sollte für jede POSIX-Shell funktionieren, versucht aber möglicherweise, andere Variablen wie _oder zu definieren PWD. Tatsächlich werden die einzigen Variablen, die sie möglicherweise zu definieren versucht, von der Shell selbst gesetzt und verwaltet. Selbst wenn Sie also den exportWert von für eine dieser Variablen importieren, wie $PWDbeispielsweise, setzt die Shell sie trotzdem sofort auf den richtigen Wert zurück. Probieren Sie es aus PWD=any_valueund überzeugen Sie sich selbst.

Und da – zumindest bei GNU bash– die Debug-Ausgabe automatisch in sichere Anführungszeichen gesetzt wird, um sie erneut in die Shell einzugeben, funktioniert dies unabhängig von der Anzahl der 'Anführungszeichen in "$VAR":

 PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file

$VARkann später in jedem Skript auf den gespeicherten Wert gesetzt werden, in dem folgender Pfad gültig ist mit:

. ./VAR.file

verwandte Informationen