выполнить операцию для * каждого * элемента, указанного grep - PullRequest
8 голосов
/ 13 марта 2012

Как я могу выполнить операцию для каждого элемента, указанного grep в отдельности?

Справочная информация:

Я использую grep для вывода списка всех файлов, содержащих определенный шаблон:

grep -l '<pattern>' directory/*.extension1

Я хочу удалить все перечисленные файлы , но также все файлы с одинаковым именем файла, но с другим расширением: .extension2.

Я попытался использовать трубу, но, похоже, он принимает вывод grep в целом.

В find есть опция -exec, но у grep ничего подобного нет.

Ответы [ 4 ]

14 голосов
/ 13 марта 2012

Если я понимаю вашу спецификацию, вы хотите:

grep --null -l '<pattern>' directory/*.extension1 | \
    xargs -n 1 -0 -I{} bash -c 'rm "$1" "${1%.*}.extension2"' -- {}

По сути, это то же самое, что описывает комментарий @ triplee, за исключением того, что он безопасен для новой строки.

Что здесь происходит?

grep с --null вернет вывод, разделенный нулями вместо новой строки.Поскольку в именах файлов могут присутствовать символы новой строки, а разделение символом новой строки делает невозможным безопасный анализ выходных данных grep, но значение NULL не является допустимым символом в имени файла и, таким образом, создает хороший разделитель.* примет поток элементов, разделенных символом новой строки, и выполнит заданную команду, передавая столько этих элементов (по одному на каждый параметр) данной команде (или echo, если команда не указана).Таким образом, если бы вы сказали:

printf 'one\ntwo three \nfour\n' | xargs echo

xargs выполнит echo one 'two three' four.Это небезопасно для имен файлов, потому что, опять же, имена файлов могут содержать встроенные символы новой строки.

Переключатель -0 на xargs изменяет его с поиска разделителя новой строки на нулевой разделитель.Это позволяет ему соответствовать выводу, полученному нами из grep --null, и делает его безопасным для обработки списка имен файлов.

Обычно xargs просто добавляет ввод в конец команды.Переключатель -I на xargs меняет его на замену указанной замещающей строки вводом.Чтобы получить представление, попробуйте этот эксперимент:

printf 'one\ntwo three \nfour\n' | xargs -I{} echo foo {} bar

И обратите внимание на отличие от предыдущей команды printf | xargs.

В случае моего решения команда, которую я выполняю, - bash,к которому я перехожу -c.Переключатель -c заставляет bash выполнять команды в следующем аргументе (а затем завершаться) вместо запуска интерактивной оболочки.Следующий блок 'rm "$1" "${1%.*}.extension2"' является первым аргументом -c и является сценарием, который будет выполняться bash.Любые аргументы, следующие за аргументом сценария -c, назначаются в качестве аргументов сценария.Это, если бы я сказал:

bash -c 'echo $0' "Hello, world"

Тогда Hello, world будет присвоено $0 (первый аргумент сценария), и внутри сценария я смогу echo вернуть его обратно.

Поскольку $0 обычно зарезервировано для имени скрипта, я передаю фиктивное значение (в данном случае --) в качестве первого аргумента, а затем вместо второго аргумента пишу {}, чтострока замены, которую я указал для xargs.Это будет заменено на xargs с каждым именем файла, проанализированным из вывода grep перед выполнением bash.

Скрипт мини-оболочки может выглядеть сложным, но это довольно тривиально.Во-первых, весь сценарий заключен в одинарные кавычки, чтобы вызывающая оболочка не могла его интерпретировать.Внутри скрипта я вызываю rm и передаю ему два имени файла для удаления: аргумент $1, который был именем файла, переданным при замене строки замены выше, и ${1%.*}.extension2.Последнее является заменой параметра в переменной $1.Важная часть - %.*, которая говорит:

  • % "Сопоставьте с концом переменной и удалите самую короткую строку, соответствующую шаблону.
  • .* Шаблонодин период, за которым следует что-либо.

Это эффективно удаляет расширение, если оно есть, из имени файла. Вы можете наблюдать эффект самостоятельно:

foo='my file.txt'
bar='this.is.a.file.txt'
baz='no extension'
printf '%s\n'"${foo%.*}" "${bar%.*}" "${baz%.*}"

Поскольку расширение имеетЯ объединяю желаемое альтернативное расширение .extension2 с именем удаленного файла, чтобы получить альтернативное имя файла.

4 голосов
/ 13 марта 2012

Если это делает то, что вы хотите, направьте вывод через /bin/sh.

grep -l 'RE' folder/*.ext1 | sed 's/\(.*\).ext1/rm "&" "\1.ext2"/'

Или, если sed вызывает у вас зуд:

grep -l 'RE' folder/*.ext1 | while read file; do
  echo rm "$file" "${file%.ext1}.ext2"
done

Удалите echo, есливыходные данные выглядят как команды, которые вы хотите запустить.

Но вы можете сделать это также с помощью find:

find /path/to/start -name \*.ext1 -exec grep -q 'RE' {} \; -print | ...

, где ... это либо скрипт sed, либо три строкиот while до done.

Идея здесь в том, что find будет ... ну, "находить" вещи на основе заданных вами квалификаторов, а именно, что вещи соответствуют глобусу файла"* .ext", И что результат "exec" успешен.-q говорит grep искать RE в {} (файл, предоставленный find) и выходить с ИСТИНОЙ или ЛОЖЬМ, не генерируя какой-либо свой собственный вывод.

Единственная реальная разница междуДелая это в find, вместо того, чтобы делать это с помощью grep, вы получаете возможность использовать удивительный набор условий find для более узкого поиска, если это необходимо.man find для деталей.По умолчанию команда find переходит в подкаталоги.

2 голосов
/ 13 марта 2012

Вы можете направить список в xargs:

grep -l '<pattern>' directory/*.extension1 | xargs rm

Что касается второго набора файлов с другим расширением, я бы сделал это (как обычно, используйте xargs echo rm при тестировании, чтобы сделать сухойвыполнить; я не проверял его, он может некорректно работать с именами файлов с пробелами в них):

filelist=$(grep -l '<pattern>' directory/*.extension1)
echo $filelist | xargs rm
echo ${filelist//.extension1/.extension2} | xargs rm
0 голосов
/ 13 марта 2012

Передайте результат в xargs, это позволит вам выполнить команду для каждого совпадения.

...