In `while IFS = read..`, warum hat IFS keine Wirkung?

12

Ich habe vielleicht etwas absolut Falsches, aber es scheint mir überzeugend, dass das Setzen von IFS als eines der Befehle in der Pre-Do / Do-Liste absolut keine Wirkung hat.
Das äußere IFS (außerhalb des whileKonstrukts) ist in allen im folgenden Skript gezeigten Beispielen maßgeblich.

Was ist denn hier los? Habe ich eine falsche Vorstellung davon, was IFS in dieser Situation tut? Ich habe erwartet, dass die Ergebnisse der Array-Aufteilung den Angaben in der Spalte "erwartet" entsprechen.


#!/bin/bash
xifs() { echo -n "$(echo -n "$IFS" | xxd -p)"; } # allow for null $IFS 
show() { x=($1) 
         echo -ne "  (${#x[@]})\t |"
         for ((j=0;j<${#x[@]};j++)); do 
           echo -n "${x[j]}|"
         done
         echo -ne "\t"
         xifs "$IFS"; echo
}
data="a  b   c"
echo -e "-----   --  -- \t --------\tactual"
echo -e "outside        \t  IFS    \tinside" 
echo -e "loop           \t Field   \tloop" 
echo -e "IFS     NR  NF \t Split   \tIFS (actual)" 
echo -e "-----   --  -- \t --------\t-----"
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 1'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 2'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 3'; show "$REPLY"; done
IFS=" ";      xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 4'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 5'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 6'; show "$REPLY"; done
IFS=;         xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 7'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t 8'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 9'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t10'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t11'; show "$REPLY"; done
echo -e "-----   --  -- \t --------\t-----"

Ausgabe:

-----   --  --   --------       actual   
outside           IFS           inside                assigned   
loop             Field          loop    #              inner
IFS     NR  NF   Split          IFS     #  expected    IFS
-----   --  --   --------       -----   #  ---------  --------
20090a   1  (3)  |a|b|c|        20090a  #                              
20090a   2  (3)  |a|b|c|        20090a  #  |a  b   c|  IFS=
20090a   3  (3)  |a|b|c|        20090a  #  |a  |   c|  IFS=b
20       4  (3)  |a|b|c|        20      #                          
20       5  (3)  |a|b|c|        20      #  |a  b   c   IFS=
20       6  (3)  |a|b|c|        20      #  |a  |   c|  IFS=b
         7  (1)  |a  b   c|             #                          
         8  (1)  |a  b   c|             #  |a|b|c|     IFS=" "
         9  (1)  |a  b   c|             #  |a  |   c|  IFS=b
62      10  (2)  |a  |   c|     62      #  |a  b   c|  IFS=
62      11  (2)  |a  |   c|     62      #  |a|b|c|     IFS=" "
-----   --  --   --------       -----      ---------   -------                        
Peter.O
quelle

Antworten:

17

(Sorry, lange Erklärung)

Ja, die IFSVariable inwhile IFS=" " read; do … hat keine Auswirkungen auf den Rest des Codes.

Lassen Sie uns zunächst genau festlegen, dass die Shell-Befehlszeile zwei verschiedene Arten von Variablen enthält:

  • Shell-Variablen (die nur innerhalb einer Shell existieren und für die Shell lokal sind)
  • Umgebungsvariablen, die für jeden Prozess existieren. Diese werden normalerweise auf fork()und gespeichert exec(), sodass untergeordnete Prozesse sie erben.

Wenn Sie einen Befehl aufrufen mit:

  A=foo B=bar command

Der Befehl wird in einer Umgebung ausgeführt, in der die (Umgebungs-) Variable Aauf foound auf gesetzt Bist bar. Bei dieser Befehlszeile bleiben die aktuellen Shell-Variablen Aund unverändert .B

Dies unterscheidet sich von:

A=foo; B=bar; command

Hier werden Shellvariablen Aund Bdefiniert und der Befehl wird ohne Umgebungsvariablen ausgeführt Aund Bdefiniert. Werte von Aund Bsind von nicht zugänglich command.

Wenn jedoch einige Shell-Variablen export-ed sind, werden die entsprechenden Umgebungsvariablen mit ihren jeweiligen Shell-Variablen synchronisiert. Beispiel:

export A
export B
A=foo; B=bar; command

Mit diesem Code werden sowohl Shell- Variablen als auch die Shell- Umgebungsvariablen auf foound gesetztbar . Da Umgebungsvariablen von Unterprozessen geerbt commandwerden, können Sie auf deren Werte zugreifen.

Um zu Ihrer ursprünglichen Frage zurückzukehren, gehen Sie folgendermaßen vor:

IFS='a' read

nur readist betroffen. In diesem Fall ist readder Wert der IFSVariablen unerheblich. Es wird IFSnur verwendet, wenn Sie die Zeile auffordern, geteilt zu werden (und in mehreren Variablen gespeichert zu werden), wie in:

echo "a :  b :    c" | IFS=":" read i j k; \
    printf "i is '%s', j is '%s', k is '%s'" "$i" "$j" "$k"

IFSwird nur verwendet, readwenn es mit Argumenten aufgerufen wird. ( Bearbeiten: Dies ist nicht ganz richtig: Leerzeichen, dh Leerzeichen und Tabulatoren, IFSwerden am Anfang / Ende der Eingabezeile immer ignoriert.)

Stéphane Gimenez
quelle
Was für eine großartige Erklärung! Es ist so einfach! Ich habe von dieser verwirrten ‚kein Semikolon‘ Syntax für Monate; und es ist einfach ein Fall, in dem es sich um eine lokale Variable handelt! .. rozcietrzewiacz hat mir in der anderen Frage den Weg geebnet ... und Sie haben gerade das Sahnehäubchen auf den Kuchen gelegt ... das war ich Die ganze Nacht wach, und es hat sich auf jeden Fall gelohnt für so gute und klare Antworten! .. Danke ..
Peter.O
Ähm. Ich musste diesen Bearbeitungskommentar mehrmals lesen, bevor ich ihn erhalten habe - Sie wollen damit sagen, dass Leerzeichen, die in $IFSder Eingabezeile enthalten sind, am Anfang / Ende der Eingabezeile entfernt werden, nehme ich an? (So ​​funktioniert es.)
Zrajm
Bitte schauen Sie sich dies an: unix.stackexchange.com/questions/382963/…
Der Wert von IFS ist wichtig, auch wenn eine einzelne Variable gelesen wird, da die Shell weiterhin die Wortteilung für die Eingabe vornimmt. So zum Beispiel der Zeichen eingeben a<tab>bzu read varwerden in var führt mit dem Wert a<space>b, aber wenn stattdessen haben Sie IFS='<newline>' read vardann wird der Wert von var seina<tab>b .
John Hascall
8

Vereinfacht ausgedrückt müssen Sie jeweils mehrere Variablen lesen, damit das IFS=<something> read ...Konstrukt in Ihren Beispielen 1 einen sichtbaren Effekt hat .

Sie vermissen den Umfang der readin den Beispielen. In Ihren Testfällen gibt es keine Änderung des IFS innerhalb der Schleife. Lassen Sie mich genau sagen, woher das zweite IFS in jeder Ihrer Zeilen kommt:

 IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo ...
                                                      ^      ^
                                                      |      |
                                          from here --'       `- to here :)

Es ist wie bei jedem Programm, das in der Shell ausgeführt wird. Die Variable, die Sie in der Befehlszeile (neu) definieren, wirkt sich auf die Programmausführung aus. Und nur das (da du nicht exportierst). Um die IFSin einer solchen Zeile neu definierte Funktion verwenden zu können, müssen Sie readdaher mehr als einer Variablen Werte zuweisen . Schauen Sie sich diese Beispiele an:

 $ data="a  b   c"
 $ echo "$data" | while           read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a|b||c|
 $ echo "$data" | while IFS=      read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a b c||||
 $ echo "$data" | while IFS='a'   read A B C; do echo \|$A\|$B\|\|$C\|; done
 || b c|||
 $ echo "$data" | while IFS='ab'  read A B C; do echo \|$A\|$B\|\|$C\|; done
 || || c|

1 Wie ich gerade von Gilles erfahren habe, hat das Setzen IFS=''(Leerzeichen) beim Lesen nur eines Feldes möglicherweise den Vorteil, dass Leerzeichen am Anfang der Zeile nicht abgeschnitten werden.

rozcietrzewiacz
quelle
Gut .. Danke ... diesmal habe ich es geschafft .. und ich liebe deine Skizze :)
Peter.O
OK, jetzt habe ich Ihren Kommentar gelesen, der zeigt, dass Sie meine Antwort auf dieses Problem in der anderen Frage nicht bemerkt haben. Vielleicht könntest du einfach den anderen zurücksetzen und diesen löschen, da es sich wirklich um ein allgemeines Problem handelt?
Rozcietrzewiacz
Ja, die beiden Fragen haben ein verwandtes Thema, aber der Titel der anderen lautet "Warum wird IFS= readbevorzugt verwendet, um nur die IFS-Umgebungsvariable zurückzusetzen". Ich wusste also nicht, dass der Aufrufer eines Befehls lokale Variablen setzen kann. Das war die Antwort auf diese Frage. Es hat sich weiterentwickelt, um den Hauptpunkt dieser Frage anzusprechen, aber als mir klar wurde, dass ich diese Frage bereits gestellt hatte ... Vielleicht sind die beiden Fragen so ähnlich wie zwei sedFragen, und ich habe das Gefühl, dass es so bleibt ... Weitere Titel für Googler zu googeln.
Peter.O