Видалити діапазон рядків вище шаблону за допомогою sed (або awk)


28

У мене є такий код, який видалить рядки з малюнком bananaта 2 рядки після нього:

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

Все йде нормально! Але мені це потрібно, щоб вилучити два рядки раніше 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]}'. Це неефективно, тому це лише натяк, а не рішення.
манатура

6
Просто роби tac file | sed ... | tac. : P
angus

@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 Оскільки це коментар, а не відповідь (вже є інші відповіді), я не буду надто детально описуватись, але суть цього у вас завжди є попередні два записи в prev [0] та prev [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's (для друку декількох рядків до і після)? (попередження: мінімально перевірено):

#!/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

@Gilles .. awkСценарій не зовсім правильний; as-is друкує порожні рядки та пропускає останні рядки. Це, здається, виправить це, але воно може бути не ідеальним чи правильним саме по собі: if (NR-before in h) { print...; delete...; }... і в ENDрозділі: for (i in h) print h[i]... Також сценарій awk друкує відповідні рядки, але tac/secверсія ні; але питання щодо цього дещо неоднозначне. "Оригінальний" awk-скрипт, на який ви надали посилання, працює чудово. Мені це подобається ... Я не впевнений, як зазначений вище "мод" впливає на друк після рядки ...
Пітер.О

@ 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примітив \nrint, який записує лише до першого символу 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.