
Я пытаюсь написать bash
скрипт, который ищет содержимое файлов в указанном дереве каталогов на наличие указанной подстроки.
Использование grep
только рекурсивной функции недостаточно, поскольку мне потенциально нужно перебрать каталог /
(и все подкаталоги) системы, что приведет к grep
исчерпанию памяти и прерыванию. Поэтому я решил получить список всех каталогов и подкаталогов в указанном дереве каталогов, используя find
следующие переменные, обозначающие аргументы, передаваемые скрипту.
searchdir=$HOME # passed in a script argument
searchstr="secret" # passed in a script argument
Я вызываю find
утилиту и сохраняю вывод во временный файл.
TF=$(mktemp)
find ${searchdir} -type d 1>$TF 2>/dev/null
Имея список всех каталогов во временном файле, я продолжаю итерацию по строкам этого файла, используя while-do
цикл с намерением выполнить поиск по всем файлам в каждом каталоге. Для grep
я использую формат параметров, предоставленный вэтот ответдля поиска всех файлов, включая скрытые, в одном каталоге.
cat $TF | while read line || [[ -n $line ]];
do
grepdir="${line}/{*,.*}"
grep -sHn "${searchstr}" ${grepdir}
done
... однако этот код не выводит никаких данных.
Я убедился, что...
Содержит ${TF}
правильный список всех каталогов. Вывод ${grepdir}
переменной дает вывод, который я ожидаю найти.
/home/user/{*,.*}
/home/user/.ssh/{*,.*}
/home/user/test/{*,.*}
# ... and so on
Если я запускаю grep
команду с жестко заданным каталогом, в частности ~/test/
, каталогом, содержащим два тестовых файла со строкой, которую она должна найти
grep -sHn "${searchstr}" /home/user/test/{*,.*}
... он правильно выводит два файла, содержащие подстроку «секрет».
/home/user/test/asdf:7:secret
/home/user/test/test.txt:5:asdfasfdsecretaasdfafd
Формат, который мне подходит, — это тот, который изначально упоминался вответ, обсуждающий рекурсивное использованиеgrep
. Если я сделаю это:
cat $TF | while read line || [[ -n $line ]];
do
grep -rn "${line}" -e "${searchstr}"
done
... Я получаю некоторый вывод (технически правильный, но со множеством дубликатов записей), но поскольку grep
обрабатывает каталоги рекурсивно, а у меня есть список всех каталогов, я обязательно буду получать одни и те же результаты много раз, а в таких каталогах, как вышеупомянутый корневой каталог, grep
произойдет полный сбой, чего я и пытаюсь избежать.
Вероятно, мне также следует упомянуть, что мои отчаянные попытки заставить это работать, такие как передача $(echo "${grepdir}")
в качестве параметра, также не привели к каким-либо результатам.
Скорее всего, в моем мышлении или понимании есть заблуждение bash
. Не следует ли bash
расширить ${grepdir}
переменную перед вызовом grep
? Где мой скрипт идет не так?
решение1
Правило №1: Если команда или скрипт не выполняет то, что вам нужно,
просмотрите сообщения об ошибках. Не бросайте их в /dev/null
.
Вы получаете сообщения об ошибках, подобные
grep: /home/user/{*,.*}: No such file or directory
grep: /home/user/.ssh/{*,.*}: No such file or directory
grep: /home/user/test/{*,.*}: No such file or directory
но вы их не видите.
Если мы посмотрим набаш(1), мы видим
Расширение выполняется в командной строке после ее разделения на слова. Существует семь видов выполняемого расширения: расширение фигурных скобок, расширение тильды, расширение параметров и переменных, подстановка команд, арифметическое расширение, разделение слов и расширение имени пути.
Порядок расширений следующий: расширение фигурных скобок; расширение тильды, расширение параметров и переменных, арифметическое расширение и подстановка команд (выполняется слева направо); разделение слов; и расширение имени пути.
Важной частью для вашей ситуации является то, что расширение скобок происходит до расширения переменной. Так что, если вы сказали
grep -sHn "${searchstr}" "${line}"/{*,.*}
затем
- раскрытие скобок превратит последний токен в
"${line}"/*
и"${line}"/.*
, - Расширение переменной превратит приведенное выше в
/home/user/*
и/home/user/.*
, а затем - Расширение имени пути превратит указанное выше в список имен файлов.
Но когда вы говорите,
grep -sHn "${searchstr}" ${grepdir}
затем
- Расширение переменной превращает последний токен в
/home/user/{*,.*}
,
и тогда уже слишком поздно для раскрытия фигурных скобок.
grep
ищет файл с буквальным именем /home/user/{*,.*}
.
ПС
grep -sHn "${searchstr}" "${line}/{*,.*}"
Это тоже не сработает, поскольку кавычки не позволят раскрыть скобки и пути.
PPS Тебе не нужны все эти брекеты;
grep -sHn "$searchstr" "$line"/{*,.*}
было бы хорошо.
решение2
Причина, по которой grep прерывается при рекурсии по всей системе, скорее всего, не в том, что он не может справиться с объемом данных, а в том, что он спотыкается об один или другой псевдофайл или файл устройства в /proc, /sys или /dev. Вы можете исключить проблемные каталоги с помощью опции --exclude
в командной строке.
Причина, по которой он не расширяет подстановочные знаки, заключается в том, что они заключены в кавычки в этой строке:
grepdir="${line}/{*,.*}"
Изменение этого, вероятно, будет способствовать их расширению.
grepdir="${line}/"{*,.*}
Другой способ добиться этого (с меньшим количеством скриптов с вашей стороны) — выбрать файлы, используя find
конвейерную передачу путей к файлам xargs
для обработки:find / ... -print 0 | xargs -0 ...
Однако в любом случае, скорее всего, все равно возникнут проблемы с файлами, на которых споткнулся исходный рекурсивный grep, если только вы их не исключите.