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ší.