Захват результатов поиска. -print0 в массив bash
С помощью find . -print0
Кажется, это единственный безопасный способ получения списка файлов в bash из-за возможности имен файлов, содержащих пробелы, символы новой строки, кавычки и т. д.
Тем не менее, мне трудно сделать вывод полезного в 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 ответов
Бесстыдно украден у Грега 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, а не через стандартный ввод.
Начиная с Bash 4.4, встроенный mapfile
имеет -d
переключатель (чтобы указать разделитель, аналогичный -d
переключатель read
оператор), а разделитель может быть нулевым байтом. Отсюда хороший ответ на вопрос в заголовке
Захватив выход
find . -print0
в массив Bash
является:
mapfile -d '' ary < <(find . -print0)
Может быть, вы ищете 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
Вы можете безопасно сделать подсчет с этим:
find . -exec echo ';' | wc -l
(Он печатает новую строку для каждого найденного файла / каталога, а затем считает распечатанные строки...)
Ответ Гордона Дэвиссона хорош для Баш. Однако для пользователей zsh существует полезный ярлык:
Сначала поместите строку в переменную:
A="$(find /tmp -type f -print0)"
Затем разделите эту переменную и сохраните ее в массиве:
B=( ${(s/^@/)A} )
Есть хитрость: ^@
это NUL-символ Чтобы сделать это, вы должны нажать Ctrl+V, а затем Ctrl+@.
Вы можете проверить, что каждая запись в $B содержит правильное значение:
for i in "$B[@]"; echo \"$i\"
Внимательные читатели могут заметить, что призыв к find
в большинстве случаев можно избежать использования команды **
синтаксис. Например:
B=( /tmp/** )
Избегайте 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
Старый вопрос, но никто не предложил этот простой метод, поэтому я подумал, что буду. Конечно, если у ваших имен файлов есть ETX, это не решит вашу проблему, но я подозреваю, что это подходит для любого реального сценария. Попытка использовать null, похоже, противоречит правилам обработки IFS по умолчанию. Сезон на ваш вкус с опциями поиска и обработки ошибок.
savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
Я думаю, что существуют более элегантные решения, но я добавлю это. Это также будет работать для имен файлов с пробелами и / или символами новой строки:
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 в Расширенном руководстве по написанию сценариев.
Это похоже на версию 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[@]}
Bash никогда не был хорош в обработке имен файлов (или любого текста), потому что он использует пробелы в качестве разделителя списка.
Я бы рекомендовал вместо этого использовать python с библиотекой sh.