Давайте посмотрим на пример с тщательно продуманным вводным текстом:
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
...