Удалить диапазон линий над шаблоном с помощью sed (или awk)


28

У меня есть следующий код, который удалит строки с шаблоном bananaи 2 строки после него:

sed '/banana/I,+2 d' file

Все идет нормально! Но мне нужно, чтобы он убрал 2 строки раньше banana , но я не могу получить его со знаком «минус» или чем-то еще (похоже на то, что grep -v -B2 banana fileдолжно делать, но не делает):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'

1
Проще всего загрузить все данные в массив, пропустить нежелательные линии , то вывод , что остается: awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'. Это неэффективно, так что это всего лишь подсказка, а не решение.
manatwork

6
Просто делай tac file | sed ... | tac. : P
ангус

@angus Я не думал об этом;)
Teresa e Junior

1
Вы могли бы сделать sed '/banana/,+2d' file это также будет работать
Акакс

1
Если вы открыты для использования awk, это довольно просто: так awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein как это комментарий, а не ответ (уже есть другие ответы), я не буду вдаваться в подробности, но суть в том, что у вас всегда есть предыдущие две записей в пред [0] и пред [1], «свежайшая» в зависимости от того, какой итерации , но всегда prev[idx], поэтому при печати, печати в !idxтом idxпорядки. Независимо, чередуйте idxи вставьте текущую запись в prev[idx].
Luv2code

Ответы:


22

Sed не возвращается: как только он обработал строку, он готов. Так что «найти строку и напечатать предыдущие N строк» ​​не будет работать как есть, в отличие от «найти строку и напечатать следующие N строк», на которую легко перейти.

Если файл не слишком длинный, так как вы, кажется, в порядке с расширениями GNU, вы можете использовать, tacчтобы перевернуть строки файла.

tac | sed '/banana/I,+2 d' | tac

Другим углом атаки является сохранение скользящего окна в таком инструменте, как awk. Адаптация от Есть ли альтернатива переключателям -A -B -C grep (чтобы напечатать несколько строк до и после)? (предупреждение: минимально проверено):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

Использование: /path/to/script -v pattern='banana' -v before=2


2
sedМожно также выполнять скользящие окна, но полученный скрипт обычно настолько нечитаем, что его проще использовать awk.
jw013

@ Жиль .. awkСценарий не совсем правильный; как-он печатает пустые строки и пропускает последние строки. Это, кажется, исправляет это, но это не может быть идеальным или правильным само по себе: if (NR-before in h) { print...; delete...; }... и в ENDразделе: for (i in h) print h[i]... Кроме того, сценарий awk печатает соответствующую строку, но tac/secверсия не делает; но вопрос немного двусмысленен по этому поводу. «Оригинальный» скрипт awk, на который вы предоставили ссылку, работает нормально… Мне это нравится… Я не уверен, как вышеупомянутый «мод» влияет на печать после линии ...
Peter.O

@ Peter.O Спасибо, сценарий awk теперь должен быть лучше. И это заняло у меня менее 6–8 лет!
Жиль "ТАК - перестань быть злым"

19

Это довольно легко с ex или vim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

Выражение гласит: для каждой строки, содержащей банан в диапазоне от текущей строки -2 до текущей строки, удалите.

Круто то, что диапазон может также содержать поиск в прямом и обратном направлении, например, при этом будут удалены все разделы файла, начиная со строки, содержащей яблоко, и заканчивая строкой, содержащей оранжевый и содержащей строку с бананом:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

7

Использование «скользящего окна» в perl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'

6

Вы можете сделать это довольно просто с помощью sed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

Я не знаю, почему кто-то сказал бы иначе, но чтобы найти строку и напечатать предыдущие строки, sed включается встроенный Pпримитив rint, который записывает только до первого \nсимвола ewline в пространстве образца. Дополнительный Dэлементарный элемент удаляет тот же сегмент пространства шаблонов перед рекурсивной переработкой сценария с тем, что осталось. И чтобы округлить его, есть примитив для добавления Nстроки ввода ext в пространство образца после вставленного \nсимвола ewline.

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

Также обратите внимание, что он будет правильно считать matchсразу предшествующий другой matchкак триггер для тихого вывода для предыдущих двух строк, а также для его печати:


1
7match
8
11match

Чтобы он работал на произвольном количестве строк, все, что вам нужно сделать, это получить преимущество.

Так:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... удаляет 5 строк, предшествующих любому совпадению.


1

Использование man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.