Подсчет строк, игнорируемых grep

Постараюсь объяснить это как можно яснее...

У меня есть скрипт, который в какой-то момент делает это:

grep -vf ignore.txt input.txt

В этом ignore.txt есть куча строк с вещами, которые я хочу, чтобы мой grep игнорировал, поэтому -v (что означает, что я не хочу видеть их в выводе grep).

Теперь я хочу знать, сколько строк input.txt было проигнорировано каждой строкой ignore.txt.

Например, если в ignore.txt есть такие строки:

line1
line2
line3

Я хотел бы знать, сколько строк input.txt было проигнорировано при игнорировании строки 1, сколько при игнорировании строки 2 и т. д.

Любые идеи о том, как я могу это сделать?

Надеюсь, это имело смысл... Спасибо!


person coconut    schedule 01.12.2011    source источник
comment
Важно ли использовать grep? Или вы бы рассмотрели решение, которое использует sed или awk?   -  person Eric Wilson    schedule 01.12.2011
comment
sed или awk тоже подойдут. Это все часть Perl-скрипта, так что это тоже сработает.   -  person coconut    schedule 01.12.2011
comment
Извините, я имел в виду количество строк, совпадающих по шаблону со строкой из ignore.txt.   -  person coconut    schedule 02.12.2011


Ответы (7)


Обратите внимание, что сумма проигнорированных строк и показанных строк НЕ может составлять общее количество строк... "строка1 и строка2 здесь" будет подсчитана дважды.

#!/usr/bin/perl
use warnings;
use strict;

local @ARGV = 'ignore.txt';
chomp(my @pats = <>);

foreach my $pat (@pats) {
    print "$pat: ", qx/grep -c $pat input.txt/;
}
person tadmc    schedule 01.12.2011
comment
Вы многократно вызываете внешнюю утилиту grep (потенциально много раз) в цикле, который не только многократно запускает утилиту, но и считывает весь входной файл каждый время! Perl может справиться с этой задачей и без всего этого. - person Dennis Williamson; 04.12.2011

Согласно unix.stackexchange

grep -o pattern file | wc -l

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

Тем не менее, я бы попытался создать более удобное решение с использованием языка сценариев, например, например. питон.

person Bubu    schedule 01.12.2011
comment
Это тоже было моим первым побуждением, но это не решает его проблемы, поскольку он спрашивает не общее количество, а количество строк в файле шаблона. - person flesk; 01.12.2011
comment
@flesk хорошо, поправьте меня, если я ошибаюсь, но если он использует несколько экземпляров grep - читай: один экземпляр для каждого шаблона - он получает количество игнорируемых строк на шаблон, не так ли? Это будет иметь место, по крайней мере, в описанной проблеме, учитывая, что каждый шаблон представляет собой целую строку для игнорирования (пример s. ignore.txt приведен выше). - person Bubu; 01.12.2011
comment
Он использует только один экземпляр grep. Переключатель -f заставляет grep читать шаблоны из файла, которые затем сопоставляются с input.txt. Когда вы передадите это wc -l, вы получите только общее количество. - person flesk; 01.12.2011
comment
Лучше использовать grep -c вместо grep ... | wc -l. - person musiKk; 01.12.2011
comment
grep -o -f ignore.txt input.txt | sort | uniq -c будет работать, но это дает совпадения по шаблону, а не несовпадения. - person sorpigal; 01.12.2011

Этот сценарий будет подсчитывать совпадающие строки путем поиска хэша и сохранять строки для печати в @result, где вы можете обрабатывать их по своему усмотрению. Чтобы эмулировать grep, просто напечатайте их.

Я сделал скрипт, чтобы он мог распечатать пример. Чтобы использовать с файлами, раскомментируйте код в скрипте и прокомментируйте те, которые помечены # example line.

Код:

use strict;
use warnings;
use v5.10;
use Data::Dumper;  # example line

# Example data. 
my @ignore = ('line1' .. 'line9'); # example line
my @input  = ('line2' .. 'line9', 'fo' .. 'fx', 'line2', 'line3'); # example line

#my $ignore = shift;  # first argument is ignore.txt
#open my $fh, '<', $ignore or die $!; 
#chomp(my @ignore = <$fh>);
#close $fh;

