Найти все вхождения в файле с помощью sed

15

Использование ОС OPEN STEP 4.2 ... В настоящее время я использую следующую sedкоманду:

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Эта команда найдет один экземпляр в файле с IP-адресом 141.299.99.1 и также включит в себя 3 строки перед ним, что все хорошо, за исключением того, что я также хотел бы найти все экземпляры IP и 3 строки перед ним и не только первый.

Дол
источник
1
Пожалуйста, всегда включайте вашу ОС. Решения очень часто зависят от используемой операционной системы. Используете ли вы Unix, Linux, BSD, OSX, что-то еще? Какая версия?
Тердон
БОЛЬШАЯ ТОЧКА! Использование Open Step версии 4.2 довольно старое, и включенные оболочки не включают в себя многие функции, упомянутые в ответах ниже.
Дейл
Из любопытства - что такое система OPEN STEP 4.2 и для чего она используется сегодня?
Турбьёрн Равн Андерсен
(и если Perl доступен, вы действительно можете сделать много хороших вещей именно с этим)
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Может быть, это так: en.wikipedia.org/wiki/OpenStep
Barmar

Ответы:

4

Вот попытка эмулировать grep -B3с помощью движущегося окна sed, основанного на этом примере GNU sed (но, надеюсь, POSIX-совместимого - с подтверждением @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

Первые два выражения заполняют многострочный буфер шаблонов и позволяют ему обрабатывать граничный случай, в котором перед первым соответствием имеется менее 3 строк предыдущего контекста. Среднее выражение (совпадение с регулярным выражением) выводит строку за верхнюю часть окна до тех пор, пока нужный текст совпадения не будет смещен в буфер шаблонов. Финал $!N;Dпрокручивает окно на одну строку, кроме случаев, когда оно достигает конца ввода.

steeldriver
источник
-eне является специфичным для GNU. Чтобы быть POSIX / портативным, вам это нужно, так как после этого ничего не может быть }(и вам нужно ;до него).
Стефан Шазелас
Спасибо @ StéphaneChazelas - так вы говорите, что для того, чтобы быть POSIX / переносимым, первую группу нужно разделить / изменить как -e '1h;2,4{H;g;}' -e '1,3d'? У меня нет системы без GNU для тестирования (и --posixпереключатель GNU sed , похоже, не заботится).
SteelDriver
1
Да, в Linux вы можете протестировать другую реализацию с sedпомощью набора инструментов из семейной реликвии, который является потомком традиционного Unix sed. Спецификация POSIX / Unix для sedнаходится по адресу pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Стефан
Я получаю событие, не найденное ни по одному из них: N; D ': Событие не найдено. Я где-то пропускаю синтаксис? Благодарность!!
Дейл
Извините, я только что понял, что мое последнее редактирование пропустило закрывающую одинарную кавычку после первого выражения -e. Я исправил это сейчас - можете ли вы попробовать еще раз с приведенным выше выражением, пожалуйста?
SteelDriver
10

grep сделает лучшую работу из этого:

grep -B 3 141.299.99.1 TESTFILE

В -B 3средства для печати три строки перед каждым матчем. Это будет печатать --между каждой группой строк. Чтобы отключить это, используйте --no-group-separatorтакже.

-BОпция поддерживается GNUgrep и большинство версий BSD , а также ( OSX , FreeBSD , OpenBSD , NetBSD ), но это технически не является стандартным вариантом.

Майкл Гомер
источник
1
Майкл Гомер - Спасибо. У меня нет опции -B. Есть еще идеи?
Дейл
@Dale Можете ли вы установить GNU grep? Это даст вам возможность.
Бармар
9

С sedвами можно сделать раздвижное окно.

sed '1N;$!N;/141.299.99.1/P;D'

Это делает это. Но будьте осторожны - bashбезумное поведение расширяется, ! даже когда цитируется !!! в командной строке из вашей истории команд может сделать его немного сумасшедшим. Добавьте к команде префикс, set +H;если вы обнаружите, что это так. Чтобы затем включить его (но почему ???) сделать set -Hпотом.

Это, конечно, будет применяться только тогда , когда вы были с помощью bash- хотя я не верю , что ты. Я вполне уверен, что вы работаете с csh- (это, случается, оболочка, чье безумное поведение bashподражает расширению истории, но, возможно, не до крайностей, которые приняла оболочка c) . Так , вероятно\! , должен работать. Я надеюсь.

Это весь переносимый код: POSIX описывает свои три оператора следующим образом: (хотя стоит отметить, что я только подтвердил, что это описание существовало еще в 2001 году)

[2addr]N Добавьте следующую строку ввода, за \nисключением завершающей ewline, к пространству шаблона, используя встроенную \newline, чтобы отделить добавленный материал от исходного материала. Обратите внимание, что текущий номер строки изменяется.

[2addr]P Записать пространство шаблона до первой \nстроки в стандартный вывод.

[2addr]D Удалите начальный сегмент пространства образца через первую \nлинию ewline и начните следующий цикл.

Итак, в первой строке вы добавляете дополнительную строку в пространство шаблонов, чтобы она выглядела так:

^line 1s contents\nline 2s contents$

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

^line 1\nline 2\nline 3$

Если ваш ip-адрес найден внутри вас, Pнаберите до первой новой строки, поэтому просто введите строку 1 здесь. В конце каждого цикла вы Dвыбираете одно и то же и начинаете заново с того, что осталось. Итак, следующий цикл выглядит так:

^line 2\nline 3\nline 4$

