В тех удивительно частых случаях, когда вам действительно нужно каким-то образом обработать все непустые строки внутри переменной (включая их подсчет), вы можете установить для IFS только новую строку, а затем использовать механизм разбиения слов в оболочке, чтобы разбить непустые строки врозь.
Например, вот небольшая функция оболочки, которая суммирует непустые строки во всех предоставленных аргументах:
lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)
Скобки, а не фигурные скобки, используются здесь, чтобы сформировать составную команду для тела функции. Это заставляет функцию выполняться в подоболочке, чтобы она не загрязняла переменную внешнего мира и настройку расширения пути при каждом вызове.
Если вы хотите перебрать непустые строки, вы можете сделать это аналогично:
IFS='
'
set -f
for line in $lines
do
printf '[%s]\n' $line
done
Таким образом, манипулирование IFS является часто пропускаемым методом, который также удобен для выполнения таких операций, как разбор путей, которые могут содержать пробелы в столбцовом вводе с разделителями табуляции. Однако вы должны знать, что преднамеренное удаление пробела, обычно включаемого в стандартную настройку IFS для space-tab-newline, может привести к отключению разделения слов в тех местах, где вы обычно ожидаете его увидеть.
Например, если вы используете переменные для создания сложной командной строки для чего-то подобного ffmpeg
, вы можете включить их, -vf scale=$scale
только если для переменной scale
задано непустое значение. Обычно этого можно достичь с помощью, ${scale:+-vf scale=$scale}
но если IFS не включает свой обычный пробел во время выполнения расширения этого параметра, пробел между -vf
и scale=
не будет использоваться в качестве разделителя слов и ffmpeg
будет передаваться -vf scale=$scale
как один аргумент, чего он не поймет.
Чтобы исправить это, вы либо должны убедиться , что IFS был установлен более обычно , прежде чем делать ${scale}
расширение, или сделать два разложения: ${scale:+-vf} ${scale:+scale=$scale}
. Разделение слов, которое оболочка выполняет в процессе первоначального разбора командных строк, в отличие от разбиения, которое происходит на этапе расширения обработки этих командных строк, не зависит от IFS.
Еще кое-что, что может стоить вашего времени, если вы собираетесь делать такие вещи, - это создание двух глобальных переменных оболочки, содержащих только табуляцию и только новую строку:
t=' '
n='
'
Таким образом, вы можете просто включить $t
и $n
в расширения, где вам нужны вкладки и переводы строк, вместо того, чтобы засорять весь код пробелами в кавычках. Если вы предпочитаете вообще избегать заключенных в кавычки пробелов в оболочке POSIX, у которой нет другого механизма, это printf
может помочь, хотя вам нужно немного поработать, чтобы обойти удаление завершающих строк в расширениях команд:
nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}
Иногда установка IFS, как будто это переменная среды для каждой команды, работает хорошо. Например, вот цикл, который считывает имя пути, которое может содержать пробелы и коэффициент масштабирования из каждой строки входного файла с разделителями табуляции:
while IFS=$t read -r path scale
do
ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt
В этом случае read
встроенная функция видит, что IFS установлен только на вкладку, поэтому он не разбивает строку ввода, которую он читает, также на пробелы. Но IFS=$t set -- $lines
не работает: оболочка расширяется $lines
при построении set
аргументов встроенной функции перед выполнением команды, поэтому временная настройка IFS таким образом, который применяется только во время выполнения самой встроенной функции, приходит слишком поздно. Вот почему приведенные выше фрагменты кода устанавливают IFS на отдельном этапе, и поэтому им приходится иметь дело с проблемой его сохранения.
wc -l
в точности соответствует оригиналу:<<<$foo
добавляет новую строку к значению$foo
(даже если оно$foo
было пустым). Я объясняю в своем ответе, почему это, возможно, не то, что хотели, но это то, что спросили