it-swarm-ru.tech

Почему `if IFS = read` используется так часто, вместо` IFS =; пока читаешь?

Кажется, что в обычной практике установка IFS выходит за пределы цикла while, чтобы не повторять установку его для каждой итерации ... Является ли это просто привычным стилем "monkey see, monkey do", как это было для этой обезьяны до Я прочитал человек читал, или я упускаю какую-то тонкую (или явно очевидную) ловушку здесь?

85
Peter.O

Ловушка в том, что

IFS=; while read..

устанавливает IFS для всей среды Shell вне цикла, тогда как

while IFS= read

переопределяет его только для вызова read (за исключением оболочки Bourne). Вы можете проверить, что делает цикл как

while IFS= read xxx; ... done

затем после такой петли echo "blabalbla $IFS ooooooo" печатает

blabalbla
 ooooooo

тогда как после

IFS=; read xxx; ... done

IFS остается переопределенным: теперь echo "blabalbla $IFS ooooooo" печатает

blabalbla  ooooooo

Поэтому, если вы используете вторую форму, вы должны не забыть сбросить: IFS=$' \t\n'.


Вторая часть этого вопроса была объединена здесь , поэтому я удалил соответствующий ответ отсюда.

86
rozcietrzewiacz

Давайте посмотрим на пример с тщательно продуманным вводным текстом:

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 variable в пустое значение.

$ 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 и на практике зависит от командной консоли.

48
Gilles 'SO- stop being evil'

Помимо (уже уточненных) IFS областей видимости различий между while IFS='' read, IFS=''; while read а также while IFS=''; read идиомы (для каждой команды против скрипта/для всей оболочки IFS область видимости переменной), урок, который нужно взять с собой, заключается в том, что вы теряете ведущую а также завершающие пробелы входной строки, если для переменной IFS задано (содержать a) пробел.

Это может иметь довольно серьезные последствия, если пути к файлам обрабатываются.

Поэтому установка переменной IFS для пустой строки - это не плохая идея, поскольку она гарантирует, что начальные и конечные пробелы в строке не будут удалены.

Смотрите также: Bash, чтение построчно из файла, с IFS

(
shopt -s nullglob
touch '  file with spaces   '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)
3
jon

Вдохновленный ответом Юзема

Если вы хотите установить IFS для реального символа, это сработало для меня

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
1
Steven Penny