Вопрос

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

Однако мне трудно сделать вывод find полезным в bash или с другими утилитами командной строки.Единственный способ использовать выходные данные — это передать их в Perl и изменить IFS Perl на ноль:

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

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

find . | wc -l

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

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

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

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

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

Это было полезно?

Решение

Бессовестно украдено у Часто задаваемые вопросы по Bash Грега:

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", но вы можете захотеть сделать что-нибудь более интересное непосредственно в цикле) его входные данные перенаправлены, он не может использовать какие-либо команды, которые могут читать из стандартного ввода.Чтобы избежать этого ограничения, я обычно использую:

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, а не через стандартный ввод.

Другие советы

Возможно, вы ищете xargs:

find . -print0 | xargs -r0 do_something_useful

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

Основная проблема заключается в том, что разделитель 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 или что-то подобное также может выполнить эту работу.

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

find /DIR -type f -print0 | tr -dc '\0' | wc -c 

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

Сбор результатов find . -print0 в массив bash

является:

mapfile -d '' ary < <(find . -print0)

Вы можете безопасно выполнить подсчет следующим образом:

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

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

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

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 в Расширенное руководство по написанию сценариев Bash.

Избегайте 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' 

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

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

Это похоже на версию Stephan202, но файлы (и каталоги) помещаются в массив одновременно.А for цикл здесь предназначен просто для того, чтобы «сделать полезные вещи»:

files=(*)                        # put files in current directory into an array
i=0
for file in "${files[@]}"
do
    echo "File ${i}: ${file}"    # do something useful 
    let i++
done

Чтобы получить счет:

echo ${#files[@]}

Старый вопрос, но никто не предложил этот простой метод, поэтому я решил, что так и сделаю.Конечно, если в ваших именах файлов есть ETX, это не решит вашу проблему, но я подозреваю, что это подойдет для любого реального сценария.Попытка использовать значение null, похоже, противоречит правилам обработки IFS по умолчанию.Приправьте по своему вкусу опциями поиска и обработкой ошибок.

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"

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

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

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

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

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

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

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

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

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

B=( /tmp/** )

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

Я бы рекомендовал использовать Python с ш вместо этого библиотека.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top