Grundlegendes zu IFS

71

Die folgenden Themen auf dieser Website und StackOverflow waren hilfreich, um die Funktionsweise zu verstehen IFS:

Ich habe aber noch ein paar kurze Fragen. Ich habe mich entschlossen, sie im selben Beitrag zu fragen, da ich denke, dass dies den zukünftigen Lesern helfen kann:

Q1. IFSwird typischerweise im Zusammenhang mit "Feldaufteilung" diskutiert. Ist Feldaufteilung dasselbe wie Wortaufteilung ?

F2: Die POSIX-Spezifikation besagt :

Wenn der Wert von IFS null ist, wird keine Feldaufteilung durchgeführt.

Entspricht das Setzen IFS=dem Setzen IFSauf Null? Ist es das, was damit gemeint ist, es auch empty stringzu setzen?

F3: In der POSIX-Spezifikation habe ich Folgendes gelesen :

Wenn IFS nicht gesetzt ist, soll sich die Shell so verhalten, als ob der Wert von IFS ist <space>, <tab> and <newline>

Angenommen, ich möchte den Standardwert von wiederherstellen IFS. Wie mache ich das? (Genauer gesagt, wie verweise ich auf <tab>und <newline>?)

F4: Wie würde dieser Code abschließend lauten :

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file

benimm dich, wenn wir die erste Zeile auf ändern

while read -r line # Use the default IFS value

oder zu:

while IFS=' ' read -r line
Amelio Vazquez-Reina
quelle

Antworten:

28
  1. Ja, sie sind gleich.
  2. Ja.
  3. In Bash und ähnlichen Muscheln könnte man so etwas tun IFS=$' \t\n'. Andernfalls können Sie die wörtlichen Steuercodes mithilfe von einfügen [space] CTRL+V [tab] CTRL+V [enter]. Wenn Sie dies jedoch planen, ist es besser, eine andere Variable zu verwenden, um den alten IFSWert vorübergehend zu speichern und ihn anschließend wiederherzustellen (oder ihn für einen Befehl vorübergehend mithilfe der var=foo commandSyntax zu überschreiben ).
    • Das erste Codefragment fügt die gesamte gelesene Zeile wörtlich ein $line, da es keine Feldtrennzeichen gibt, für die eine Wortteilung durchgeführt werden kann. Bedenken Sie jedoch, dass viele Shells zum Speichern von Strings Cstrings verwenden und dass die erste Instanz einer NUL dennoch dazu führen kann, dass sie vorzeitig beendet wird.
    • Das zweite Code-Snippet enthält möglicherweise keine exakte Kopie der Eingabe $line. Wenn beispielsweise mehrere aufeinanderfolgende Feldtrennzeichen vorhanden sind, werden sie zu einer einzigen Instanz des ersten Elements. Dies wird häufig als Verlust des umgebenden Leerzeichens erkannt.
    • Das dritte Code-Snippet verhält sich genauso wie das zweite, mit der Ausnahme, dass es nur auf ein Leerzeichen aufgeteilt wird (nicht auf das übliche Leerzeichen, die Tabulatortaste oder die Newline).
Chris Down
quelle
3
Die Antwort auf Q2 ist falsch: Ein leeres IFSund ein nicht gesetztes IFSsind sehr unterschiedlich. Die Antwort auf Q4 ist zum Teil falsch: Hier werden innere Trennzeichen nicht berührt, sondern nur führende und nachfolgende.
Gilles
3
@ Gilles: In Q2 bezieht sich keiner der drei angegebenen Nennwerte auf einen nicht eingestellten IFS, alle bedeuten IFS=.
Stéphane Gimenez
@ Gilles Im zweiten Quartal habe ich nie gesagt, dass sie gleich sind. Und innere Separatoren berühren, wie hier gezeigt: IFS=' ' ; foo=( bar baz qux ) ; echo "${#foo[@]}". (Äh, was? Es sollten mehrere Leerzeichen enthalten sein, damit die Engine sie weiterhin entfernt).
Chris Down
2
@ StéphaneGimenez, Chris: Oh, richtig, Entschuldigung wegen Q2, ich habe die Frage falsch verstanden. Für das vierte Quartal sprechen wir über read; Die letzte Variable nimmt alles auf, was übrig bleibt, mit Ausnahme des letzten Trennzeichens, und lässt innere Trennzeichen im Inneren.
Gilles
1
Gilles ist teilweise richtig, was die Leerzeichen betrifft, die nicht durch Lesen entfernt werden. Lesen Sie meine Antwort für Details.
22

Frage 1: Ja. "Field Splitting" und "Word Splitting" sind zwei Begriffe für dasselbe Konzept.

Frage 2: Ja. Wenn IFSnicht gesetzt (dh nach unset IFS), ist dies gleichbedeutend IFSmit $' \t\n'(ein Leerzeichen, ein Tabulator und eine neue Zeile). Wenn IFSauf einen leeren Wert gesetzt ist (das ist, was "null" hier bedeutet) (dh nach IFS=oder IFS=''oder IFS=""), wird überhaupt keine Feldaufteilung durchgeführt (und $*bei der normalerweise das erste Zeichen von verwendet wird $IFS, wird ein Leerzeichen verwendet).

