Как я могу сохранить результаты команды "найти" в виде массива в Bash
Я пытаюсь сохранить результат от find
виде массивов. Вот мой код:
#!/bin/bash
echo "input : "
read input
echo "searching file with this pattern '${input}' under present directory"
array='find . -name ${input}'
len=${#array[*]}
echo "found : ${len}"
i=0
while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done
Я получаю 2. TXT файлов в текущем каталоге. Поэтому я ожидаю '2' как результат ${len}
. Тем не менее, он печатает 1. Причина в том, что он принимает весь результат find
как один элемент. Как я могу это исправить?
PS
Я нашел несколько решений на StackOverFlow о подобной проблеме. Тем не менее, они немного отличаются, поэтому я не могу применить в моем случае. Мне нужно сохранить результаты в переменной до цикла. Еще раз спасибо.
Ответы
Ответ 1
Вот одно решение для получения вывода find
в массив bash
:
array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find . -name "${input}" -print0)
Это сложно, потому что, как правило, имена файлов могут содержать пробелы, новые строки и другие символы, враждебные сценариям. Единственный способ использовать find
и иметь имена файлов, безопасно отделенные друг от друга, - это использовать -print0
который печатает имена файлов, разделенные нулевым символом. Это не было бы большим неудобством, если бы функции bash readarray
/mapfile
поддерживали разделенные mapfile
строки, но это не так. read
Bash делает, и это приводит нас к циклу выше.
Как это устроено
-
Первая строка создает пустой массив: array=()
-
Каждый раз, когда выполняется оператор read
, имя файла, разделенное нулями, читается из стандартного ввода. Опция -r
говорит read
чтобы оставить символы обратной косой черты в покое. -d $'\0'
сообщает read
что входные данные будут разделены -d $'\0'
. Поскольку мы опускаем имя для read
, оболочка помещает ввод в имя по умолчанию: REPLY
.
-
Оператор array+=("$REPLY")
добавляет новое имя файла в массив array
.
-
В последней строке сочетает в себе команду перенаправления и замены, чтобы обеспечить выход find
к стандартному входу в while
цикла.
Зачем использовать процесс подстановки?
Если бы мы не использовали подстановку процесса, цикл можно записать так:
array=()
find . -name "${input}" -print0 >tmpfile
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done <tmpfile
rm -f tmpfile
В приведенном выше выводе find
хранится во временном файле, и этот файл используется как стандартный ввод для цикла while. Идея замены процесса состоит в том, чтобы сделать такие временные файлы ненужными. Таким образом, вместо того, имея в while
петля получить его из стандартного ввода tmpfile
, мы можем это получить его из стандартного ввода <(find. -name ${input} -print0)
.
Процесс замещения широко полезен. Во многих местах, где команда хочет прочитать из файла, вы можете указать замену процесса <(...)
вместо имени файла. Существует аналогичная форма >(...)
, которую можно использовать вместо имени файла, где команда хочет записать в файл.
Как и массивы, подстановка процессов - это особенность bash и других расширенных оболочек. Он не является частью стандарта POSIX.
Дополнительные примечания
Следующая команда создает переменную оболочки, а не массив оболочки:
array='find . -name "${input}"'
Если вы хотите создать массив, вам понадобится поместить символы в конец вывода find. Итак, наивно можно было:
array=('find . -name "${input}"') # don't do this
Проблема заключается в том, что оболочка выполняет разбиение слов по результатам find
поэтому не гарантируется, что элементы массива будут такими, как вы хотите.
Ответ 2
Если вы используете bash
4 или более поздней версии, вы можете заменить использование find
на
shopt -s globstar nullglob
array=( **/*"$input"* )
Шаблон **
, включенный globstar
, соответствует 0 или более каталогам, позволяя шаблону соответствовать произвольной глубине в текущем каталоге. Без опции nullglob
шаблон (после расширения параметра) обрабатывается буквально, поэтому без совпадений у вас будет массив с одной строкой, а не с пустым массивом.
Добавьте параметр dotglob
в первую строку, если вы хотите перемещаться по скрытым каталогам (например, .ssh
) и сопоставлять скрытые файлы (например, .bashrc
).
Ответ 3
вы можете попробовать что-то вроде
array=(`find . -type f | sort -r | head -2`)
, and in order to print the array values , you can try something like echo "${array[*]}"
Ответ 4
Bash 4.4 представил -d
для readarray
/mapfile
, так что теперь это можно решить с помощью
readarray -d '' array < <(find . -name "$input" -print0)
для метода, который работает с произвольными именами файлов, включая пробелы, символы новой строки и символы-заглушки.
Из руководства (опуская другие варианты):
mapfile [-d delim] [array]
-d
Первый символ delim
используется для завершения каждой строки ввода, а не перевода строки. Если delim
- пустая строка, mapfile
завершит строку, когда он читает символ NUL.
И readarray
- это просто синоним mapfile
.
Ответ 5
В bash $(<any_shell_cmd>)
помогает выполнить команду и $(<any_shell_cmd>)
вывод. Передача этого в IFS
с \n
качестве разделителя помогает преобразовать это в массив.
IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")
Ответ 6
Вы можете сделать следующее:
#!/bin/bash
echo "input : "
read input
echo "searching file with this pattern '${input}' under present directory"
array=(`find . -name '*'${input}'*'`)
for i in "${array[@]}"
do :
echo $i
done
Ответ 7
Для меня это работало нормально на Cygwin:
declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
echo "$nm"
done
Это работает с пробелами, но не с двойными кавычками (") в именах каталогов (которые в любом случае недопустимы в среде Windows).
Остерегайтесь пробела в опции -printf.