Давайте розглянемо приклад із ретельно складеним вхідним текстом:
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 і залежить від оболонки на практиці.
while IFS=X readне розпадається наX, алеwhile IFS=X; readробить ...