Grep Match и извлечение


10

У меня есть файл, который содержит строки как

proto=tcp/http  sent=144        rcvd=52 spkt=3 
proto=tcp/https  sent=145        rcvd=52 spkt=3
proto=udp/dns  sent=144        rcvd=52 spkt=3

Мне нужно извлечь значение прото которое tcp/http, tcp/https, udp/dns.

До сих пор я пробовал это, grep -o 'proto=[^/]*/'но только смог извлечь значение как proto=tcp/.



Это работа для sed, awkили perl, не grep.
OrangeDog

Ответы:


1

Предполагая, что это связано с вашим предыдущим вопросом , вы идете по неверному пути. Вместо того, чтобы пытаться собрать воедино кусочки скриптов, которые будут делать то, что вы хотите, большую часть времени, и вам нужно будет получать совершенно разные скрипты каждый раз, когда вам нужно будет сделать что-то хоть немного отличающееся, просто создайте 1 скрипт, который может анализировать ваши входной файл в массив ( f[]ниже), который отображает ваши имена полей (теги) на их значения, а затем вы можете делать с результатом все, что захотите, например, учитывая этот входной файл из предыдущего вопроса:

$ cat file
Feb             3       0:18:51 17.1.1.1                      id=firewall     sn=qasasdasd "time=""2018-02-03"     22:47:55        "UTC""" fw=111.111.111.111       pri=6    c=2644        m=88    "msg=""Connection"      "Opened"""      app=2   n=2437       src=12.1.1.11:49894:X0       dst=4.2.2.2:53:X1       dstMac=42:16:1b:af:8e:e1        proto=udp/dns   sent=83 "rule=""5"      "(LAN->WAN)"""

мы можем написать скрипт awk, который создает массив значений, проиндексированных по их именам / тегам:

$ cat tst.awk
{
    f["hdDate"] = $1 " " $2
    f["hdTime"] = $3
    f["hdIp"]   = $4
    sub(/^([^[:space:]]+[[:space:]]+){4}/,"")

    while ( match($0,/[^[:space:]]+="?/) ) {
        if ( tag != "" ) {
            val = substr($0,1,RSTART-1)
            gsub(/^[[:space:]]+|("")?[[:space:]]*$/,"",val)
            f[tag] = val
        }

        tag = substr($0,RSTART,RLENGTH-1)
        gsub(/^"|="?$/,"",tag)

        $0 = substr($0,RSTART+RLENGTH)
    }

    val = $0
    gsub(/^[[:space:]]+|("")?[[:space:]]*$/,"",val)
    f[tag] = val
}

и учитывая, что вы можете делать со своими данными все, что угодно, просто ссылаясь на них по именам полей, например, используя GNU awk -eдля упрощения смешивания скрипта в файле со скриптом командной строки:

$ awk -f tst.awk -e '{for (tag in f) printf "f[%s]=%s\n", tag, f[tag]}' file
f[fw]=111.111.111.111
f[dst]=4.2.2.2:53:X1
f[sn]=qasasdasd
f[hdTime]=0:18:51
f[sent]=83
f[m]=88
f[hdDate]=Feb 3
f[n]=2437
f[app]=2
f[hdIp]=17.1.1.1
f[src]=12.1.1.11:49894:X0
f[c]=2644
f[dstMac]=42:16:1b:af:8e:e1
f[msg]="Connection"      "Opened"
f[rule]="5"      "(LAN->WAN)"
f[proto]=udp/dns
f[id]=firewall
f[time]="2018-02-03"     22:47:55        "UTC"
f[pri]=6

$ awk -f tst.awk -e '{print f["proto"]}' file
udp/dns

$ awk -f tst.awk -e 'f["proto"] ~ /udp/ {print f["sent"], f["src"]}' file
83 12.1.1.11:49894:X0

2
Это потрясающе, спасибо вам большое :)
user356831

Для такого рода работы, perlможет быть проще в использовании.
OrangeDog