F3: Wenn Sie das Standardverhalten haben möchten IFS, können Sie verwenden unset IFS. Wenn Sie IFSdiesen Standardwert explizit festlegen möchten , können Sie das Literalzeichen Leerzeichen, Tabulatorzeichen und Zeilenumbruch in einfache Anführungszeichen setzen. In ksh93, bash oder zsh können Sie verwenden IFS=$' \t\n'. Portabel, wenn Sie vermeiden möchten, dass Ihre Quelldatei ein literales Tabulatorzeichen enthält, können Sie verwenden

IFS=" $(echo t | tr t \\t)
"

Q4: Mit IFSSatz auf einen leeren Wert, read -r linesetzt lineauf die ganze Linie mit Ausnahme seiner abschließenden Newline. Mit IFS=" "werden Leerzeichen am Anfang und am Ende der Zeile abgeschnitten. Mit dem Standardwert von IFSwerden Tabulatoren und Leerzeichen abgeschnitten.

Gilles
quelle
2
Q2 ist teilweise falsch. Wenn IFS leer ist, wird "$ *" ohne Trennzeichen verbunden. (Zum Beispiel $@gibt es einige Variationen zwischen Shells in nicht aufgelisteten Kontexten wie IFS=; var=$@). Es sollte beachtet werden, dass wenn IFS leer ist, keine Wortteilung durchgeführt wird, aber $ var immer noch zu keinem Argument erweitert wird, anstatt zu einem leeren Argument, wenn $ var leer ist, und Globbing weiterhin angewendet wird, so dass Sie weiterhin Variablen zitieren müssen (auch wenn Sie Globbing deaktivieren)
Stéphane Chazelas
13

Q1. Feldaufteilung.

Ist Feldaufteilung dasselbe wie Wortaufteilung?

Ja, beide weisen auf die gleiche Idee hin.

F2: Wann ist IFS null ?

Setzt IFS=''das gleiche wie null, auch das gleiche wie eine leere Zeichenfolge?

Ja, alle drei bedeuten dasselbe: Es soll keine Feld- / Wortteilung durchgeführt werden. Dies wirkt sich auch auf das Drucken von Feldern aus (wie bei echo "$*"). Alle Felder werden ohne Leerzeichen zusammen verkettet.

F3: (Teil a) Deaktivieren Sie IFS.

In der POSIX-Spezifikation habe ich Folgendes gelesen :

Wenn IFS nicht festgelegt ist, verhält sich die Shell so, als ob der Wert von IFS <Leerzeichen> <Tabulator> <Zeilenumbruch> ist .

Welches ist genau gleichbedeutend mit:

Mit einem unset IFSsoll sich die Shell so verhalten, als wäre IFS Standard.

Das bedeutet, dass die 'Feldaufteilung' mit einem Standard-IFS-Wert identisch oder nicht festgelegt ist.
Das bedeutet NICHT, dass IFS unter allen Bedingungen gleich funktioniert. Genauer gesagt, setzt die Ausführung OldIFS=$IFSdie Variable OldIFSauf null und nicht auf die Standardeinstellung. Wenn Sie versuchen, IFS zurückzusetzen, IFS=OldIFSwird IFS auf Null gesetzt, nicht so wie zuvor. Achtung !!.

F3: (Teil b) Stellen Sie IFS wieder her.

Wie kann ich den Standardwert von IFS wiederherstellen? Angenommen, ich möchte den Standardwert von IFS wiederherstellen. Wie mache ich das? ( Genauer gesagt, wie verweise ich auf <tab> und <newline> ?)

Für zsh, ksh und bash (AFAIK) kann IFS auf den Standardwert gesetzt werden:

IFS=$' \t\n'        # works with zsh, ksh, bash.

Fertig, Sie brauchen nichts weiter zu lesen.

Wenn Sie jedoch IFS für sh zurücksetzen müssen, kann dies komplex werden.

Werfen wir einen Blick von der einfachsten bis zur vollständigen Ausführung ohne Nachteile (außer Komplexität).

1.- IFS deaktivieren.

Wir könnten nur unset IFS(Lesen Sie Q3 Teil a, oben.).

2.- Zeichen tauschen.

Um dieses Problem zu umgehen, können Sie den Wert von "Tab" und "Newline" austauschen, um das Festlegen des IFS-Werts zu vereinfachen. Dies funktioniert dann auf die gleiche Weise.

Setzen Sie IFS auf <Leerzeichen> <Zeilenumbruch> <Tabulator> :

sh -c 'IFS=$(echo " \n\t"); printf "%s" "$IFS"|xxd'      # Works.

3.- Ein einfaches? Lösung:

Wenn untergeordnete Skripte vorhanden sind, für die IFS korrekt festgelegt werden muss, können Sie immer manuell schreiben:

IFS = '   
'

