Захват результатов поиска. -print0 в массив bash - PullRequest
72 голосов
/ 13 июля 2009

Использование find . -print0 представляется единственным безопасным способом получения списка файлов в bash из-за возможности имен файлов, содержащих пробелы, символы новой строки, кавычки и т. Д.

Тем не менее, мне трудно на самом деле сделать вывод find полезным в bash или с другими утилитами командной строки. Единственный способ, которым мне удалось использовать вывод, - это передать его в perl и изменить IFS в perl на null:

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

В этом примере печатается количество найденных файлов, что исключает опасность появления новых строк в именах файлов, приводящих к искажению счетчика, как это происходит с:

find . | wc -l

Поскольку большинство программ командной строки не поддерживают ввод с разделителями, равными нулю, я полагаю, что лучше всего было бы захватить вывод find . -print0 в массив bash, как я делал в приведенном выше фрагменте perl, и затем продолжить задача, какой бы она ни была.

Как я могу это сделать?

Это не работает:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

Гораздо более общий вопрос: Как я могу делать полезные вещи со списками файлов в bash?

Ответы [ 13 ]

97 голосов
/ 13 июля 2009

Бесстыдно похищен из Greg's BashFAQ :

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

Обратите внимание, что используемая здесь конструкция перенаправления (cmd1 < <(cmd2)) похожа, но не совсем так, как более обычный конвейер (cmd2 | cmd1) - если команды являются встроенными командами оболочки (например, while), Конвейерная версия выполняет их в подоболочках, и любые переменные, которые они устанавливают (например, массив a), теряются при выходе. cmd1 < <(cmd2) только запускает cmd2 в подоболочке, поэтому массив живет после своей конструкции. Предупреждение: эта форма перенаправления доступна только в bash, даже не bash в режиме sh-эмуляции; Вы должны начать свой сценарий с #!/bin/bash.

Кроме того, поскольку шаг обработки файла (в данном случае просто a[i++]="$file", но вы, возможно, захотите сделать что-то более замысловатое непосредственно в цикле) имеет перенаправленный ввод, он не может использовать любые команды, которые могут читать из stdin. Чтобы избежать этого ограничения, я склонен использовать:

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

... который передает список файлов через блок 3, а не через стандартный ввод.

7 голосов
/ 13 июля 2009

Может быть, вы ищете xargs:

find . -print0 | xargs -r0 do_something_useful

Опция -L 1 также может быть полезна для вас, что делает xargs exec do_something_useful только с одним аргументом файла.

5 голосов
/ 29 октября 2011

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

Сначала мы создаем небольшую программу, которая выполняет эту часть для нас:

#!/bin/bash
printf "%s" "$@" | base64

... и назовите его base64str (не забудьте chmod + x)

Во-вторых, теперь мы можем использовать простой и понятный цикл for:

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

Итак, хитрость в том, что строка base64 не имеет знака, который создает проблемы для bash - конечно, xxd или что-то подобное может также выполнить эту работу.

4 голосов
/ 13 июля 2009

Еще один способ подсчета файлов:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 
3 голосов
/ 14 сентября 2017

Начиная с Bash 4.4, встроенный mapfile имеет переключатель -d (для указания разделителя, аналогичного переключателю -d оператора read), а разделитель может быть нулевым байтом. Отсюда хороший ответ на вопрос в заголовке

Захват вывода find . -print0 в массив bash

есть:

mapfile -d '' ary < <(find . -print0)
2 голосов
/ 13 июля 2009

Вы можете безопасно сделать подсчет с этим:

find . -exec echo ';' | wc -l

(Он печатает новую строку для каждого найденного файла / каталога, а затем считает напечатанные строки ...)

1 голос
/ 18 августа 2009

Я новичок, но я верю, что это ответ; надеюсь, это кому-нибудь поможет:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
1 голос
/ 13 июля 2009

Избегайте xargs, если можете:

man ruby | less -p 777 
IFS=$'\777' 
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) 
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) 
echo ${#array[@]} 
printf "%s\n" "${array[@]}" | nl 
echo "${array[0]}" 
IFS=$' \t\n' 
1 голос
/ 13 июля 2009

Я думаю, что есть более элегантные решения, но я добавлю это. Это также будет работать для имен файлов с пробелами и / или символами новой строки:

i=0;
for f in *; do
  array[$i]="$f"
  ((i++))
done

Вы можете, например, перечислить файлы один за другим (в данном случае в обратном порядке):

for ((i = $i - 1; i >= 0; i--)); do
  ls -al "${array[$i]}"
done

На этой странице приведен хороший пример, подробнее см. Глава 26 в Расширенном руководстве по написанию сценариев .

0 голосов
/ 24 июня 2016

Гордон Дэвиссон отлично подходит для Баш. Однако для пользователей zsh существует полезный ярлык:

Сначала поместите строку в переменную:

A="$(find /tmp -type f -print0)"

Затем разделите эту переменную и сохраните ее в массиве:

B=( ${(s/^@/)A} )

Есть хитрость: ^@ - это символ NUL. Для этого вам нужно набрать Ctrl + V, а затем Ctrl + @.

Вы можете проверить, что каждая запись в $ B содержит правильное значение:

for i in "$B[@]"; echo \"$i\"

Внимательные читатели могут заметить, что в большинстве случаев можно избежать вызова команды find, используя синтаксис **. Например:

B=( /tmp/** )
...