1
@OrangeDog почему ты так думаешь? Я бы на самом деле хотел бы увидеть эквивалент в Perl, если вы не против опубликовать такой ответ. Perl определенно не будет легче использовать, если у меня его нет на коробке и я не могу его установить, хотя с этим я часто сталкивался на протяжении многих лет. Awk, с другой стороны, является обязательной утилитой и поэтому всегда присутствует в установках UNIX, как sed, grep, sort и т. Д.
Эд Мортон,

@EdMorton true, хотя лично я никогда не сталкивался с дистрибутивом, в котором perl не был включен по умолчанию. Сложные awkи sedсценарии, как правило, проще, perlпотому что это по сути их расширенный набор, с дополнительными функциями для общих задач.
OrangeDog

@OrangeDog Никто не должен писать сценарий sed, который более сложен, чем s/old/new/gsed и awk, поэтому давайте отложим это. Я совершенно не согласен с тем, что сложные сценарии awk проще в Perl. Конечно, они могут быть более краткими, но краткость не является желательным атрибутом программного обеспечения, лаконичность есть, и они очень редко получают какую-либо реальную выгоду, плюс их обычно намного труднее читать, поэтому люди публикуют такие вещи, как zoitz.com / archives / 13 о perl и относиться к нему как к языку только для записи, в отличие от awk. Я все еще хотел бы видеть Perl, эквивалентный этому, хотя
Эд Мортон

13

С grep -o, вы должны будете точно соответствовать тому, что вы хотите извлечь. Поскольку вы не хотите извлекать proto=строку, вам не следует сопоставлять ее.

Расширенное регулярное выражение, которое будет соответствовать tcpили udpпосле косой черты и некоторой непустой буквенно-цифровой строки:

(tcp|udp)/[[:alnum:]]+

Применяя это к вашим данным:

$ grep -E -o '(tcp|udp)/[[:alnum:]]+' file
tcp/http
tcp/https
udp/dns

Чтобы убедиться, что мы делаем это только в строках, начинающихся со строки proto=:

grep '^proto=' file | grep -E -o '(tcp|udp)/[[:alnum:]]+'

С помощью sedудаления всего до первого =и после первого пустого символа:

$ sed 's/^[^=]*=//; s/[[:blank:]].*//' file
tcp/http
tcp/https
udp/dns

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

sed -n '/^proto=/{ s/^[^=]*=//; s/[[:blank:]].*//; p; }' file

Здесь мы подавляем вывод по умолчанию с помощью -nопции, а затем мы запускаем подстановки и явный вывод строки, только если строка совпадает ^proto=.


Используя awkразделитель полей по умолчанию, а затем разделив первое поле =и напечатав его второй бит:

$ awk '{ split($1, a, "="); print a[2] }' file
tcp/http
tcp/https
udp/dns

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

awk '/^proto=/ { split($1, a, "="); print a[2] }' file

10

Если вы используете GNU grep (для -Pопции), вы можете использовать:

$ grep -oP 'proto=\K[^ ]*' file
tcp/http
tcp/https
udp/dns

Здесь мы сопоставляем proto=строку, чтобы убедиться, что мы извлекаем правильный столбец, но затем отбрасываем ее из вывода с \Kфлагом.

Выше предполагается, что столбцы разделены пробелом. Если табуляция также является допустимым разделителем, вы должны использовать \Sдля сопоставления непробельные символы, поэтому команда будет:

grep -oP 'proto=\K\S*' file

Если вы также хотите защитить от полей соответствия, где proto=есть подстрока, например thisisnotaproto=tcp/https, вы можете добавить границу слова \bследующим образом:

grep -oP '\bproto=\K\S*' file

1
Вы можете улучшить это, просто написав grep -oP 'proto=\K\S+'. За пробелом proto=tcp/httpможет следовать табуляция вместо пробелов, и в \Sотличие от этого [^ ]будет соответствовать любой непробельный символ.
Мосви

@mosvy: Это хорошее предложение, спасибо.
user000001

1
Во всяком случае, -oэто тоже GNUism. -Pподдерживается только GNU, grepесли он создан с поддержкой PCRE (необязательно во время сборки).
Стефан

6

Использование awk:

awk '$1 ~ "proto" { sub(/proto=/, ""); print $1 }' input

$1 ~ "proto"будет гарантировать, что мы protoдействуем только в строках с первым столбцом

