Подтвердить что ты не робот

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

4b9b3361

Ответ 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 делает, и это приводит нас к циклу выше.

Как это устроено

  1. Первая строка создает пустой массив: array=()

  2. Каждый раз, когда выполняется оператор read, имя файла, разделенное нулями, читается из стандартного ввода. Опция -r говорит read чтобы оставить символы обратной косой черты в покое. -d $'\0' сообщает read что входные данные будут разделены -d $'\0'. Поскольку мы опускаем имя для read, оболочка помещает ввод в имя по умолчанию: REPLY.

  3. Оператор array+=("$REPLY") добавляет новое имя файла в массив array.

  4. В последней строке сочетает в себе команду перенаправления и замены, чтобы обеспечить выход 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.