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

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

Тем не менее, мне нелегко на самом деле сделать вывод вывода полезным в bash или с другими утилитами командной строки. Единственный способ, которым я смог использовать выход, - это связать его с perl и изменить perl IFS на 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?

Ответ 1

Бесстыдно украден из Greg 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-emulation; вы должны запустить script с помощью #!/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, а не stdin.

Ответ 2

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

find . -print0 | xargs -r0 do_something_useful

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

Ответ 3

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

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

#!/bin/bash
printf "%s" "[email protected]" | 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

Вот очерк о том, как правильно обрабатывать имена файлов в оболочке, с большим количеством специфических особенностей:

http://www.dwheeler.com/essays/filenames-in-shell.html

Ответ 5

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

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

Ответ 6

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

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 -Scripting Guide.

Ответ 7

Вы можете смело делать счет с помощью этого:

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

Это похоже на версию 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[@]}

Ответ 11

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

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

Ответ 12

Ответ Гордона Дэвисона отлично подходит для 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/** )

Ответ 13

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

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

является:

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

Ответ 14

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

Вместо этого я рекомендую использовать python с sh.