
Я пытаюсь найти файлы, используя 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
работает только для неинтерактивных оболочек.
Другая типичная замена канала < <(...)
(замена процесса) имеет проблему, заключающуюся в том, что довольно сложно проверить, успешно ли завершился дочерний процесс.