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
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).$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čí.$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.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.
Rozděluje se pole stejně jako rozdělování slov?
Ano, oba ukazují na stejný nápad.
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.
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 !!.
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).
Mohli bychom jen unset IFS
(Přečtěte si část Q3 výše, výše.).
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.
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.
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í.
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,
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.