Zgrep останавливается после первого совпадения, когда аргументы передаются из xargs

Zgrep останавливается после первого совпадения, когда аргументы передаются из xargs

Я использую эту команду для поиска шаблонов в zip-файлах (похожих на предложенные здесь) https://superuser.com/questions/144926/unix-grep-for-a-string-within-all-gzip-files-in-all-subdirectories

find . -regex ".*/.*zip" | xargs zgrep -m 1 -E "PATTERN"

Grepping все еще продолжается после первого совпадения. Вероятно, find/ xargsявляется виновником. Как остановить поиск после того, grepкак находит первое совпадение?

ПСКак остановить команду find после первого совпадения?не сработает, так как findего необходимо остановить после успешного совпадения grep, а не только после первого совпадения find.

решение1

Несколько вещей:

  • zgrepзаключается в просмотре .zсжатых .gzфайлов, а не файлов внутри сжатых zipархивов.

    zipgrepИногда в комплекте с , идет (сломанный) скрипт unzipдля просмотра zipархивов, но он запускается egrepдля каждого члена архива (поэтому для -m1каждого egrepбудет выведено первое совпадение для каждого файла).

    zgrep, аналогично — это скрипт, который поставляется с gzip, который передает вывод gzip -cdfqв grepдля каждого файла. gzip -dможет распаковывать zipфайлы, но делает это только для первого элемента архива и только если он сжат (в zipфайлах не все элементы обязательно сжаты, особенно небольшие).

  • xargsзапускает столько команд, сколько необходимо, но может запустить и несколько, если список файлов большой.

Здесь лучшим вариантом, вероятно, будет реализация zipgrepвручную (в данном случае с помощью инструментов GNU):

find . -name '*.zip' -type f -exec sh -c '
    unzip -Z1 "$1" |
      while IFS= read -r file; do
        unzip -p "$1" "$file" | grep --label="$1//$file" -Hm1 -- "$0" && exit
      done' PATTERN {} \; -quit

Это запускает одну оболочку для каждого файла, но так же будет запускаться zipgrepи zipgrepгораздо больше команд.

Он может не сработать, если имена членов архива содержат подстановочные знаки ( *, [, ?) или другие символы, такие как символы ASCII от 0x1 до 0x1f и различные другие, но это в основном из-за ошибок и ограничений в unzip, и это не так плохо, как при использовании zipgrep.

решение2

Пытаться:

find . -iname '*.zip' -print0 | xargs -0r zgrep -l -E 'PATTERN'

Я использовал -inameвместо этого -regex— это тоже работает и, на мой взгляд, менее запутанно, чем findстранная обработка регулярных выражений. -print0и xargs -0используются для того, чтобы любые имена файлов с пробелами или метасимволами оболочки в них обрабатывались правильно.

grepПараметр 's -lзадокументирован на странице руководства:

   -l, --files-with-matches
          Suppress  normal  output;  instead  print the name of each input
          file from which output would normally have  been  printed.   The
          scanning  will  stop  on  the  first match.

Первое указанное совпадение относится к каждому файлу, поэтому, если совпадет несколько файлов, они все будут выведены. Обратите внимание, что это означает, что grep продолжит поиск в других файлах, даже после того, как найдет одно совпадение.

Если вы хотите, чтобы он остановился после самого первого совпадения, вы можете использовать grepопцию --line-bufferedи передать вывод grep в head -1. Когда будет выведено первое совпадение, headвыведет его и завершит работу, grepбольше не будет иметь stdout, поэтому завершит работу, и findпоследует за ним.

find . -iname '*.zip' -print0 | xargs -0r zgrep --line-buffered -l -E 'PATTERN' | head -1

решение3

grepопция (или zgrep) -mзаставит его прекратить чтениетекущий файлв первом матче:

   -m NUM, --max-count=NUM
          Stop reading a file after NUM matching lines.  

Это не помешает ему искатьследующийфайл. Например:

$ echo "hello" > foo
$ echo "hello" > bar
$ grep -m 1 hello foo bar
foo:hello
bar:hello

Итак, проблема не в xargsтом, что вы сканируете несколько файлов. Для того, чтобы иметь grep(или zgrep) остановиться после первого совпаденияфайл, вам придется запустить небольшой цикл, как предложил @Stephane. Или что-то вроде этого с bash :

shopt -s globstar
for i in **/*.zip; do
  zgrep -l pattern "$i" && break; 
done

Или для zip-архивов, которыесодержать несколько файлов(спасибо @Stephane):

shopt -s globstar
for i in **/*.zip; do
  if unzip -p "$i" | grep -q hello; then 
    echo "$i" && break;
  fi;
done

решение4

grep -m 1выводит список первых совпадений каждого файла.

Есть простой способ вывести только первое совпадение: pipe through head -n 1. Поиск скоро умрет отСИГПАЙП.

find . -regex ".*/.*zip" -print0 | xargs -0 zgrep -E "PATTERN" | head -n 1

Связанный контент