...и так далее. Если ваш ip будет найден на любом из этих трех, самый старый распечатает - каждый раз. Так ты всегда впереди всего на три строчки.

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

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Это немного сложнее, чем ваш случай, потому что я должен был чередовать либо 0\n новой строки или 0$конца шаблонного пространства, чтобы более близко походить на вашу проблему - но они немного отличаются тем, что для этого требуется привязка - что может быть немного сложным, поскольку шаблон-пространство постоянно смещается.

Я использовал нечетные случаи 10 и 52, чтобы показать, что, пока якорь является гибким, то и вывод тоже. Полностью переносимый, я могу достичь тех же результатов, вместо этого рассчитывая на алгоритм и делаю:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

И расширить поиск при ограничении моего окна - с 0 до 9 и 0 и с 3 строк до двух.

В любом случае, вы поняли идею.

mikeserv
источник
Спасибо за ваш тяжелый труд. Извините, куда бы я поместил имя файла, который бы я хотел найти для поиска?
Дейл
@ Дейл - мой плохой. sed '...' $filename, Между прочим - я оставил периоды из вашей собственной строки поиска, но на самом деле это не периоды в шаблоне - они представляют какой-либо один символ. Вам, вероятно, oct\.oct\.oct\.octследует избегать их, чтобы они соответствовали только периодам.
mikeserv
Я попытался отследить его и другие символы <>, и я получил событие не найдено, которое я получаю с другими решениями здесь, поэтому мне интересно, несовместима ли моя ОС с этими решениями.
Дейл
теперь результаты с -> N; /141.299.99.1/P; D ': событие не найдено.
Дейл
@Dale - пожалуйста, смотрите обновление. Это должно помочь вам.
mikeserv
4

Поскольку вы упомянули, что у вас нет -Bопции grep, вы можете использовать Perl (например), чтобы сделать скользящее окно из 4 строк:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

Ответ Рамеша делает то же самое с awk.

Джозеф Р.
источник
Я не уверен, что моя версия Perl поддерживает это, но я попробую. Большое спасибо, что нашли время ответить на мой вопрос - очень благодарен!
Дейл
@ Дейл Добро пожаловать. Я сомневаюсь, что этот код использует любые передовые возможности Perl.
Джозеф Р.
4

Когда доступно, вы можете использовать pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file
хаос
источник
Проверяю, есть ли у меня PCREGREP. Мне нравится компактность команды. Очень благодарен за ваше время и усилия. Спасибо!!!
Дейл
4

Вы можете реализовать тот же базовый подход, что и другие ответы, не связанные с grep, в самой оболочке (это предполагает относительно недавнюю оболочку, которая поддерживает =~):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

В качестве альтернативы, вы можете записать весь файл в массив:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 
Тердон
источник
Моя оболочка очень старая - Steve Jobs Open Step. Отличная идея, и спасибо за ваше время! Дейл
Дейл
@ Дейл подход Perl будет работать практически везде. Пожалуйста, сообщите нам свою операционную систему (добавьте ее к своему вопросу), чтобы мы могли предложить то, что будет работать для вас.
Тердон
Если я скопирую ваш Perl и положу в NotePad и поместу в одну строку, он будет работать! Вопрос - если бы я хотел, скажем, за 10 строк до схемы совпадения, где бы я изменил 3 на 10? Благодарность!
Дейл
Я вижу, что могу добавить больше строк, добавив больше выражений $ F [$ iX]. Благодарность!
Дейл
4

Если ваша система не поддерживает grepконтекст, вы можете вместо этого попробовать ack-grep :

ack -B 3 141.299.99.1 file

ack такой инструмент, как grep, оптимизированный для программистов.

cuonglm
источник
Мне нравится компактность команды, но моя система не поддерживает ack при просмотре справочных страниц. Отличная идея и большое спасибо за ваше время! Дейл
Дейл
@ Дейл: Удивительно! Какая у тебя ОС? Если у вас есть perl, вы можете использовать ack.
cuonglm
2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

В этом awkрешении используется массив, который всегда будет содержать 3 строки перед текущим шаблоном. Следовательно, когда шаблон сопоставляется, содержимое массива вместе с текущим шаблоном печатается.

тестирование

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

После того, как я выполню команду, вывод

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
Рамеш
источник
так подробно - большое спасибо. Я попробую. Очень благодарен за ваше время! Дейл
Дейл
У меня есть тестовый файл, и ваше решение работает! Однако проблема заключается в том, что когда я запускаю его в своем большом производственном файле, он возвращается с слишком длинным номером записи, поэтому выходные данные не могут работать с командой. Моя оригинальная команда в верхней части этой страницы работает, но находит только один экземпляр. Я ценю вашу помощь. Могу ли я что-нибудь сделать с моей исходной командой, чтобы найти более одного экземпляра?
Дейл
1

В большинстве из них /141.299.99.1/также будут совпадать (например) 141a299q99+1или 141029969951потому, что .в регулярном выражении может быть представлен любой символ.

Использование /141[.]299[.]99[.]1/безопаснее, и вы можете добавить дополнительный контекст в начале и в конце всего регулярного выражения , чтобы убедиться , что он не соответствует 3141., .12, .104и т.д.

user117529
источник
1
Это хороший момент, и я тоже об этом подумал. Тем не менее, я использовал строку, предоставленную спрашивающим, как известный рабочий матч - и уведомил его лично о том же самом, когда предоставилась возможность. Во всяком случае - не все из них - ответ Steeldriver цитирует матч с самого начала.
mikeserv