Grep匹配并提取


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/httptcp/httpsudp/dns

到目前为止,我已经尝试过了,grep -o 'proto=[^/]*/'但是只能提取as的值proto=tcp/



这是一项工作sedawk或者perl不是grep
OrangeDog

Answers:


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放在盒子里也无法安装的话,Perl肯定不会更容易使用,这是我多年来经常要处理的事情。AWK,另一方面是一个强制性的效用,所以它们总是出现在UNIX安装,只是像sed,grep,排序等
埃德莫顿

@EdMorton是的,尽管我个人从未遇到过默认不包含perl的发行版。复杂awksed脚本通常更简单,perl因为它本质上是它们的超集,并具有用于常见任务的其他功能。
OrangeDog

@OrangeDog没有人应该写过比s/old/new/gsed 更复杂的sed脚本,而sed也不是awk,所以我们将其搁置一旁。我完全不同意复杂的awk脚本在perl中更简单。当然,它们可能更简短,但简洁性不是软件的理想属性,简洁是其难得的东西,它们几乎没有真正的好处,而且它们通常更难阅读,这就是人们发布zoitz.com之类的原因的原因。 / archives / 13关于perl,并将其称为只写语言,与awk不同。我仍然希望看到与此等效的perl
Ed Morton

13

使用grep -o,您将必须完全匹配要提取的内容。由于您不想提取proto=字符串,因此不应该匹配它。

扩展正则表达式将匹配tcpudp后跟一个斜杠和一些非空的字母数字字符串,它是

(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

为确保只在以string开头的行上执行此操作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

为确保只在以string开头的行上执行此操作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
您可以通过编写just来改善这一点grep -oP 'proto=\K\S+'。该proto=tcp/http可随后代替空格选项卡,\S不同于[^ ]将匹配任何非空格字符。
mosvy

@mosvy:很好的建议,谢谢。
user000001

1
无论如何,-o也是GNUism。-P只有grep使用PCRE支持构建的GNU才支持(在构建时是可选的)。
斯特凡Chazelas

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的字段分隔符设置为=或一个空格。然后,如果线路相匹配的=,那么无论udtc后跟一个p,打印第二字段。

另一种sed方法(不适用于所有版本的sed,但可以与GNU一起使用sed):

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

-n意思是“不打印”和-E能够扩展正则表达式这给我们\S的“非空白”,+为“一个或多个”以及用于捕获括号。最后,/p仅当操作成功后,如果替换操作符匹配,最后at才会使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:]]是更便携的等效项。
斯特凡Chazelas

1

这是另一个很简单的解决方案:

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

grep没有任何匹配项。[tc,ud]\*\\/.*长相对于一个的任一发生t,或c,或,ud,随后文字*字符,则p和反斜杠。你可能是说grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'。不过,如果你使用AWK,你不妨做AWK整个事情:awk -F'[= ]' '/(tc|ud)p/{print $2}' file
terdon

有人修改了我的原著,在星号之前有一个额外的反斜杠,我刚刚删除了先生。
mkzia

感谢您的编辑,但恐怕这只是偶然。正如我之前解释的,[tc,ud]p意思是“之一tc,ud后跟一个p。所以在这里比赛,只是因为tcpcpudpdp,但它也将匹配,ptp等。此外,现在你有*,它会匹配ppp以及(在*表示“0或更多”,所以它会匹配,即使它不匹配),你不想要一个字符类(。 [ ]),你想要的是一组:(tc|ud)(使用-E的标志grep。)此外,.*使其匹配整行。
terdon

1
@Jesse_b:尽管从技术上讲mkzia并不是“新贡献者”,但是他们并不是一个经验丰富的用户,事​​实证明他们没有为命令使用代码格式。然而他们足够聪明,键入\*率先拿到*他们的命令显示为*而不是斜体降价。当您将命令放入代码格式时,导致出现在\之前*(因此导致命令失败)。在编辑其他人的帖子时,请当心更改帖子外观的方法。
G-Man说'Resstate Monica''Jun

@terdon:(1)不,实际上不匹配ppp。当然,你是正确的,它将匹配,p或  tp-或uucpttpcutpductpd,up
G-Man说'Resstate Monica''Jun


0
cat file| cut -f1 -d' '| cut -f2 -d'='
tcp/http
tcp/https
udp/dns

剪切选项:

  • -f -场
  • -d -分度
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.