Bash: перенаправить вывод 'find' в 'readarray'

Bash: перенаправить вывод 'find' в 'readarray'

Я пытаюсь найти файлы, используя find, и поместить эти файлы в массив Bash, чтобы я мог выполнять другие операции с ними (например, lsили grepих). Но я не могу понять, почему readarrayне считывает findвывод, поскольку он передается в него.

Допустим, у меня есть два файла в текущем каталоге, file1.txtи file2.txt. Итак, findвывод будет следующим:

$ find . -name "file*"
./file1.txt
./file2.txt

Поэтому я хочу передать это в массив, двумя элементами которого являются строки "./file1.txt"и "./file2.txt"(разумеется, без кавычек).

Я пробовал это, среди прочего:

$ declare -a FILES
$ find . -name "file*" | readarray FILES
$ echo "${FILES[@]}"; echo "${#FILES[@]}"

0

Как вы можете видеть из echoвывода, мой массив пуст.

Так что же я делаю не так? Почему readarrayне считывает findвывод 's как стандартный ввод и не помещает эти строки в массив?

решение1

При использовании конвейера bash запускает команды в подоболочках¹. Таким образом, массив заполняется, но в подоболочке, поэтому родительская оболочка не имеет к нему доступа. Вы также, вероятно, захотите опцию, -tчтобы не сохранять разделители строк в членах массива, поскольку они не являются частью имен файлов.

Используйте замену процесса:

readarray -t FILES < <(find .)

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

readarray -td '' < <(find . -print0)

( -dопция была добавлена ​​в bash 4.4)


¹ за исключением последнего компонента трубы при использовании lastpipeопции, но это только для неинтерактивных вызовов bash.

решение2

Правильное решение:

unset a; declare -a a
while IFS= read -r -u3 -d $'\0' file; do
    a+=( "$file" )        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

Это похоже на то, чтоBashFAQ Грега 020подробно объясняет иэтот ответ охватывает.

Не имеет проблем с файлами с нечетными именами (не содержащими NUL в имени), с пробелами или новыми строками. И результат устанавливается в массиве, что делает его полезным для дальнейшей обработки.

решение3

readarrayтакже может читать из stdin

readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"

решение4

Решение shopt -s lastpipe, показанное в исходном вопросе, работает, так как часть после последней |выполняется в том же процессе, что и большая часть скрипта:

shopt -s lastpipe

# This is just the code form the original question.
# Of course it could be done with -print0, etc.
declare -a FILES
find . -name "name*" | readarray -t FILES
echo find and readarray exit status: "${PIPESTATUS[@]}"
echo "${FILES[@]}"
echo "${#FILES[@]}"

Однако это практично только в том случае, если lastpipeможно сделать стандартом для всего проекта (возможно, у вас уже есть такая общая настройка для set -uи т. д.). Также знайте, что это lastpipeработает только для неинтерактивных оболочек.

Другая типичная замена канала < <(...)(замена процесса) имеет проблему, заключающуюся в том, что довольно сложно проверить, успешно ли завершился дочерний процесс.

Связанный контент