my @result;

my %lookup = map { $_ => 0 } @ignore;
my $rx = join '|', map quotemeta, @ignore;

#while (<>) {  # This processes the remaining arguments, input.txt etc
for (@input) { # example line
    chomp;     # Required to avoid bugs due to missing newline at eof
    if (/($rx)/) {
        $lookup{$1}++;
    } else {
        push @result, $_;
    }
}

#say for @result;       # This will emulate grep
print Dumper \%lookup;  # example line

Вывод:

$VAR1 = {
          'line6' => 1,
          'line1' => 0,
          'line5' => 1,
          'line2' => 2,
          'line9' => 1,
          'line3' => 2,
          'line8' => 1,
          'line4' => 1,
          'line7' => 1
        };
person TLP    schedule 01.12.2011

while IFS= read -r pattern ; do
        printf '%s:' "$pattern"
        grep -c -v "$pattern" input.txt
done < ignore.txt

grep с -c подсчитывает совпадающие строки, но с добавлением -v подсчитывает не совпадающие строки. Итак, просто переберите шаблоны и посчитайте один раз для каждого шаблона.

person sorpigal    schedule 01.12.2011
comment
У одного из нас это наоборот. ОП спрашивает, сколько строк input.txt было проигнорировано при игнорировании строки 1. Похоже, ваш код подсчитывает количество строк input.txt, которые не игнорируются шаблоном. - person jmcnamara; 02.12.2011
comment
@jmcnamara: Да, у одного из нас все наоборот. OP сбивает с толку, запрашивая игнорируемые строки из списка шаблонов, называемого ignore.txt - игнорируются ли строки, которые соответствуют или НЕ соответствуют шаблонам игнорирования? Для меня формулировка звучит так, будто несоответствие игнорирования — это количество, которое он хочет; Я написал уточняющий вопрос. - person sorpigal; 02.12.2011
comment
Смотрите мой комментарий к ответу tadmc. Вы вызываете grep потенциально много раз и читаете весь файл каждый раз. - person Dennis Williamson; 04.12.2011
comment
@DennisWilliamson: я знаю это. Это не очень эффективное решение. Если эффективность так важна, необходимо более сложное решение, будь то в perl или в оболочке. - person sorpigal; 04.12.2011

Это напечатает количество проигнорированных совпадений вместе с соответствующим шаблоном:

grep -of ignore.txt input.txt | sort | uniq -c

Например:

$ perl -le 'print "Coroline" . ++$s for 1 .. 21' > input.txt
$ perl -le 'print "line2\nline14"'               > ignore.txt

$ grep -of ignore.txt input.txt | sort | uniq -c
      1 line14
      3 line2

То есть строка, соответствующая «line14», была проигнорирована один раз. Строка, соответствующая "строке2", была проигнорирована 3 раза.

Если вы просто хотите подсчитать общее количество игнорируемых строк, это сработает:

grep -cof ignore.txt input.txt 

Обновление: изменен приведенный выше пример, чтобы использовать строки, чтобы вывод был немного яснее.

person jmcnamara    schedule 01.12.2011
comment
При этом учитываются совпадения шаблона, а не несовпадения. Вам нужно будет вычесть результирующее количество для каждого шаблона из общего количества строк в файле, чтобы получить количество проигнорированных строк. - person sorpigal; 01.12.2011
comment
@Сорпигал. Не совсем. Он подсчитывает количество совпадений, но это совпадения, которые будут проигнорированы при запуске под -v. Например, выполнение OP -vf для 25 строк file.txt, созданных выше, дает количество строк, равное 21, т. е. 4 строки были проигнорированы. Мой однострочник выше дает разбивку этих 4 игнорируемых строк. - person jmcnamara; 02.12.2011

Это может сработать для вас:

# seq 1 15 | sed '/^1/!d' | sed -n '$='
7

Объяснение:

Удалите все строки, кроме тех, которые совпадают. Передайте эти совпадающие (игнорируемые) строки другой команде sed. Удалите все эти строки, но покажите номер только последней строки. Таким образом, в этом примере игнорируются строки с 1 по 15, строки с 1, 10 по 15 — всего 7 строк.

