Могу ли я поместить строки, разделенные символами новой строки, в массив с помощью встроенного чтения?

У меня есть переменная:

var='/path/to/filename.ext
     /path/to/filename2.ext
     /path/to/filename3.ext'

Я хочу поместить все строки, разделенные новой строкой в ​​массиве:

declare -a arr

Основываясь на многочисленных сообщениях здесь, в StackOverflow, я нашел несколько способов:

# method 1: while loop
while read line; do
    arr+=($line)
done <<< "$var"

# method 2: readarray
readarray -t arr <<< "$var"

# method 3:
IFS=$'\n'
arr=("$var")

Однако, прежде чем я узнал все эти методы, я использовал другой, а именно:

# method 4 (not working in the current situation)
IFS=$'\n'
read -a arr <<< "$var"

Это не работает, потому что он сохранит только первую строку var в arr[0]. Я не понимаю, почему он не работает в ситуациях, когда разделитель является символом новой строки, тогда как он работает с другими разделителями, например:

IFS='|'
strings='/path/to/filename.ext|/path/to/filename2.ext|'
read -a arr <<< "$strings"

Есть ли что-то, что мне не хватает?

Изменить

Убрал мой собственный ответ, который утверждал, что вы не можете использовать read для этой цели. Оказывается, вы можете.

Ответ 1

Оказывается, ваш ответ неверен. Да, ты можешь! вам нужно использовать переключатель -d в read:

-d delim

Первый символ delim используется для завершения строки ввода, а не для новой строки.

Если вы используете его с пустым аргументом, bash использует нулевой байт в качестве разделителя:

$ var=$'/path/to/filename.ext\n/path/to/filename2.ext\n/path/to/filename3.ext'
$ IFS=$'\n' read -r -d '' -a arr < <(printf '%s\0' "$var")
$ declare -p arr
declare -a arr='([0]="/path/to/filename.ext" [1]="/path/to/filename2.ext" [2]="/path/to/filename3.ext")'

Успех. Здесь мы используем замену процесса с помощью printf, которая просто сбрасывает содержимое переменной с конечным нулевым байтом, так что read счастлив и возвращает код возврата успеха. Вы можете использовать:

IFS=$'\n' read -r -d '' -a arr <<< "$var"

В этом случае содержание arr является одинаковым; единственное отличие состоит в том, что код возврата read равен 1 (сбой).


В качестве побочного примечания: существует разница между

$ IFS=$'\n'
$ read ...

и

$ IFS=$'\n' read ...

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

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


Еще одно примечание: о вашем методе 1. Вам не хватает кавычек, вы не отключаете IFS, и вы не используете флаг -r для read. Это плохо:

while read line; do
    arr+=($line)
done <<< "$var"

Это хорошо:

while IFS= read -r line; do
    arr+=( "$line" )
done <<< "$var"

Почему?

  • не отбрасывая IFS, вы удалите удаленные пробелы и начало.
  • Без -r некоторые обратные косые черты будут пониматься как экранирование обратных косых черт (\', trailing \, \ и, возможно, другие).
  • Без правильного цитирования ( "$line" ) вы получите расширение слова и расширение имени файла: вы не хотите, чтобы на вашем входе содержались пробелы или символы глобуса (например, *, [, ?, и т.д.).