Spójrzmy na przykład z starannie przygotowanym tekstem wejściowym:
text=' hello world\
foo\bar'
To dwie linie, pierwsza zaczynająca się spacją i kończąca się odwrotnym ukośnikiem. Po pierwsze, spójrzmy na to, co się dzieje bez żadnych środków ostrożności read
(ale używając printf '%s\n' "$text"
do ostrożnego drukowania $text
bez ryzyka ekspansji). (Poniżej $
znajduje się monit powłoki).
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
read
zjadł ukośniki odwrotne: ukośnik-nowa linia powoduje zignorowanie nowej linii, a ukośnik-cokolwiek ignoruje ten pierwszy ukośnik. Aby uniknąć specjalnego traktowania ukośników odwrotnych, używamy read -r
.
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
Tak lepiej, mamy dwie linie zgodnie z oczekiwaniami. Dwie linie prawie zawierają pożądaną treść: podwójna spacja między hello
i world
została zachowana, ponieważ znajduje się w line
zmiennej. Z drugiej strony początkowa przestrzeń została zjedzona. Dzieje się tak, ponieważ read
odczytuje tyle słów, ile przekazujesz, zmienne, z tą różnicą, że ostatnia zmienna zawiera resztę wiersza - ale wciąż zaczyna się od pierwszego słowa, tzn. Początkowe spacje są odrzucane.
Tak więc, aby odczytać każdą linię dosłownie, musimy upewnić się, że nie dochodzi do podziału słów . Robimy to, ustawiając IFS
zmienną na pustą wartość.
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
Zwróć uwagę, jak ustawiliśmy IFS
specjalnie na czas trwania read
wbudowanego . W IFS= read -r line
ustawia zmienne środowiska IFS
(na pusty wartość) specjalnie dla realizacji read
. Jest to przykład ogólnej składni komend prostych : (być może pusta) sekwencja przypisań zmiennych, po której następuje nazwa komendy i jej argumenty (można także przekierowywać w dowolnym momencie). Ponieważ read
jest to funkcja wbudowana, zmienna nigdy nie kończy się w środowisku zewnętrznego procesu; niemniej jednak wartość $IFS
jest przypisywana tak długo, jak długo read
jest wykonywana¹. Pamiętaj, że read
nie jest to specjalne wbudowane , więc zadanie trwa tylko przez czas jego trwania.
Dlatego staramy się nie zmieniać wartości IFS
innych instrukcji, które mogą na nim polegać. Ten kod będzie działał bez względu na to, co IFS
początkowo ustawił otaczający kod i nie spowoduje żadnych problemów, jeśli kod w pętli będzie polegał IFS
.
Porównaj z tym fragmentem kodu, który wyszukuje pliki w ścieżce oddzielonej dwukropkami. Lista nazw plików jest odczytywana z pliku, jedna nazwa pliku w wierszu.
IFS=":"; set -f
while IFS= read -r name; do
for dir in $PATH; do
## At this point, "$IFS" is still ":"
if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
done
done <filenames.txt
Jeśli pętla była while IFS=; read -r name; do …
, to for dir in $PATH
nie podzieliłaby się $PATH
na składniki oddzielone dwukropkami. Gdyby kod był IFS=; while read …
, byłoby jeszcze bardziej oczywiste, że IFS
nie jest ustawione :
w treści pętli.
Oczywiście możliwe byłoby przywrócenie wartości IFS
po wykonaniu read
. Wymagałoby to jednak znajomości poprzedniej wartości, która stanowi dodatkowy wysiłek. IFS= read
to prosty sposób (i dogodnie także najkrótszy sposób).
¹ A jeśli read
zostanie przerwany przez sygnał pułapki, być może podczas działania pułapki - nie jest to określone przez POSIX i zależy od powłoki w praktyce.
while IFS=X read
nie dzieli się naX
, alewhile IFS=X; read
...