Давайте розглянемо приклад із ретельно складеним вхідним текстом:
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
робить ...