Как удалить все совпадающие строки и одну после каждой из них?

У меня есть большой файл и список моих конкретных строк. Вывод не должен содержать мои конкретные строки и еще одну после каждой из них. Два последовательных совпадения невозможны из-за структуры файла, который я хочу отфильтровать. Например,

Конкретные строки:

'ggg'
'sss'

Вход:

'ggg'
'123'
'rrr'
'321'
'sss'
'666'

Выход:

'rrr'
'321'

Простой grep -v -A 1 не работает

🤔 А знаете ли вы, что...
С Bash можно управлять процессами, запущенными в фоновом режиме, с помощью управляющих сигналов.


130
5

Ответы:

Решено

Предположения:

  • мы ищем точные совпадения строк, включая пробелы, знаки препинания и кавычки.
  • совпадения могут встречаться в последовательных строках, и в этом случае мы игнорируем все совпадения плюс следующую несовпадающую строку (ПРИМЕЧАНИЕ: OP добавил комментарий, в котором говорится, что совпадения последовательных строк невозможны; см. конец ответа для упрощенного сценария awk)

Общий подход:

  • если мы находим совпадающую строку, мы игнорируем текущую строку и устанавливаем флаг, чтобы игнорировать следующую строку
  • если флаг установлен, мы игнорируем текущую строку и снимаем флаг
  • в противном случае мы печатаем текущую строку

Пример входного файла:

$ cat input
'ggg'                       # match/ignore and 
'123'                       # ignore
'rrr'
'321'
'sss'                       # match/ignore and 
'666'                       # ignore
'aaa' 'ggg' 'xxx'
'12345'
'xxx'                       # match/ignore and
'xxx'                       # match/ignore and
98352                       # ignore
'xyz'
hello world

Пример набора строк для сопоставления (и игнорирования):

$ cat lines
'ggg'              # will not match on the line: 'aaa' 'ggg' 'xxx'
'sss'
rrr                # will not match on 'rrr' because of the missing quotes
'xxx'              # will match on consecutive lines and skip the next non-matching line

ПРИМЕЧАНИЕ. Комментарии в файлах не существуют.

Одна awk идея:

awk '
#### 1st file:

FNR==NR { a[$0];  next }       # save line as index in array a[]

#### 2nd file:

$0 in a { skip=1; next }       # if line is an index in array then set the "skip" flag and ignore this line

skip    { skip=0; next }       # if flag is set then clear flag and ignore this line

1                              # otherwise print current line
' lines input

######
# or as a one-liner

awk 'FNR==NR {a[$0];next} $0 in a {skip=1;next} skip {skip=0;next} 1' lines input

Это генерирует:

'rrr'
'321'
'aaa' 'ggg' 'xxx'
'12345'
'xyz'
hello world

ПРИМЕЧАНИЕ. Если предположения неверны и/или это не работает для реальных файлов OP, тогда нам потребуется обновить вопрос с использованием более репрезентативного набора данных.


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

awk '
FNR==NR { a[$0];   next }       # 1st file: save line as index in array a[]
$0 in a { getline; next }       # 2nd file: if line is an index in array then get next line (and ignore) then skip to next input line otherwise ...
1                               # print current line
' lines input

######
# or as a one-liner

awk 'FNR==NR {a[$0];next} $0 in a {getline;next} 1' lines input

Если мы удалим одну из строк 'xxx' из файла input, это сгенерирует:

'rrr'
'321'
'aaa' 'ggg' 'xxx'
'12345'
'xyz'
hello world

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

В любом POSIX awk вы можете легко реализовать конечный автомат, который сделает это при чтении второго файла:

awk 'FNR == NR {a[$0]; next}
     skip == 1 {skip = 0; next}
     $0 in a   {skip = 1; next}
               {skip = 0; print}' file1 file2

При анализе первого файла (FNR == NR) мы сохраняем строки в ассоциативном массиве a и переходим к следующей строке (next). При анализе второго файла мы находимся либо в состояниях skip == 1, либо skip == 0. Начальное состояние — skip == 0, а поведение конечного автомата:

  1. Если мы находимся в состоянии skip == 1, установите состояние skip == 0 и перейдите к следующей строке, не печатая текущую (это строка, следующая за соответствующей).
  2. В противном случае, если текущая строка соответствует строке из первого файла, установите состояние skip == 1 и перейдите к следующей строке, не печатая текущую (она совпадающая).
  3. В противном случае установите состояние skip == 0 и напечатайте текущую строку (это не совпадающая строка и не строка, следующая за совпадающей).

Используя любой awk:

$ awk 'NR==FNR{lines[$0]; next} $0 in lines{c=2} !(c&&c--)' lines input
'rrr'
'321'

Если вы хотите пропустить 15 строк вместо 2, измените 2 на 15.

См. также Печать с помощью sed или awk строки по соответствующему шаблону, чтобы узнать о похожих идиомах.


Вот awk-скрипт:

function check(line) { return \
line != "'ggg'" && \
line != "'sss'"
}
check($0) && check(prev) {print $0}
{prev = $0}

А также Perl-скрипт:

open(FH, '<', $ARGV[0]) or die;
while(<FH>){
    push @spec, $_;
}
close(FH);
open(FH, '<', $ARGV[1]) or die;
while($line = <FH>){
    if (not (grep /$line/, @spec) and (length $prev <= 0 or not (grep /$prev/, @spec))) {
        print $line;
    }
    $prev = $line;
}
close(FH);

Он принимает 2 аргумента командной строки: файл с определенными строками и входной файл.


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

sed 's#.*#/^&$/ba#' lines | sed -f - -e 'b;:a;N;d' file

Используйте файл строк для создания входного файла регулярных выражений sed для каждой строки входных строк.

Передайте регулярные выражения через канал в вызов sed, который ничего не делает, если регулярные выражения не совпадают. В противном случае добавьте следующую строку и удалите как соответствующую, так и следующую строку.