Gibt es eine Möglichkeit, eine Shell-Variable zu serialisieren? Angenommen, ich habe eine Variable $VAR
und möchte sie in einer Datei oder in einem anderen Format speichern und später erneut lesen, um denselben Wert wiederzuerlangen.
Gibt es eine tragbare Möglichkeit, dies zu tun? (Ich glaube nicht)
Gibt es eine Möglichkeit, dies in bash oder zsh zu tun?
Antworten:
Warnung: Bei jeder dieser Lösungen müssen Sie sich bewusst sein, dass Sie der Integrität der Datendateien vertrauen, um sicher zu sein, da diese in Ihrem Skript als Shell-Code ausgeführt werden. Ihre Sicherheit ist von größter Bedeutung für die Sicherheit Ihres Skripts!
Einfache Inline-Implementierung zum Serialisieren einer oder mehrerer Variablen
Ja, sowohl in bash als auch in zsh können Sie den Inhalt einer Variablen auf eine Weise serialisieren, die mit dem
typeset
Builtin und dem-p
Argument leicht abgerufen werden kann . Das Ausgabeformat ist so, dass Sie einfachsource
die Ausgabe durchführen können, um Ihre Daten wieder zu erhalten.Du kannst deine Sachen entweder später in deinem Skript oder in einem anderen Skript zurückbekommen:
Dies funktioniert für bash, zsh und ksh, einschließlich der Übergabe von Daten zwischen verschiedenen Shells. Bash übersetzt dies in seine eingebaute
declare
Funktion, während zsh dies implementiert,typeset
aber da bash einen Alias hat, funktioniert dies so oder so, wie wir estypeset
hier aus Gründen der ksh-Kompatibilität verwenden.Komplexere verallgemeinerte Implementierung mit Funktionen
Die obige Implementierung ist sehr einfach. Wenn Sie sie jedoch häufig aufrufen, möchten Sie sich möglicherweise eine Utility-Funktion geben, um sie zu vereinfachen. Wenn Sie jemals versuchen, die oben genannten Funktionen in benutzerdefinierte Funktionen einzubeziehen, treten außerdem Probleme mit dem Variablenumfang auf. Diese Version sollte diese Probleme beseitigen.
Beachten Sie, dass zur Aufrechterhaltung der Bash / Zsh-Kreuzkompatibilität beide Fälle behoben werden
typeset
unddeclare
der Code daher in einer oder beiden Shells funktionieren sollte. Dies fügt etwas Masse und Unordnung hinzu, die beseitigt werden könnten, 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 Einbeziehen des Codes in andere Funktionen) besteht darin, dass die
typeset
Funktion Code generiert, der standardmäßig eine lokale Variable und keine globale Variable erstellt, wenn er aus einer Funktion in ein Skript zurückgeleitet wird.Dies kann mit einem von mehreren Hacks behoben werden. Mein anfänglicher Versuch, dies zu beheben, bestand darin, die Ausgabe des Serialisierungsprozesses zu analysieren
sed
, um das-g
Flag hinzuzufügen, sodass der erstellte Code eine globale Variable definiert, wenn er zurückgegeben wird.Beachten Sie, dass der flippige
sed
Ausdruck nur mit dem ersten Vorkommen von 'typeset' oder 'declare' übereinstimmen und-g
als erstes Argument hinzufügen soll . Es ist nur erforderlich, das erste Vorkommen abzugleichen, da es , wie Stéphane Chazelas in Kommentaren zutreffend ausgeführt hat, auch Fälle abdeckt, in denen der serialisierte String wörtliche Zeilenumbrüche enthält, denen das Wort declare oder typeset folgt.Neben meinem ersten Parsing zu korrigieren Fauxpas , Stéphane auch vorgeschlagen , einen weniger spröden Weg , dies zu hacken , dass nicht nur Seite Schritte , um die Probleme mit den Saiten Parsen aber könnte eine nützliche Haken sein , zusätzliche Funktionalität hinzufügen , indem Sie eine Wrapper - Funktion mit den Aktionen neu zu definieren Dies setzt voraus, dass Sie keine anderen Spiele mit den Befehlen declare oder typeset spielen. Diese Technik wäre jedoch einfacher zu implementieren, wenn Sie diese Funktionalität als Teil einer anderen eigenen Funktion oder als Teil einer anderen Funktion einbinden würden Sie hatten keine Kontrolle darüber, welche Daten geschrieben wurden und ob das
-g
Flag hinzugefügt wurde oder nicht . Ähnliches könnte auch mit Aliasen geschehen, siehe Gilles 'Antwort für eine Implementierung.Um das Ergebnis noch nützlicher zu machen, können wir mehrere Variablen durchlaufen, die an unsere Funktionen übergeben wurden, indem wir davon ausgehen, dass jedes Wort im Argumentarray ein Variablenname ist. Das Ergebnis sieht ungefähr so aus:
In beiden Fällen sieht die Verwendung folgendermaßen aus:
quelle
declare
ist dasbash
Äquivalent vonksh
'stypeset
.bash
,zsh
Unterstützt auchtypeset
so in dieser Hinsichttypeset
mehr tragbar ist.export -p
ist POSIX, aber es braucht kein Argument und seine Ausgabe ist shellabhängig (obwohl es für POSIX-Shells gut spezifiziert ist, zum Beispiel, wenn bash oder ksh als aufgerufen wirdsh
). Denken Sie daran, Ihre Variablen anzugeben. Die Verwendung des split + glob-Operators ist hier nicht sinnvoll.-E
nur in einigen BSDs gefunden wirdsed
. Variablenwerte können Zeilenumbrüche enthalten, daher kann die korrekte Funktionsed 's/^.../.../'
nicht garantiert werden.a=$'foo\ndeclare bar' bash -c 'declare -p a'
für install wird eine zeile ausgegeben, die mit beginntdeclare
. Es ist wahrscheinlich besser,declare() { builtin declare -g "$@"; }
bevor Sie anrufensource
(und danach deaktivieren)shopt -s expandalias
wenn Sie nicht interaktiv sind. Mit Funktionen können Sie dendeclare
Wrapper auch so erweitern, dass nur die von Ihnen angegebenen Variablen wiederhergestellt werden.Verwenden Sie Umleitung, Befehlssubstitution und Parametererweiterung. Doppelte Anführungszeichen sind erforderlich, um Leerzeichen und Sonderzeichen beizubehalten. Durch das Nachziehen werden
x
die nachfolgenden Zeilenumbrüche gespeichert, die ansonsten bei der Befehlsersetzung entfernt würden.quelle
Alle serialisieren - POSIX
In jeder POSIX-Shell können Sie alle Umgebungsvariablen mit serialisieren
export -p
. Dies schließt nicht exportierte Shell-Variablen nicht ein. Die Ausgabe wird ordnungsgemäß in Anführungszeichen gesetzt, sodass Sie sie in derselben Shell zurücklesen und genau dieselben Variablenwerte abrufen können. Die Ausgabe ist in einer anderen Shell möglicherweise nicht lesbar, z. B. verwendet ksh die Nicht-POSIX-$'…'
Syntax.Serialisieren Sie einige oder alle - ksh, bash, zsh
Ksh (sowohl pdksh / mksh als auch ATT ksh), bash und zsh bieten mit dem
typeset
eingebauten Code eine bessere Möglichkeit.typeset -p
druckt alle definierten Variablen und ihre Werte aus (zsh lässt die Werte von Variablen aus, die mit ausgeblendet wurdentypeset -H
). Die Ausgabe enthält eine ordnungsgemäße Deklaration, damit Umgebungsvariablen beim Zurücklesen exportiert werden (wenn eine Variable jedoch bereits beim Zurücklesen exportiert wird, wird sie nicht unexportiert), sodass Arrays als Arrays usw. zurückgelesen werden. Hier auch die Ausgabe wird korrekt zitiert, ist aber garantiert nur in derselben Shell lesbar. Sie können eine Reihe von Variablen zum Serialisieren in der Befehlszeile übergeben. Wenn Sie keine Variable übergeben, werden alle serialisiert.In bash und zsh kann eine Funktion nicht wiederhergestellt werden, da
typeset
Anweisungen in einer Funktion auf diese Funktion beschränkt sind. Sie müssen. ./some_vars
in dem Kontext ausgeführt werden, in dem Sie die Werte der Variablen verwenden möchten, und dabei darauf achten, dass Variablen, die beim Export global waren, erneut als global 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:In der Bash (die
declare
eher verwendet alstypeset
):Deklariert in ksh
typeset
lokale Variablen in mit definierten Funktionenfunction function_name { … }
und globale Variablen in mit definierten Funktionenfunction_name () { … }
.Serialisieren Sie einige - POSIX
Wenn Sie mehr Kontrolle wünschen, können Sie den Inhalt einer Variablen manuell exportieren. Um den Inhalt einer Variablen genau in eine Datei zu drucken, verwenden Sie die integrierte
printf
Funktion (echo
enthält einige Sonderfälle, z. B.echo -n
einige Shells, und fügt eine neue Zeile hinzu):Sie können dies mit zurücklesen, mit der
$(cat VAR.content)
Ausnahme, dass die Befehlsersetzung nachfolgende Zeilenumbrüche entfernt. Um diese Falten zu vermeiden, sollten Sie dafür sorgen, dass die Ausgabe niemals mit einem Zeilenumbruch endet.Wenn Sie mehrere Variablen drucken möchten, können Sie diese in einfache Anführungszeichen setzen und alle eingebetteten einfachen Anführungszeichen durch ersetzen
'\''
. Diese Form des Zitierens kann in jede Bourne / POSIX-artige Shell zurückgelesen werden. Das folgende Snippet funktioniert in jeder POSIX-Shell. Es funktioniert nur für String-Variablen (und numerische Variablen in Shells, die über sie verfügen, obwohl sie als Strings zurückgelesen werden). Es wird nicht versucht, Array-Variablen in Shells zu behandeln, die über sie verfügen.Hier ist ein weiterer Ansatz, der keinen Unterprozess auslöst, sondern die Manipulation von Zeichenfolgen verstärkt.
Beachten Sie, dass bei Shells, die schreibgeschützte Variablen zulassen, eine Fehlermeldung angezeigt wird, wenn Sie versuchen, eine schreibgeschützte Variable zurückzulesen.
quelle
$PWD
und mit sich$_
- bitte lesen Sie Ihre eigenen Kommentare weiter unten.typeset
einem Alias fürtypeset -g
?Vielen Dank @ stéphane-Chazelas , die all Probleme mit meinen früheren Versuchen , wiesen darauf hin, das scheint jetzt an der Arbeit um ein Array zu stdout oder in eine Variable serialise.
Diese Technik analysiert die Eingabe nicht per Shell (im Gegensatz zu
declare -a
/declare -p
) und ist daher sicher gegen das böswillige Einfügen von Metazeichen in den serialisierten Text.Hinweis: Zeilenumbrüche sind nicht entgangen, weil
read
Löschungen das\<newlines>
Zeichenpaar, so-d ...
muss stattdessen lesen übergeben werden, und dann werden unescaped Zeilenumbrüche erhalten.All dies wird in der
unserialise
Funktion verwaltet.Es werden zwei magische Zeichen verwendet, das Feldtrennzeichen und das Datensatztrennzeichen (sodass mehrere Arrays zu demselben Stream serialisiert werden können).
Diese Zeichen können als definiert werden
FS
undRS
aber auch nicht alsnewline
Zeichen definiert werden , da eine mit Escape-Zeichen versehene neue Zeile durch gelöscht wirdread
.Das Escape-Zeichen muss
\
der Backslash sein, da dies verwendet wirdread
, um zu verhindern, dass das Zeichen als Zeichen erkannt wirdIFS
.serialise
wird serialisieren"$@"
auf stdout ,serialise_to
wird auf die Variable serialisiert, die in benannt ist$1
und unserialisieren mit:
oder
z.B
(ohne abschließende Newline)
lies es zurück:
oder
Bash's
read
respektiert den Escape-Charakter\
(es sei denn, Sie übergeben das Flag -r), um die spezielle Bedeutung von Zeichen zu entfernen, z. B. für die Trennung von Eingabefeldern oder die Begrenzung von Zeilen.Wenn Sie ein Array anstelle einer einfachen Argumentliste serialisieren möchten, übergeben Sie Ihr Array einfach als Argumentliste:
Sie können diese Funktion
unserialise
in einer Schleife wieread
gewohnt verwenden, da es sich nur um einen umbrochenen Lesevorgang handelt. Beachten Sie jedoch, dass der Stream nicht durch Zeilenumbrüche getrennt ist:quelle
bash
undzsh
diese als rendern$'\xxx'
. Versuchen Sie es mitbash -c $'printf "%q\n" "\t"'
oderbash -c $'printf "%q\n" "\u0378"'
$IFS
der Unverändertheit ab und kann leere Array-Elemente jetzt nicht mehr ordnungsgemäß wiederherstellen. Tatsächlich wäre es sinnvoller, einen anderen Wert von IFS zu verwenden und-d ''
zu vermeiden, dass Newline umgangen werden muss. Verwenden Sie zum Beispiel:
als Feldtrennzeichen und lassen Sie nur diesen und den umgekehrten Schrägstrich unberührt und verwenden Sie ihnIFS=: read -ad '' array
zum Importieren.read
. Mit backslash-newline forread
können Sie eine logische Zeile auf eine andere physische Zeile setzen. Edit: ah ich sehe das du das problem mit newline schon erwähnst.Sie könnten verwenden
base64
:quelle
Eine andere Möglichkeit besteht darin, sicherzustellen, dass Sie alle
'
harten Anführungszeichen wie folgt verarbeiten:Oder mit
export
:Die erste und zweite Option funktionieren in jeder POSIX-Shell, vorausgesetzt, der Wert der Variablen enthält keine Zeichenfolge:
Die dritte Option sollte für jede POSIX-Shell funktionieren, kann jedoch versuchen, andere Variablen wie
_
oder zu definierenPWD
. Die Wahrheit ist jedoch, dass die einzigen Variablen, die es zu definieren versucht, von der Shell selbst festgelegt und verwaltet werden - und selbst wenn Sie denexport
Wert für eine von ihnen importieren - wie$PWD
zum Beispiel -, setzt die Shell sie einfach auf zurück den korrekten Wert trotzdem sofort ermitteln - versuchen Sie esPWD=any_value
und überzeugen Sie sich.Und weil - zumindest bei GNUs
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"
:$VAR
kann später in jedem Skript, in dem der folgende Pfad gültig ist, auf den gespeicherten Wert gesetzt werden:quelle
$$
ist die PID der laufenden Shell, hast du das Zitat falsch und gemein verstanden\$
oder so? Der grundsätzliche Ansatz, ein Dokument hier zu verwenden, könnte funktionieren, aber es ist schwierig und kein Einzeilenmaterial: Unabhängig davon, was Sie als Endmarker auswählen, müssen Sie etwas auswählen, das nicht in der Zeichenfolge enthalten ist.$VAR
enthält%
. Der dritte Befehl funktioniert nicht immer mit Werten, die mehrere Zeilen enthalten (auch nach dem Hinzufügen der offensichtlich fehlenden Anführungszeichen).env
. Ich bin immer noch gespannt, was du mit den mehreren Zeilen meinst -sed
löscht jede ZeileVAR=
bis zur letzten - damit alle Zeilen$VAR
weitergeleitet werden. Können Sie uns bitte ein Beispiel geben, das es zerstört?VAR
) nicht geändertPWD
oder_
oder vielleicht auch andere , dass einige Schalen definieren. Die zweite Methode erfordert bash; Das Ausgabeformat von-v
ist nicht standardisiert (Bindestrich, ksh93, mksh und zsh funktionieren nicht).Fast gleich, aber ein bisschen anders:
Aus Ihrem Skript:
Diese obige Zeit wird getestet.
quelle
'
,*
etc.echo "$LVALUE=\"$RVALUE\""
soll auch die Zeilenumbrüche behalten und das Ergebnis in der cfg_file sollte so aussehen: MY_VAR1 = "Line1 \ nLine 2" Wenn also MY_VAR1 ausgewertet wird, enthält es auch die neuen Zeilen. Natürlich könnten Sie Probleme haben, wenn Ihr gespeicherter Wert selbst"
char enthält. Aber auch das könnte erledigt werden.