Como eu extraio múltiplas cadeias que são delimitadas por vírgulas de um arquivo de log?

1

Eu preciso extrair certas strings de um arquivo de log, embora as strings NÃO estejam em um campo / coluna padronizado. Por exemplo:

date="2017-01-03 08:30:02 -0500",fac=f_kernel_ipfilter,area=a_general_area,type=t_attack,pri=p_major,hostname=hostname,category=policy_violation,event="ACL deny",attackip=1.1.1.1,attackzone=internal,app_risk=low,app_categories=remote-admin,netsessid=c550e586ba75a,src_geo=US,srcip=1.1.1.1,srcport=38256,srczone=internal,protocol=6,dst_geo=US,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All,reason="Traffic denied by policy.",application=SSH

Eu quero pegar srcip, srczone, protocolo, dstip, dstzone, dstport e rule_name. Atualmente, uso uma correspondência de perl lazy para remover os campos OUT que não quero. Existe uma maneira de apenas pegar essas 8 strings e os dados dentro das vírgulas como ,dstport=80, , independentemente da posição no arquivo de log? muitas posições de entradas diferentes para os mesmos dados, o que torna isso difícil.

    
por Steve 03.01.2017 / 15:47

7 respostas

4

Aqui está uma abordagem rápida e suja usando perl :

$ perl -F, -lane '@l = grep {/srcip|srczone|protocol|dstip|dstzone|dstport|rule_name/} @F; 
                  print join ",",@l' file 
srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All

O -a faz o perl agir como o awk e dividir suas linhas de entrada no caractere dado por -F , salvando-as como elementos da matriz @F . Então, nós grep da matriz e manteremos os elementos correspondentes às suas palavras de destino na matriz @l e, finalmente, imprimiremos @l unidos com vírgulas.

Observe que isso falhará se algum dos seus padrões puder ser subpadrão (digamos que você tenha foo=bar e foobar=baz ).

Para listas mais longas de padrões de destino (supondo que você não queira escrever um script real), você pode armazená-los em uma matriz e juntá-los com | para fazer o regex para grep. E adicionando \b em torno de cada padrão que você protege dos subpadrões correspondentes. Se também removermos a matriz temporária desnecessária, obteremos:

$ perl -F, -lane '
    BEGIN{
     $pat="\b" . join "\b|",qw(srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=)
    } print join ",",grep {/$pat/}@F' file 
srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All

Our resident expert said it cannot be done in the bourne shell regardless of sed awk or [. . .. ] From your commant

Desculpe, mas isso é absurdo. Aqui está uma (muitas) maneiras de fazer isso em cada uma dessas ferramentas:

  1. Bourne (novamente) shell. Não use isso, só mostro para demonstrar que é possível.

    $ pat=(srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=); 
    $ o=""; while IFS=, read -a fields; do 
                for f in "${fields[@]}"; do 
                    for pat in "${pat[@]}"; do 
                        [[ $f =~ $pat ]] && o="$f,$o"
                    done 
                done
               done < file ; echo ${o%,}
    
  2. Awk

    Salve seus padrões de destino em um arquivo:

    $ cat patterns
    srcip
    srczone
    protocol
    dstip
    dstzone
    dstport
    rule_name
    

    Então:

    $ awk -F, '(NR==FNR){ 
                    pat[$0]++; 
                    next;
                } 
                {
                    for(i=1;i<=NF;i++){ 
                        split($i,a,"="); 
                        if(a[1] in pat){
                            printf "%s=%s,",a[1],a[2]
                        }
                    }
                    print ""
                }' patterns file | sed 's/,$//'
    srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All
    
  3. sed (e shell)

    $ pat=(srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=);
    $ for p in ${pat[@]}; do 
        sed -E "s/.*($p[^,]*).*//" file; done | 
            sed ':a;N;$!ba;s/\n/,/g'
    srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstzone=external,dstport=80,rule_name=Deny_All
    
  4. Bourne shell (ou qualquer shell POSIX) + sed (como para 1., não faça isso, é possível, mas bobo)

    $ set srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=
    $ for f in "$@"; do sed "s/.*\($f[^,]*\).*//" file; done | sed ':a;N;$!ba;s/\n/,/g'
    srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstzone=external,dstport=80,rule_name=Deny_All
    
