it-swarm-eu.dev

Porozumění IFS

Následující nití na tomto webu a StackOverflow byly užitečné pro pochopení toho, jak IFS funguje:

Ale stále mám pár krátkých otázek. Rozhodl jsem se je zeptat na stejném příspěvku, protože si myslím, že by to mohlo zlepšit budoucí čtenáře:

Q1. IFS je obvykle diskutováno v kontextu "rozdělení pole". Je rozdělení pole stejné jako rozdělení textu ?

Q2: Specifikace POSIX říká :

Pokud je hodnota IFS nulová, nesmí se provádět žádné dělení pole.

Nastavení IFS= stejné jako nastavení IFS na null? Je to to, co se míní nastavením na empty string také?

Q3: Ve specifikaci POSIX I read následující:

Pokud není nastaven IFS, Shell se bude chovat, jako by hodnota IFS byla <space>, <tab> and <newline>

Řekněme, že chci obnovit výchozí hodnotu IFS. Jak to udělám? (konkrétněji jak odkazuji na <tab> a <newline>?)

Q4: Nakonec, jak by tento kód:

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

chovat se, pokud změníme první řádek na

while read -r line # Use the default IFS value

nebo do:

while IFS=' ' read -r line
76
  1. Ano, jsou stejné.
  2. Ano.
  3. V bash a podobných skořápkách byste mohli udělat něco jako IFS=$' \t\n'. Jinak byste mohli vložit doslovné řídicí kódy pomocí [space] CTRL+V [tab] CTRL+V [enter]. Pokud to však chcete udělat, je lepší použít jinou proměnnou k dočasnému uložení staré hodnoty IFS a poté ji obnovit (nebo dočasně přepsat pro jeden příkaz pomocí var=foo command syntaxe).
    • První fragment kódu doslovně přepíše celý řádek do $line, Protože neexistují žádné oddělovače polí, které by prováděly rozdělení Wordu. Mějte však na paměti, že protože mnoho skořápek používá struny k ukládání řetězců, první instance NUL může stále způsobit, že se vzhled předčasně ukončí.
    • Druhý zlomek kódu nemusí vložit přesnou kopii vstupu do $line. Například, pokud existuje více po sobě jdoucích oddělovačů polí, vytvoří se z jedné instance prvního prvku. Toto je často považováno za ztrátu okolního mezery.
    • Třetí fragment kódu bude stejný jako druhý, kromě toho, že se rozdělí pouze na mezeru (nikoli na obvyklou mezeru, kartu nebo nový řádek).
30
Chris Down

Q1: Ano. „Rozdělení pole“ a „Rozdělení slova“ jsou dva termíny pro stejný koncept.

Q2: Ano. Pokud IFS není nastaveno (tj. Za unset IFS), je ekvivalentní IFS nastaveno na $' \t\n' (mezera, karta a nový řádek). Pokud je IFS nastavena na prázdnou hodnotu (to znamená „null“ zde) (tj. Za IFS= nebo IFS='' nebo IFS=""), nedělí se vůbec žádné rozdělení pole (a $*, které obvykle používá první znak $IFS, používá mezeru).

Q3: Pokud chcete mít výchozí chování IFS, můžete použít unset IFS. Pokud chcete explicitně nastavit IFS na tuto výchozí hodnotu, můžete do jednotlivých uvozovek vložit mezery, tabulátory a řádky doslovných znaků. V ksh93, bash nebo zsh můžete použít IFS=$' \t\n'. Přenosně, pokud se chcete ve zdrojovém souboru vyhnout znaku doslovné karty, můžete použít

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

Q4: S IFS nastaveno na prázdnou hodnotu read -r line nastaví line na celý řádek s výjimkou jeho ukončujícího nového řádku. S IFS=" ", mezery na začátku a na konci řádku jsou oříznuty. Při výchozí hodnotě IFS jsou oříznuty karty a mezery.

Q1. Rozdělení pole.

Rozděluje se pole stejně jako rozdělování slov?

Ano, oba ukazují na stejný nápad.

Q2: Kdy je IFS null ?.

Je nastavení IFS='' Stejné jako null, stejně jako prázdný řetězec?

Ano, všechna tři znamenají totéž: Rozdělení polí/slov se nesmí provádět. To také ovlivní tisková pole (jako u echo "$*") Všechna pole budou zřetězena společně bez mezery.

Q3: (část a) Unset IFS.

Ve specifikaci POSIX I přečtěte si následující :