РЕДАКТИРОВАТЬ:

Извините, неправильно прочитал вопрос (все еще немного запутался!):

 sed 's,.*,sed "/&/!d;s/.*/matched &/" input.txt| uniq -c,' ignore.txt | sh

Это показывает количество matches для каждого шаблона в ignore.txt

 sed 's,.*,sed "/&/d;s/.*/non-matched &/" input.txt | uniq -c,' ignore.txt | sh

Это показывает количество non-matches для каждого шаблона в ignore.txt

При использовании GNU sed это тоже должно работать:

sed 's,.*,sed "/&/!d;s/.*/matched &/" input.txt | uniq -c,;e' ignore.txt

or

sed 's,.*,sed "/&/d;s/.*/non-matched &/" input.txt | uniq -c,;e' ignore.txt

Н.Б. Ваш успех с шаблонами может варьироваться, например, заранее проверьте метасимволы.

Поразмыслив, я подумал, что это можно улучшить до:

sed 's,.*,/&/i\\matched &,;$a\\d' ignore.txt | sed -f - input.txt | sort -k2n | uniq -c

or

sed 's,.*,/&/!i\\non-matched &,;$a\\d' ignore.txt | sed -f - input.txt | sort -k2n | uniq -c

Но НЕТ, на больших файлах это на самом деле медленнее.

person potong    schedule 01.12.2011

И ignore.txt, и input.txt отсортированы?

Если это так, вы можете использовать команду comm!

$ comm -12 ignore.txt input.txt

Сколько строк игнорируется?

$ comm -12 ignore.txt input.txt | wc -l

Или, если вы хотите сделать больше обработки, объедините comm с awk.:

$ comm ignore.txt input.txt | awk '
    END {print "Ignored lines = " igtotal " Lines not ignored = "commtotal " Lines unique to Ignore file = " uniqtotal}
    {
       if ($0 !~ /^\t/) {uniqtotal+=1}
       if ($0 ~ /^\t[^\t]/) {commtotal+=1}
       if ($0 ~ /^\t\t/) {igtotal+=1}
    }'

Здесь я использую вкладки, которые помещаются в вывод командой comm: * Если нет вкладок, строка находится только в ignore.txt. * Если есть одна вкладка, то она только в input.txt * Если есть две вкладки, строка есть в обоих файлах.

Кстати, не все строки в ignore.txt игнорируются. Если строки нет и в input.txt, нельзя сказать, что она игнорируется.

По предложению Денниса Уильямсона

comm ignore.txt input.txt | awk '
   !/^\t/ {uniqtotal++}
   /^\t[^\t]/ {commtotal++}
   /^\t\t/ {igtotal++}
     END {print "Ignored lines = " igtotal " Lines not ignored = "commtotal " Lines unique to Ignore file = " uniqtotal}'
person David W.    schedule 01.12.2011
comment
var++ работает, $o - это опечатка, размещение END в начале меня раздражает (извините), вы можете опустить внешний набор фигурных скобок и операторы if(), а $0: !/^\t/ {uniqtotal++} и /^\t[^\t]/ {commtotal++} работают - person Dennis Williamson; 04.12.2011
comment
@DennisWilliamson - Спасибо, что заметили опечатку. Я вырезал и вставил это из своего теста. Иногда, просматривая пост, я исправляю что-то перед публикацией. Я иногда случайно что-то удаляю и перепечатываю. Может быть, отсюда и взялся $o. Вы правы в большинстве своих комментариев. Я проводил быстрый тест и просто делал вещи, которые, как я знал, работали, даже если это немного больше, чем нужно напечатать. Что касается внешних фигурных скобок, они нужны мне в моей версии awk. - person David W.; 05.12.2011
comment
Какая версия авк? Вам нужны внешние фигурные скобки, если вы используете if, но они вам не нужны, если вы используете /.../ {...} (без if, как я описал в своем предыдущем комментарии). - person Dennis Williamson; 05.12.2011
comment
@ Деннис Уильямсон - Хорошо. Я вижу, у вас все еще есть фигурные скобки вокруг приращения. Добавил предложенный вами формат в мой ответ. - person David W.; 06.12.2011