Pochopitelně chápu, že lze přidat hodnotu do proměnné interního oddělovače polí. Například:
$ IFS=blah
$ echo "$IFS"
blah
$
Rozumím také tomu, že read -r line
uloží data z stdin
do proměnné s názvem line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Jak však může příkaz přiřadit hodnotu proměnné? A ukládá nejprve data z stdin
do proměnné line
a poté zadejte hodnotu line
na IFS
?
Přijatá odpověď:
V prostředí POSIX read
, bez jakékoli možnosti nečte řádek , čte slova z řádku (pravděpodobně se zpětným lomítkem), kde slova jsou $IFS
oddělovač a zpětné lomítko lze použít k opuštění oddělovačů (nebo pokračování v řádcích).
Obecná syntaxe je:
read word1 word2... remaining_words
read
čte stdin jeden bajt po druhém¹, dokud nenajde neuvedený znak nového řádku (nebo konec vstupu), rozdělí jej podle složitých pravidel a uloží výsledek tohoto rozdělení do $word1
, $word2
… $remaining_words
.
Například na vstupu jako:
<tab> foo bar baz blah blah
whatever whatever
a s výchozí hodnotou $IFS
, read a b c
by přiřadilo:
$a
⇐foo
$b
⇐bar baz
$c
⇐blah blahwhatever whatever
Pokud je nyní předán pouze jeden argument, nestane se z něj read line
. Stále read remaining_words
. Zpracování zpětného lomítka se stále provádí, prázdné znaky² IFS jsou stále odstraněny ze začátku a konce.
-r
volba odstraní zpracování zpětného lomítka. Takže stejný příkaz výše s -r
místo toho přiřadí
$a
⇐foo
$b
⇐bar
$c
⇐baz blah blah
Nyní, pro část rozdělení, je důležité si uvědomit, že pro $IFS
existují dvě třídy znaků :prázdné znaky IFS² (včetně mezery a tabulátoru (a nového řádku, i když zde na tom nezáleží, pokud nepoužijete -d), které jsou shodou okolností také ve výchozí hodnotě $IFS
) a ostatní. Zacházení s těmito dvěma třídami postav je odlišné.
S IFS=:
(:
nejedná se o prázdný znak IFS), vstup jako :foo::bar::
by bylo rozděleno na ""
, "foo"
, ""
, bar
a ""
(a další ""
u některých implementací na tom nezáleží, kromě read -a
). Zatímco když nahradíme to :
s mezerou se rozdělení provádí pouze na foo
a bar
. To znamená, že první a koncové jsou ignorovány a jejich sekvence jsou považovány za jeden. Pro kombinování bílých a jiných znaků v $IFS
platí další pravidla . Některé implementace mohou přidat/odebrat speciální úpravu zdvojením znaků v IFS (IFS=::
nebo IFS=' '
).
Zde tedy platí, že pokud nechceme, aby byly odstraněny úvodní a koncové znaky prázdného znaku, musíme tyto prázdné znaky IFS odstranit z IFS.
I se znaky IFS bez mezer, pokud vstupní řádek obsahuje jeden (a pouze jeden) z těchto znaků a je to poslední znak v řádku (např. IFS=: read -r word
na vstupu jako foo:
) s POSIX shelly (nikoli zsh
ani nějaký pdksh
verze), je tento vstup považován za jeden foo
slovo, protože v těchto shellech jsou znaky $IFS
jsou považováni za terminátory , tedy word
bude obsahovat foo
, nikoli foo:
.
Takže kanonický způsob, jak číst jeden řádek vstupu pomocí read
vestavěný je:
IFS= read -r line
(všimněte si, že pro většinu read
implementace, které fungují pouze pro textové řádky, protože znak NUL není podporován kromě zsh
).
Pomocí var=value cmd
syntaxe zajišťuje IFS
je nastaven jinak pouze po dobu trvání daného cmd
příkaz.
Poznámka k historii
read
builtin byl představen Bourneovým shellem a měl již číst slova , ne čáry. Existuje několik důležitých rozdílů s moderními shelly POSIX.
Bourne shell se read
nepodporoval -r
možnost (která byla zavedena shellem Korn), takže neexistuje žádný způsob, jak zakázat zpracování zpětného lomítka, kromě předběžného zpracování vstupu pomocí něčeho jako sed 's/\/&&/g'
tam.
Bourne shell neměl tuto představu o dvou třídách postav (které opět zavedl ksh). V Bourne shellu procházejí všechny znaky stejným způsobem jako IFS mezery v ksh, tedy IFS=: read a b c
na vstupu jako foo::bar
by přiřadil bar
na $b
, nikoli prázdný řetězec.
V Bourne shellu s:
var=value cmd
Pokud cmd
je vestavěný (jako read
is), var
zůstane nastaven na value
za cmd
skončil. To je zvláště důležité u $IFS
protože v Bourne shellu $IFS
se používá k rozdělení všeho, nejen rozšíření. Také pokud odstraníte znak mezery z $IFS
v Bourne shellu "[email protected]"
již nefunguje.
V Bourne shellu způsobí přesměrování složeného příkazu jeho spuštění v podshellu (v nejstarších verzích dokonce věci jako read var < file
nebo exec 3< file; read var <&3
nefungovalo), takže v Bourne shellu bylo vzácné použít read
pro cokoli kromě uživatelského vstupu na terminálu (kde má tato manipulace s pokračováním řádku smysl)
Některé Unices (jako HP/UX, jeden je také v util-linux
) stále mají line
příkaz pro čtení jednoho řádku vstupu (to býval standardní příkaz UNIX až do Single UNIX Specification verze 2).
To je v podstatě stejné jako head -n 1
kromě toho, že čte jeden bajt po druhém, aby se ujistil, že nečte více než jeden řádek. V těchto systémech můžete:
line=`line`
To samozřejmě znamená vytvořit nový proces, provést příkaz a přečíst jeho výstup pomocí kanálu, takže je mnohem méně efektivní než IFS= read -r line
od ksh , ale stále mnohem intuitivnější.