Pokud není nastaven IFS, Shell se bude chovat, jako by hodnota IFS byla <space> <tab> <newline> .

Což přesně odpovídá:

S unset IFS Se Shell bude chovat, jako by byl IFS výchozí.

To znamená, že „rozdělení pole“ bude přesně stejné s výchozí hodnotou IFS nebo bude deaktivováno.
To neznamená, že IFS bude fungovat stejným způsobem ve všech podmínkách. Přesněji řečeno, provedením OldIFS=$IFS Nastaví var OldIFS na null , nikoli výchozí. A když se pokusíme nastavit IFS zpět, takto IFS=OldIFS Nastaví IFS na null, nezachová to tak, jak bylo předtím. Dávej si pozor !!.

Q3: (část b) Obnovte IFS.

Jak mohu obnovit hodnotu IFS na výchozí hodnotu. Řekněme, že chci obnovit výchozí hodnotu IFS. Jak to udělám? (konkrétněji jak odkazuji na <tab> a <newline> ?)

Pro zsh, ksh a bash (AFAIK) lze IFS nastavit na výchozí hodnotu jako:

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

Hotovo, nemusíte číst nic jiného.

Ale pokud potřebujete znovu nastavit IFS pro sh, může to být složité.

Pojďme se podívat od nejjednoduššího k dokončení bez nevýhod (kromě složitosti).

1.- Zrušte nastavení IFS.

Mohli bychom jen unset IFS (Přečtěte si část Q3 výše, výše.).

2. - Výměna znaků.

Jako řešení, zaměňování hodnoty tab a newline usnadňuje nastavení hodnoty IFS a poté funguje stejným způsobem.

Nastavte IFS na <space> <newline> <tab> :

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

3.- Jednoduché? řešení:

Pokud existují podřízené skripty, které potřebují správně nastavit IFS, můžete vždy psát ručně:

 IFS = '
' 

Kde byla ručně zadaná sekvence: IFS='spacetabnewline', sekvence, která byla skutečně správně zadána výše (Pokud potřebujete potvrdit, upravte tuto odpověď). Kopie/vložení z vašeho prohlížeče se však zlomí, protože prohlížeč stlačí/skryje mezeru. Je obtížné sdílet výše uvedený kód.

4.- Kompletní řešení.

Psaní kódu, který lze bezpečně zkopírovat, obvykle zahrnuje jednoznačný tisknutelný únik.

Potřebujeme nějaký kód, který „vytvoří“ očekávanou hodnotu. Ale i když je koncepčně správný, tento kód NENÍ nastaven koncové \n:

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

To se děje proto, že ve většině prostředí jsou při expanzi odstraněny všechny koncové řádky nahrazení příkazů $(...) nebo `...`.

Potřebujeme použít trik pro sh:

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

Alternativním způsobem může být nastavení IFS jako hodnoty prostředí z bash (například) a pak volání sh (jeho verze, které akceptují IFS, které mají být nastaveny prostřednictvím prostředí), takto:

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

Stručně řečeno, sh je resetování IFS na výchozí docela zvláštní dobrodružství.

Q4: Ve skutečném kódu:

A konečně, jak by tento kód:

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

chovat se, pokud změníme první řádek na

while read -r line # Use the default IFS value

nebo do:

while IFS=' ' read -r line

Za prvé: Nevím, jestli je na porpouse echo $line (S var NENÍ citován), nebo ne. Zavádí druhou úroveň „rozdělení pole“, kterou čtení nemá. Takže odpovím na oba. :)

S tímto kódem (abyste mohli potvrdit). Budete potřebovat žitečné 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

Dostanu:

$ ./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

První hodnota je pouze správná hodnota IFS='spacetabnewline'

Další řádek obsahuje všechny hexadecimální hodnoty, které má var $a, A nový řádek '0a' na konci, protože bude přidělen každému příkazu pro čtení.

Další řádek, pro který je IFS null, neprovádí žádné „dělení polí“, ale nový řádek je odstraněn (podle očekávání).

Další tři řádky, protože IFS obsahuje mezeru, odstraňte počáteční mezery a nastavte linku var na zbývající zůstatek.

Poslední čtyři řádky ukazují, co bude dělat nekotovaná proměnná. Hodnoty budou rozděleny na (několik) mezer a budou vytištěny jako: bar,baz,qux,

15
user79743

unset IFS vymaže IFS, i když se předpokládá, že IFS je "\ t\n":

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

Testováno na bash verzích 4.2.45 a 3.2.25 se stejným chováním.

4
derekm