sub(/proto=/, "")удалит proto=из ввода

print $1 печатает оставшийся столбец


$ awk '$1 ~ "proto" { sub(/proto=/, ""); print $1 }' input
tcp/http
tcp/https
udp/dns

3

Код игры в гольф на grepрешениях

grep -Po "..p/[^ ]+" file

или даже

grep -Po "..p/\S+" file


2

Просто еще одно grepрешение:

grep -o '[^=/]\+/[^ ]\+' file

И аналогичный с sedпечатью только сопоставленной захваченной группы:

sed -n 's/.*=\([^/]\+\/[^ ]\+\).*/\1/p' file

1

Другой awkподход:

$ awk -F'[= ]' '/=(tc|ud)p/{print $2}' file
tcp/http
tcp/https
udp/dns

Это установит в качестве разделителя полей awk либо =пробел, либо. Затем, если строка соответствует a =, затем либо, udлибо tcпосле a p, выведите 2-е поле.

Другой sedподход (не переносимый на все версии sed, но работает с GNU sed):

$ sed -En 's/^proto=(\S+).*/\1/p' file 
tcp/http
tcp/https
udp/dns

Значения -n«не печатать» и -Eвключает расширенные регулярные выражения, которые дают нам \S«непробельные символы», +«один или несколько» и круглые скобки для захвата. Наконец, /pв конце команда sed будет печатать строку только в том случае, если операция была успешной, поэтому, если было найдено совпадение для оператора подстановки.

И, perl один:

$ perl -nle '/^proto=(\S+)/ && print $1' file 
tcp/http
tcp/https
udp/dns

Это -nозначает «читать входной файл построчно и применять скрипт, заданный -eдля каждой строки». -lДобавляет символ новой строки к каждому printвызову (и удаляет покидающих перевода строки из входного). Сам скрипт напечатает самый длинный отрезок непробельных символов, найденных после a proto=.


1
-Eстановится все более портативным, но \Sэто не так. [^[:space:]]это более переносимый эквивалент.
Стефан

1

Вот еще одно довольно простое решение:

grep -o "[tc,ud]*p\\/.*  "   INPUTFile.txt  |   awk '{print $1}'

Ваш grepничего не соответствует. [tc,ud]\*\\/.*выглядит для одного вхождения либо t, или c, или , ,или , uили d, а затем буквального *характер, то pобратная косая черта. Вы, наверное, имели в виду grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Но, если вы используете AWK, вы можете также сделать все это в AWK: awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
Terdon

Кто-то изменил мой оригинал, перед звездой был дополнительный Backslash, который я только что удалил, сэр.
mkzia

Спасибо за редактирование, но я боюсь, что это работает только случайно. Как я уже объяснял ранее, [tc,ud]pозначает «один из t, c, ,, uили dсопровождаемый p. Так что это соответствует здесь только потому , что tcpесть cpи udpесть dp. Но это будет также соответствовать ,pили и tpт.д. Кроме того , теперь, когда у вас есть *, это будет соответствовать , pppа также ( *означает «0 или более» , так что будет соответствовать , даже если он не соответствует) вы не хотите , класс символов (. [ ]), что вы хотите , это группа: (tc|ud)(использование с -Eфлагом grep.) Кроме того , .*делает его соответствовать всей линии.
Terdon

1
@Jesse_b: Хотя mkzia технически не является «Новым участником», они неопытные пользователи, о чем свидетельствует тот факт, что они не использовали форматирование кода для своей команды. И все же они были достаточно умны, чтобы печатать, \*чтобы первые *в их команде отображались как *, а не как курсивная уценка. Когда вы помещаете команду в кодовый формат, вы вызываете \перед *появлением (что приводит к сбою команды). Когда вы редактируете посты других людей, пожалуйста, следите за изменением внешнего вида поста, как это.
G-Man говорит: «Восстанови Монику»

@terdon: (1) Нет, на самом деле это не будет совпадать ppp. Конечно , вы правы , что это будет соответствовать ,pили  tp- или uucp, ttp, cutp, ductpили d,up.
G-Man говорит: «Восстанови Монику»


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.