Correspondência e extração do Grep


10

Eu tenho um arquivo que contém linhas como

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

Eu preciso extrair o valor de proto que é tcp/http, tcp/https, udp/dns.

Até agora eu tentei isso, grep -o 'proto=[^/]*/'mas apenas capaz de extrair o valor como proto=tcp/.



Este é um trabalho para sed, awkou perlnão grep.
OrangeDog

Respostas:


1

Supondo que isso esteja relacionado à sua pergunta anterior , você está seguindo o caminho errado. Em vez de tentar reunir pedaços de scripts que meio que fazem o que você quer na maioria das vezes e que precisam de um script completamente diferente toda vez que você precisa fazer algo que seja um pouquinho diferente, basta criar um script que possa analisar seu arquivo de entrada em uma matriz ( f[]abaixo) que mapeia seus nomes de campo (tags) para seus valores e, em seguida, você pode fazer o que quiser com o resultado, por exemplo, considerando esse arquivo de entrada da sua pergunta anterior:

$ 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)"""

podemos escrever um script awk que cria uma matriz dos valores indexados por seus nomes / tags:

$ 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
}

e como você pode fazer o que quiser com seus dados, basta referenciá-los pelos nomes dos campos, por exemplo, usando o GNU awk -epara facilitar a mistura de um script em um arquivo com um script de linha de comando:

$ 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
Isto é incrível, obrigado soo muito :)
user356831

Para esse tipo de trabalho, perlpode ser mais fácil de usar.
OrangeDog

1
@OrangeDog, por que você acha isso? Na verdade, eu gostaria de ver o equivalente em perl, se você não se importar em postar uma resposta desse tipo. Definitivamente, o Perl não será mais fácil de usar se eu não o tiver na minha caixa e não puder instalá-lo, o que é algo com o qual eu tenho que lidar com frequência ao longo dos anos. Awk, por outro lado é um utilitário obrigatório e por isso está sempre presente em instalações UNIX, assim como sed, grep, tipo, etc.
Ed Morton

@ Edmorton true, embora eu nunca tenha encontrado pessoalmente uma distribuição em que o perl não foi incluído por padrão. Geralmente, os scripts awke complexos sedsão mais simples perlporque são essencialmente um superconjunto deles, com recursos adicionais para tarefas comuns.
OrangeDog

@OrangeDog ninguém deve escrever um script sed que seja mais complicado do que s/old/new/gsed e não seja um awk, então vamos deixar isso de lado. Eu discordo totalmente de que scripts complexos do awk são mais simples em perl. Eles podem ser mais breves, é claro, mas a brevidade não é um atributo desejável do software, a concisão é e é extremamente raro que eles tenham algum benefício real, além de serem geralmente muito mais difíceis de ler, e é por isso que as pessoas postam coisas como zoitz.com / archives / 13 sobre perl e se referem a ele como uma linguagem somente de gravação, diferente do awk. Eu ainda gostaria de ver um perl equivalente a isso, porém
Ed Morton

13

Com grep -o, você terá que corresponder exatamente ao que deseja extrair. Como você não deseja extrair a proto=sequência, não deve correspondê-la.

Uma expressão regular estendida que corresponderia a uma barra tcpou a ela seria udpseguida por uma barra e alguma sequência alfanumérica não vazia é

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

Aplicando isso aos seus dados:

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

Para garantir que apenas façamos isso nas linhas que começam com a sequência proto=:

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

Com sed, removendo tudo antes do primeiro =e depois do primeiro caractere em branco:

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

Para garantir que apenas façamos isso nas linhas que começam com a sequência proto=, você pode inserir a mesma etapa de pré-processamento grepcomo acima, ou usar

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

Aqui, suprimimos a saída padrão com a -nopção e, em seguida, acionamos as substituições e uma impressão explícita da linha somente se a linha corresponder^proto= .


Com awk, usando o separador de campo padrão e, em seguida, divida o primeiro campo =e imprima o segundo bit:

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

Para garantir que apenas façamos isso nas linhas que começam com a sequência proto=, você pode inserir a mesma etapa de pré-processamento grepcomo acima, ou usar

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

10

Se você estiver no GNU grep (para a -Popção), poderá usar:

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

Aqui, correspondemos à proto=string, para garantir que estamos extraindo a coluna correta, mas a descartamos da saída com o\K sinalizador

O acima pressupõe que as colunas são separadas por espaço. Se as guias também forem um separador válido, você usaria \Spara corresponder aos caracteres que não são de espaço em branco; portanto, o comando seria:

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

Se você também deseja se proteger contra os campos de correspondência em que proto=há uma substring, como a thisisnotaproto=tcp/https, adicione o limite de palavras da seguinte \bforma:

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

1
Você pode melhorar isso escrevendo apenas grep -oP 'proto=\K\S+'. O proto=tcp/httppode ser seguido por um separador em vez de espaços, e \Sao contrário [^ ]irá corresponder a qualquer caractere não-espaço.
mosvy

@mosvy: Essa é uma boa sugestão, obrigado.
user000001

1
Enfim, -oé um GNUism também. -Pé suportado apenas pelo GNU grepse construído com suporte a PCRE (opcional no momento da construção).
Stéphane Chazelas

6

Usando awk:

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

$1 ~ "proto"garantirá que apenas tomemos medidas de acordo com protoa primeira coluna

sub(/proto=/, "")irá remover proto=da entrada

print $1 imprime a coluna restante


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

3

Código de golfe nas grepsoluções

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

ou mesmo

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


2

Apenas outra grepsolução:

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

E um similar com a sedimpressão apenas do grupo capturado correspondente:

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

1

Outra awkabordagem:

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

Isso definirá o separador de campos do awk como um =ou um espaço. Então, se a linha corresponder a =, então, udou tcseguida por ump , imprima o 2º campo a.

Outra sedabordagem (não é portátil para todas as versões do sed, mas funciona com o GNU sed):

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

Os -nmeios "não imprimir" e -Epermitem expressões regulares estendidas que nos fornecem \S"espaço em branco", +"um ou mais" e os parênteses para captura. finalmente, o/p no final, o sed imprimirá uma linha apenas se a operação for bem-sucedida e, se houver uma correspondência para o operador de substituição.

E um perl:

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

Os -nmeios "leem o arquivo de entrada linha por linha e aplicam o script fornecido por -ecada linha". O -lacrescenta uma nova linha para cada printchamada (e remove novas linhas que saem a partir da entrada). O script em si imprimirá a maior extensão de caracteres que não sejam espaços em branco encontrados após a proto=.


1
-Eestá ficando cada vez mais portátil, mas \Snão está. [^[:space:]]é um equivalente mais portátil.
Stéphane Chazelas

1

Aqui está outra solução bastante fácil:

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

O seu grepnão corresponde a nada. [tc,ud]\*\\/.*procura uma ocorrência de t, ou c, ou ,ou uou d, seguida por um *caractere literal , depois a pe uma barra invertida. Você provavelmente quis dizer grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Mas então, se você estiver usando awk, assim como você pode fazer a coisa toda em awk: awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
terdon

Alguém modificou meu original, havia uma barra invertida extra antes da estrela, que acabei de remover Sir.
Mkzia # 8/19

Obrigado pela edição, mas temo que só funcione por acaso. Como expliquei antes, [tc,ud]psignifica "um dos t, c, ,, uou dseguido por um p. Então, ele corresponde aqui apenas porque tcptem cpe udptem dp. Mas também corresponderia ,pou tpetc Além disso, agora que você tem o *, ele irá corresponder pppbem (o *meios "0 ou mais" para que ele irá corresponder, mesmo quando ele não corresponde) você não quer uma classe de caracteres (. [ ]), o que você quer é um grupo: (tc|ud)(uso com a -Ebandeira de grep.) Além disso, o .*torna coincidir com a linha inteira.
terdon

1
@Jesse_b: Embora o mkzia não seja tecnicamente um “Novo colaborador”, eles são um usuário inexperiente, como evidenciado pelo fato de que eles não usaram a formatação de código para seu comando. E, no entanto, eram espertos o suficiente para digitar \*para que o primeiro *comando aparecesse como um * e não como uma marcação em itálico. Quando você coloca o comando no formato de código, faz com que o \antes do *apareça (causando falha no comando). Quando você edita as postagens de outras pessoas, fique atento para alterar a aparência da postagem assim.
G-Man diz que 'Reinstate Monica'

@terdon: (1) Não, na verdade não vai corresponder ppp. É claro que você está certo de que ele irá corresponder ,pou  tp- ou uucp, ttp, cutp, ductpou d,up.
G-Man diz que 'Reinstate Monica'


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

opções de corte:

  • -f - campo
  • -d - delímetro
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.