por 03.01.2017 / 16:07
1

Uma solução com o GNU awk :

gawk -v OFS= -v FPAT=',(srcip|srczone|protocol|dstip|dstzone|dstport|rule_name)=[^,]*' -e 'NF > 0 { $1=$1; print }'

Aqui, estou usando um recurso específico para o GNU awk : com a variável FPAT , estou especificando o formato dos campos com um regex, para que cada parte da linha correspondente à regex seja atribuída codificar%. Em seguida, atribuo $1...$n a $1 para que $1 seja reconstruído a partir do zero usando apenas $0 .

    
por 03.01.2017 / 16:36
1

Estou um pouco atrasado, mas ofereço uma sugestão - esse tipo de dado é bastante adequado para map ing em um hash:

#!/usr/bin/env perl

use strict;
use warnings;
#for debugging - can be removed;
use Data::Dumper;

my @fields = qw ( srcip srczone protocol dstip dstzone dstport rule_name );

#read STDIN or files specified on command line (just like grep/sed/awk)
while ( <> ) {

   #split commas
   #then read key-value pairs. 
   my %row = map { m/(.*)=(.*)/ } split /,/;
   #for debugging:
   print Dumper \%row;

   #print fields tab-separated and in order as above. 
   print join "\t", @row{@fields};
}

É um pouco mais difícil oneliner-ify porque você tem uma lista de campos para soletrar. Mas:

perl -lane -F, 'BEGIN { @k = qw ( srcip srczone protocol dstip dstzone dstport rule_name ) } %r = map { m/(.*)=(.*)/ } @F; print join "\t", @r{@k}'
    
por 19.01.2017 / 11:42
0

Coloque os padrões regex desejados em um arquivo e use egrep.

Seu arquivo (vamos chamá-lo filters.txt):

srcip=(.*?),
srczone=(.*?),
....

Seu comando:

grep -Eof filters.txt logfile.txt
    
por 03.01.2017 / 16:13
0

bash

IFS=, read -r -a fields <<< "$date"
results=()
for keyval in "${fields[@]}"; do 
    IFS='=' read -r key value <<< "$keyval"
    case $key in 
        srcip|srczone|protocol|dstip|dstzone|dstport|rule_name) results+=("$keyval")
    esac
done
(IFS=,; echo "${results[*]}")
srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All
    
por 04.01.2017 / 16:46
0

Solução

Você pode usar o comando cut . No seu caso, você pode dar três parâmetros:

  • -d ',' : representa o delimitador da sua string. No seu caso, é o , . Você pode substituir o delimitador pelo que você quiser.
  • -f 4,7,8 : representa a parte da string que você deseja obter (separada por vírgula). Neste caso, é a quarta, sétima e oitava parte. Você pode especificar um intervalo usando este formulário 1-8 , por exemplo.
  • file.ext : este é o seu arquivo de log.

Exemplos de uso

Digamos que seu arquivo de log seja file.log (nome muito criativo):

  • cut -d ',' -f 1-5 file.log : isso lhe dará date="2017-01-03 08:30:02 -0500" fac=f_kernel_ipfilter,area=a_general_area

  • cut -d ',' -f 3,5,6 file.log : isso lhe dará fac=f_kernel_ipfilter,type=t_attack,pri=p_major

  • cut -d ',' -f 4 file.log : isso lhe dará type=t_attack

Nota

cut passará por todas as linhas do seu arquivo. Se você quiser pular as linhas que não contêm o delimitador especificado, use a opção -s .

    
por 03.01.2017 / 16:15
0

Eu tentei apenas com sed

sed -r 'h;
s/.*(srcip=[^,]*).*//; H; g; 
s/.*(srczone=[^,]*).*//; H; g; 
s/.*(protocol=[^,]*).*//; H; g; 
s/.*(dstip=[^,]*).*//; H; g; 
s/.*(dstport=[^,]*).*//; H; g; 
s/.*(dstzone=[^,]*).*//; H; g; 
s/.*(rule_name=[^,]*).*//; H; g; 
s/.*\n//M; 
s/\n/,/g;
' file
    
por 18.01.2017 / 20:22