Wo die manuell eingegebene Sequenz lautete:, IFS='spacetabnewline'Sequenz, die oben tatsächlich korrekt eingegeben wurde (Wenn Sie eine Bestätigung benötigen, bearbeiten Sie diese Antwort). Das Kopieren / Einfügen aus Ihrem Browser wird jedoch unterbrochen, da der Browser das Leerzeichen ausblendet. Es ist schwierig, den Code wie oben beschrieben freizugeben.

4.- Komplettlösung.

Das Schreiben von Code, der sicher kopiert werden kann, beinhaltet normalerweise eindeutige druckbare Escapezeichen.

Wir brauchen Code, der den erwarteten Wert "produziert". Aber selbst wenn dieser Code konzeptionell korrekt ist, wird KEIN Trailing gesetzt \n:

sh -c 'IFS=$(echo " \t\n"); printf "%s" "$IFS"|xxd'      # wrong.

Dies liegt daran, dass unter den meisten Shells alle nachfolgenden Zeilenumbrüche $(...)oder `...`Befehlsersetzungen bei der Erweiterung entfernt werden.

Wir müssen einen Trick für sh verwenden:

sh -c 'IFS="$(printf " \t\nx")"; IFS="${IFS%x}"; printf "$IFS"|xxd'  # Correct.

Eine alternative Möglichkeit kann sein, IFS als Umgebungswert aus bash festzulegen (zum Beispiel) und dann sh (die Versionen davon, die IFS akzeptieren, um über die Umgebung festgelegt zu werden) wie folgt aufzurufen:

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'

Kurz gesagt, es ist ein merkwürdiges Abenteuer, IFS auf Standard zurückzusetzen.

F4: Im aktuellen Code:

Schließlich, wie würde dieser Code:

while IFS= read -r line
do
    echo $line
done < /path_to_text_file

benimm dich, wenn wir die erste Zeile auf ändern

while read -r line # Use the default IFS value

oder zu:

while IFS=' ' read -r line

Erstens: Ich weiß nicht, ob der echo $line(mit dem var NICHT zitierte) Schweinswal da ist oder nicht. Es führt eine zweite Ebene der 'Feldaufteilung' ein, die es beim Lesen nicht gibt. Also werde ich beides beantworten. :)

Mit diesem Code (so könnte man das bestätigen). Du brauchst das nützliche xxd :

#!/bin/ksh
# Correctly set IFS as described above.
defIFS="$(printf " \t\nx")"; defIFS="${defIFS%x}";
IFS="$defIFS"
printf "IFS value: "
printf "%s" "$IFS"| xxd -p

a='   bar   baz   quz   '; l="${#a}"
printf "var value          : %${l}s-" "$a" ; printf "%s\n" "$a" | xxd -p

printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x--          : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf 'Values      quoted :\n' ""  # With values quoted:
printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null    quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS default quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf '%s\n' "Values unquoted :"   # Now with values unquoted:
printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x-- unquoted : "
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null  unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS defau unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

Ich bekomme:

$ ./stackexchange-Understanding-IFS.sh
IFS value: 20090a
var value          :    bar   baz   quz   -20202062617220202062617a20202071757a2020200a
IFS --x--          :    bar   baz   quz   -20202062617220202062617a20202071757a202020
Values      quoted :
IFS null    quoted :    bar   baz   quz   -20202062617220202062617a20202071757a202020
IFS default quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS unset   quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS space   quoted :       bar   baz   quz-62617220202062617a20202071757a
Values unquoted :
IFS --x-- unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS null  unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS defau unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS unset unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS space unquoted : bar, baz, quz, 6261722c62617a2c71757a2c

Der erste Wert ist genau der richtige Wert von IFS='spacetabnewline'

Die nächste Zeile enthält alle $aHexadezimalwerte, die die Variable hat, und am Ende eine neue Zeile '0a', die jedem Lesebefehl zugewiesen wird.

Die nächste Zeile, für die IFS null ist, führt keine 'Feldaufteilung' durch, aber die neue Zeile wird entfernt (wie erwartet).

Da IFS ein Leerzeichen enthält, entfernen Sie in den nächsten drei Zeilen die ursprünglichen Leerzeichen und setzen Sie die var-Zeile auf den verbleibenden Kontostand.

Die letzten vier Zeilen zeigen, was eine Variable ohne Anführungszeichen bewirkt. Die Werte werden auf die (mehreren) Felder aufgeteilt und gedruckt als:bar,baz,qux,


quelle
4

unset IFS löscht IFS, auch wenn danach angenommen wird, dass IFS "\ t \ n" ist:

$ echo "'$IFS'"
'   
'
$ IFS=""
$ echo "'$IFS'"
''
$ unset IFS
$ echo "'$IFS'"
''
$ IFS=$' \t\n'
$ echo "'$IFS'"
'   
'
$

Getestet auf den Bash-Versionen 4.2.45 und 3.2.25 mit dem gleichen Verhalten.

derekm
quelle
Die Frage und die verknüpfte Dokumentation nicht darüber reden unsetvon IFS, wie hier in den Kommentaren der akzeptierten Antwort erklärt.
ILMostro_7