Чому так часто використовується "while IFS = read" замість `IFS =; поки читати..`?


81

Здається, що звичайна практика поставила б встановлення IFS поза циклом while, щоб не повторювати його для кожної ітерації ... Це просто звичний стиль "мавпа бачити, мавпа робити", як це було для цієї мавпи до Я читаю, що людина читає , чи пропускаю тут якусь тонку (або очевидно очевидну) пастку?

Відповіді:


82

Пастка в тому

IFS=; while read..

встановлює значення IFSдля всієї оболонки зовнішнього циклу, тоді як

while IFS= read

переосмислює його лише для readвиклику (крім оболонки Борна). Ви можете перевірити, що роблять цикл, як

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'.


Друга частина цього питання тут об'єднана , тому відповідь я видалила звідси.


Гаразд, здається, що потенційна «пастка» - це нехтування перезавантаженням зовнішніх IFS ... Але мені цікаво, чи є ще щось вперед ... Я тестую тут речі, досить гарячково, і я зауважте, що встановлення IFS у списку команд while поводиться qute по-різному, залежно від того, чи слід за ним двокрапкою. Я не розумію такої поведінки (поки що), і мені зараз цікаво, чи є на цьому рівні особлива увага ... наприклад. while IFS=X readне розпадається на X, але while IFS=X; readробить ...
Peter.O

(Ви мали в виду підлозі двокрапка, вірно?) Другий whileне має особливого сенсу - умова для while кінців на цій точкою з коми, так що немає ніякого фактичного циклу ... readстає тільки перша команда в циклі один-елемент ... Чи ні ? Що з doтодішнім ..?
rozcietrzewiacz

1
Ні, зачекайте - ви праві, у вас може бути кілька команд у whileстані (раніше do).
rozcietrzewiacz

О, безумовно, ви можете мати їх ... як ви зрозуміли ... але вони, схоже, не люблять напівкрапки ... (і цикл буде тримати циклічний ad-infinitum, поки остання команда не поверне не -zero код виходу) ... Мені зараз цікаво, чи пастка повністю лежить в іншому секторі; розуміння того, як працює список команд while , наприклад. чому це IFS=працює, але IFS=Xні ... (або, можливо, я на цей час хоч
відмовився

1
$ rozcietrzewiacz .. На жаль, я не помітив вашого оновлення, коли я перемістив своє оновлення (як згадувалося в попередньому коментарі). Це виглядає цікаво, і воно починає мати сенс ... але навіть на ніч- птах, як я, вкрай пізно ... (я щойно почув птахів вранці:) ... Це сказав, я трохи згуртувався і прочитав ваші приклади ... Я думаю, що це я отримав, насправді я ' я впевнений, що ти це отримав, але я повинен спати :) ... Це майже Еврика! момент ... дякую
Peter.O

45

Давайте розглянемо приклад із ретельно складеним вхідним текстом:

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з’їли косою рисою косий риси: backslash-newline змушує ігнорувати новий рядок, а backslash - все, що ігнорує цю першу риску. Щоб уникнути особливих обробок, ми використовуємо 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 і залежить від оболонки на практиці.


4
Дякую Жиллю ... дуже приємна екскурсія ... (ти мав на увазі "встановити -f"?) .... Тепер, для читача, щоб переказати все, що вже було сказано, я хотів би підкреслити питання, яке було я дивлюся на це неправильно. Перш за все, це те, що конструкція while IFS= read(без напівкрапки після =) не є особливою формою whileабо з IFSабо з read.. Конструкція є родовою: тобто. anyvar=anyvalue anycommand. Відсутність ;після налаштування anyvarробить область anyvar локального значення anycommand.. Цикл "виконано / виконано" на 100% не пов'язаний з локальною сферою any_var.
Пітер.O

3

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

Це може мати досить серйозні наслідки, якщо обробляються шляхи до файлів.

Тому встановлення змінної 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"
)

+1 відмінна демонстрація, прибирання після файлу 'rm * * з * пробілами *'
amdn

0

Натхненний відповіддю Юзема

Якщо ви хочете встановити IFSфактичного персонажа, це працювало для мене

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.