Давайте посмотрим на пример с тщательно продуманным вводным текстом:
text=' hello world\
foo\bar'
Это две строки, первая из которых начинается с пробела и заканчивается обратной косой чертой. Во-первых, давайте посмотрим на то, что происходит без каких-либо мер предосторожности read(но с помощью printf '%s\n' "$text"осторожной печати $textбез риска расширения). (Ниже приведена $ подсказка оболочки.)
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
readуничтожение обратной косой черты: обратная косая черта-новая строка приводит к игнорированию новой строки, а обратная косая черта - все игнорирует эту первую обратную косую черту. Чтобы избежать обратного слеша, мы используем специально read -r.
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
Это лучше, у нас есть две линии, как и ожидалось. Две строки почти содержат желаемое содержимое: двойной пробел между helloи worldбыл сохранен, потому что он находится внутри lineпеременной. С другой стороны, начальное пространство было съедено. Это потому, что readчитает столько слов, сколько вы передаете переменным, за исключением того, что последняя переменная содержит остаток строки - но она все равно начинается с первого слова, то есть начальные пробелы отбрасываются.
Итак, чтобы буквально прочитать каждую строку, нам нужно убедиться, что разделение слов не происходит. Мы делаем это, устанавливая IFSпеременную в пустое значение.
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
Обратите внимание, как мы установили IFS специально для продолжительности readвстроенного . В IFS= read -r lineустанавливает переменную окружения IFS(пустое значение) , специально для исполнения read. Это пример общего простого синтаксиса команды : (возможно, пустая) последовательность назначений переменных, за которой следует имя команды и ее аргументы (также вы можете добавлять перенаправления в любой точке). Поскольку readэта переменная является встроенной, она фактически никогда не попадает в среду внешнего процесса; тем не менее, ценность $IFS- это то, что мы назначаем там, пока readвыполняется ». Обратите внимание, что readэто не специальная встроенная функция , поэтому назначение выполняется только в течение его продолжительности.
Поэтому мы стараемся не менять значение IFSдругих инструкций, которые могут на него полагаться. Этот код будет работать независимо от того, что IFSизначально было установлено для окружающего кода , и не вызовет никаких проблем, если код внутри цикла полагается IFS.
Сравните это с фрагментом кода, который просматривает файлы в двоеточии. Список имен файлов читается из файла, по одному имени файла в строке.
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
Если бы цикл был while IFS=; read -r name; do …, то for dir in $PATHне разбился бы на разделенные $PATHдвоеточием компоненты. Если бы код был IFS=; while read …, было бы еще более очевидно, что IFSон не установлен :в теле цикла.
Конечно, было бы возможно восстановить значение IFSпосле выполнения read. Но это потребовало бы знания предыдущего значения, что является дополнительным усилием. IFS= readэто простой способ (и, удобно, также самый короткий путь).
¹ И, если readон прерывается перехваченным сигналом, возможно, во время выполнения перехвата - это не указано в POSIX и зависит от оболочки на практике.
while IFS=X readне разделяетсяX, ноwhile IFS=X; read...