Ich bin immer sehr zurückhaltend, $IFS
weil es ein globales Problem ist.
Aber oft macht es das Laden von Strings in ein Bash-Array nett und prägnant, und für Bash-Skripte ist es schwierig, Prägnanz zu finden.
Also denke ich, es ist vielleicht besser als gar nichts, wenn ich versuche, den Anfangsinhalt einer $IFS
anderen Variablen zu "speichern" und ihn dann sofort wiederherzustellen, nachdem ich mit $IFS
etwas fertig bin .
Ist das praktisch? Oder ist es im Grunde genommen sinnlos und ich sollte einfach direkt auf das IFS
zurückgreifen, was es für seine späteren Verwendungen benötigt?
bash
shell-script
Steven Lu
quelle
quelle
$' \t\n'
wenn Sie bash verwenden.unset $IFS
Es wird einfach nicht immer auf die erwartete Standardeinstellung zurückgesetzt.Antworten:
Sie können IFS nach Bedarf speichern und zuweisen. Daran ist nichts auszusetzen. Es ist nicht ungewöhnlich, den Wert für die Wiederherstellung nach einer vorübergehenden, schnellen Änderung zu speichern, wie z. B. in Ihrem Beispiel für die Array-Zuweisung.
Wie @llua in seinem Kommentar zu Ihrer Frage erwähnt, wird durch einfaches Deaktivieren von IFS das Standardverhalten wiederhergestellt, das dem Zuweisen einer Leerzeichen-Tabulator-Zeile entspricht.
Es lohnt sich zu überlegen, wie problematischer es sein kann, IFS nicht explizit zu setzen / zu entfernen, als dies zu tun.
Ab der POSIX 2013 Edition, 2.5.3 Shell-Variablen :
Eine POSIX-kompatible, aufgerufene Shell kann IFS von ihrer Umgebung erben oder nicht. Daraus folgt:
"$*"
) verwenden möchte, jedoch möglicherweise unter einer Shell ausgeführt wird, die IFS aus der Umgebung heraus initialisiert, muss IFS explizit aktivieren / deaktivieren, um sich gegen das Eindringen in die Umgebung zu schützen.NB Es ist wichtig zu verstehen, dass für diese Diskussion das Wort "angerufen" eine bestimmte Bedeutung hat. Eine Shell wird nur aufgerufen, wenn sie explizit mit ihrem Namen (einschließlich eines
#!/path/to/shell
Shebang) aufgerufen wird . Eine Subshell, wie sie beispielsweise von$(...)
oder erstellt wird,cmd1 || cmd2 &
ist keine aufgerufene Shell, und ihr IFS ist (zusammen mit dem Großteil ihrer Ausführungsumgebung) mit dem der übergeordneten Shell identisch. Eine aufgerufene Shell setzt den Wert von$
auf ihre PID, während Subshells ihn erben.Dies ist nicht nur eine pedantische Auseinandersetzung; In diesem Bereich gibt es tatsächliche Abweichungen. Hier ist ein kurzes Skript, das das Szenario mit verschiedenen Shells testet. Es exportiert ein modifiziertes IFS (gesetzt auf
:
) in eine aufgerufene Shell, die dann ihr Standard-IFS druckt.IFS ist im Allgemeinen nicht für den Export markiert, aber wenn dies der Fall ist, beachten Sie, dass bash, ksh93 und mksh die Umgebungen ignorieren
IFS=:
, während dash und busybox dies berücksichtigen.Einige Versionsinformationen:
Obwohl bash, ksh93 und mksh IFS nicht aus der Umgebung initialisieren, exportieren sie ihr modifiziertes IFS erneut.
Wenn Sie IFS aus irgendeinem Grund portabel über die Umgebung übergeben müssen, können Sie dies nicht mit IFS selbst tun. Sie müssen den Wert einer anderen Variablen zuweisen und diese Variable für den Export markieren. Kinder müssen diesen Wert dann explizit ihrem IFS zuweisen.
quelle
IFS
Wert in den meisten Situationen, in denen er verwendet werden soll, explizit anzugeben. Daher ist es oft nicht besonders produktiv, auch nur zu versuchen, seinen ursprünglichen Wert "beizubehalten".read
oder doppelte Verweise auf nicht in Anführungszeichen gesetzt sind$*
. Diese Liste ist einfach zu übersehen, daher ist sie möglicherweise nicht vollständig (insbesondere wenn man die POSIX-Erweiterungen moderner Shells in Betracht zieht).Im Allgemeinen empfiehlt es sich, die Bedingungen auf den Standard zurückzusetzen.
In diesem Fall jedoch nicht so sehr.
Warum?:
$' \t\n'
.unset IFS
verhält es sich so , als wäre es auf den Standardwert eingestellt .Das Speichern des IFS-Werts ist ebenfalls problematisch.
Wenn das ursprüngliche IFS nicht gesetzt wurde, setzt der Code
IFS="$OldIFS"
das IFS auf""
, nicht auf.Um den Wert von IFS tatsächlich beizubehalten (auch wenn nicht festgelegt), verwenden Sie Folgendes:
quelle
bash
,unset IFS
nicht zu ungesetzt IFS , wenn es in einem übergeordneten Kontext (Funktionskontext) lokale erklärt worden war und nicht im aktuellen Kontext.Sie haben Recht, wenn Sie zögern, ein globales Unternehmen zu bekämpfen. Fürchte dich nicht, es ist möglich, sauberen Arbeitscode zu schreiben, ohne jemals die aktuelle globale
IFS
Version zu ändern oder einen umständlichen und fehleranfälligen Save / Restore-Vorgang auszuführen .Sie können:
Setze IFS für einen einzelnen Aufruf:
oder
Setze IFS in eine Subshell:
Beispiele
So erhalten Sie eine durch Kommas getrennte Zeichenfolge aus einem Array:
Hinweis: Dies
-
dient nur zum Schutz eines leeren Arrays,set -u
indem ein Standardwert angegeben wird, wenn dieser nicht festgelegt ist (in diesem Fall ist dieser Wert die leere Zeichenfolge) .Die
IFS
Änderung ist nur innerhalb der durch die$()
Befehlsersetzung hervorgerufenen Subshell anwendbar . Dies liegt daran, dass Subshells Kopien der Variablen der aufrufenden Shell haben und daher deren Werte lesen können. Alle von der Subshell vorgenommenen Änderungen wirken sich jedoch nur auf die Kopie der Subshell und nicht auf die übergeordnete Variable aus.Vielleicht denken Sie auch: Warum überspringen Sie nicht die Subshell und tun dies einfach:
Hier findet kein Befehlsaufruf statt, und diese Zeile wird stattdessen als zwei unabhängige nachfolgende Variablenzuweisungen interpretiert, als wäre es:
Lassen Sie uns zum Schluss erklären, warum diese Variante nicht funktioniert:
Der
echo
Befehl wird zwar mit derIFS
Variablen "set to" aufgerufen,
, ist aberecho
egal oder wird nicht verwendetIFS
. Die Magie des Expandierens"${array[*]}"
zu einer Zeichenkette wird von der (Unter-) Shell selbst ausgeführt, bevor sieecho
überhaupt aufgerufen wird.So lesen Sie eine ganze Datei (die keine
NULL
Bytes enthält ) in eine einzelne Variable mit dem Namen einVAR
:Anmerkung:
IFS=
ist dasselbe wieIFS=""
undIFS=''
, wobei alle IFS auf die leere Zeichenfolge setzen. Dies unterscheidet sich stark vonunset IFS
: Wenn diesIFS
nicht der Fall ist, ist das Verhalten aller intern verwendeten Bash-FunktionenIFS
genau so, als hätteIFS
es den Standardwert von$' \t\n'
.Durch das Setzen
IFS
der leeren Zeichenfolge wird sichergestellt, dass führende und nachfolgende Leerzeichen erhalten bleiben.Der Befehl
-d ''
oder-d ""
weist read an, nur den aktuellen Aufruf einesNULL
Bytes anstelle der üblichen Newline zu stoppen.Um
$PATH
entlang seiner:
Begrenzer zu teilen :Dieses Beispiel dient nur der Veranschaulichung. In dem allgemeinen Fall, dass Sie entlang eines Trennzeichens aufteilen, ist es möglich, dass die einzelnen Felder dieses Trennzeichen enthalten (eine maskierte Version davon). Denken Sie daran, eine Zeile einer
.csv
Datei einzulesen, deren Spalten selbst Kommas enthalten können (in irgendeiner Weise mit Escapezeichen versehen oder in Anführungszeichen gesetzt). Das obige Snippet funktioniert in solchen Fällen nicht wie vorgesehen.Das heißt, es ist unwahrscheinlich, dass Sie auf solche
:
Pfade stoßen$PATH
. UNIX / Linux-Pfadnamen dürfen zwar ein enthalten:
, es scheint jedoch, dass Bash solche Pfade ohnehin nicht verarbeiten kann, wenn Sie versuchen, sie zu Ihren$PATH
Dateien hinzuzufügen und ausführbare Dateien darin zu speichern, da kein Code zum Parsen von mit Escapezeichen versehenen / zitierten Doppelpunkten vorhanden ist : Quellcode der Bash 4.4 .Beachten Sie schließlich, dass das Snippet eine nachgestellte neue Zeile an das letzte Element des resultierenden Arrays anfügt (wie von @ StéphaneChazelas in jetzt gelöschten Kommentaren angegeben). Wenn die Eingabe eine leere Zeichenfolge ist, handelt es sich bei der Ausgabe um ein einzelnes Element Array, in dem das Element aus einer newline (
$'\n'
) besteht.Motivation
Der grundlegende
old_IFS="${IFS}"; command; IFS="${old_IFS}"
Ansatz, der das Globale berührtIFS
, funktioniert erwartungsgemäß für die einfachsten Skripte. Sobald Sie jedoch eine Komplexität hinzufügen, kann diese leicht auseinander brechen und subtile Probleme verursachen:command
es sich um eine Bash-Funktion handelt, die auch die globale Funktion ändertIFS
(entweder direkt oder ausgeblendet in einer weiteren Funktion, die sie aufruft), und dabei versehentlich dieselbe globaleold_IFS
Variable für das Speichern / Wiederherstellen verwendet, tritt ein Fehler auf.IFS
das naive Speichern und Wiederherstellen nicht , wenn der ursprüngliche Status von nicht festgelegt wurde, und führt sogar zu völligen Fehlern, wenn die häufig (falsch) verwendeteset -u
(akaset -o nounset
) Shell-Option verwendet wird ist in Kraft.help trap
). Wenn dieser Code auch das globale ändertIFS
oder davon ausgeht, dass es einen bestimmten Wert hat, können subtile Fehler auftreten.Sie könnten eine robustere save / restore - Sequenz (wie derjenige in vorgeschlagenen ersinnen dieser andere Antwort einige oder alle dieser Probleme zu vermeiden. Allerdings würde man das Stück laut Standardcode wiederholen müssen , wo immer Sie vorübergehend eine benutzerdefinierte benötigen
IFS
. Diese Reduziert die Lesbarkeit und Wartbarkeit des Codes.Zusätzliche Überlegungen zu bibliotheksähnlichen Skripten
IFS
IFS
Dies ist insbesondere ein Problem für Autoren von Shell-Funktionsbibliotheken, die sicherstellen müssen, dass ihr Code unabhängig vom globalen Status ( , Shell-Optionen, ...), der von ihren Aufrufern festgelegt wurde, stabil funktioniert, und auch, ohne diesen Status überhaupt zu stören (die Aufrufer könnten sich darauf verlassen) drauf bleiben immer statisch).Wenn Sie Bibliothekscode schreiben, können Sie sich nicht darauf verlassen,
IFS
dass Sie einen bestimmten Wert haben (nicht einmal den Standardwert) oder überhaupt festgelegt sind. Stattdessen müssen Sie explizitIFS
für jedes Snippet festlegen, von dem das Verhalten abhängtIFS
.Wenn
IFS
in jeder Codezeile explizit der erforderliche Wert festgelegt ist (auch wenn dies der Standardwert ist), bei dem der Wert mithilfe eines der beiden in dieser Antwort beschriebenen Mechanismen von Bedeutung ist, um den Effekt zu lokalisieren, ist der Code beides unabhängig vom Weltstaat und vermeidet es, ihn gänzlich zu plündern. Dieser Ansatz hat den zusätzlichen Vorteil, dass er für eine Person, die das Skript liest, dasIFS
für genau diesen einen Befehl / diese Erweiterung von Bedeutung ist, sehr eindeutig ist, und zwar bei minimalem Textaufwand (im Vergleich zu selbst dem grundlegendsten Speichern / Wiederherstellen).Welcher Code ist überhaupt betroffen
IFS
?Glücklicherweise gibt es nicht so viele Szenarien, in denen es darauf
IFS
ankommt (vorausgesetzt, Sie geben immer Ihre Erweiterungen an ):"$*"
und"${array[*]}"
Erweiterungenread
integrierten Targeting-Funktion für mehrere Variablen (read VAR1 VAR2 VAR3
) oder eine Array-Variable (read -a ARRAY_VAR_NAME
)read
Targetings einer einzelnen Variablen, wenn führende / nachfolgende Whitespace- oder Nicht-Whitespace-Zeichen in angezeigt werdenIFS
.quelle
:
wann:
der Begrenzer ist?:
ist ein gültiges Zeichen, das in einem Dateinamen auf den meisten UNIX / Linux-Dateisystemen verwendet wird. Es ist also durchaus möglich, ein Verzeichnis mit einem Namen zu haben, der Folgendes enthält:
. Vielleicht haben einige Shells die Möglichkeit,:
in PATH mit etwas wie zu flüchten\:
, und dann werden Spalten angezeigt, die keine tatsächlichen Begrenzer sind (Bash erlaubt anscheinend kein derartiges Flüchten. Die Low-Level-Funktion, die beim Durchlaufen von$PATH
nur Suchen:
in verwendet wird eine C-Zeichenfolge: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
entlang Beispiel:
klarer.Warum riskieren Sie einen Tippfehler bei der Einstellung von IFS,
$' \t\n'
wenn Sie nur noch etwas tun müssen?Alternativ können Sie eine Subshell aufrufen, wenn Sie keine Variablen benötigen, die in den folgenden Bereichen festgelegt / geändert wurden:
quelle
IFS
es anfangs deaktiviert war.