AWK: Como exibir corretamente uma coluna com várias palavras e entre aspas?

0

Eu uso o awk assim:

grep -i 'logged in' path-to-file | tail -n -10 | awk '{ print $6, "logged in on ",substr($2,1,8),$1"."; }' | sed 's/"//g'

Mas na coluna $6 é um "nickname" dos usuários, então às vezes é apenas uma coluna de uma palavra, mas às vezes tem mais de uma palavra.

2017-12-21 21: 54: 01.714540 Usuário # 41 nickname: "sarah the princes" nome de usuário: "guest" Endereço IP: 111111111, endereço UDP: udp logado.

Em vez de imprimir todo o apelido dos príncipes sarah, ele exibe apenas a primeira palavra que é sarah.

    
por Joey 21.12.2017 / 10:33

2 respostas

0

Você pode usar a função gsub() do awk para substituir todas as ocorrências de " e " (citações seguidas por espaço E espaço seguidos por uma aspa) por algum separador arbitrário, definir FS para esse separador e extrair o que você quer. Observe que, se você alterar FS, a numeração dos campos também será alterada. Você também precisará redefinir o FS de volta ao seu valor original para processar corretamente a próxima linha de entrada.

No seu caso, você também quer extrair alguns dados (data e hora) dos campos antes de alterar o FS.

por exemplo. se ./file contiver 5 linhas, cada uma cópia exata da linha de amostra fornecida por você:

$ grep -i 'logged in' ./file | tail | awk '
{ d=$1;
  t=$2; sub(/\..*/,"",t);

  FS="XXX";
  gsub(/" | "/,"XXX",$0);
  print $2,"logged in at", t, d;
  FS="[[:space:]]+"
}'
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21
sarah the princes logged in at 21:54:01 2017-12-21

Eu usei XXX como o separador de campo porque ele não aparece em nenhum lugar na entrada. Um caractere de tabulação teria funcionado tão bem para este exemplo, mas isso não teria demonstrado que os separadores de campo não precisam ser um único caractere - o que será importante se você não puder (ou não puder facilmente) determinar um único caractere que não é usado em nenhuma parte da entrada.

Fica mais complicado se você precisar extrair dados de campo de após os campos com aspas duplas (por exemplo, o endereço IP ou os campos da porta udp) - não é possível extraí-los antes do gsub porque você não pode ter certeza de qual será o número do seu campo. Eu estaria inclinado a usar perl neste ponto (ou talvez até sed como na resposta @ Wildcard), mas uma maneira de fazer isso com awk é expandir a expressão regular da chamada de função gsub para se adequar . por exemplo. substituindo o script awk por:

$ grep -i 'logged in' ./file | tail | awk '
{   d=$1;
    t=$2;
    sub(/\..*/,"",t);

    FS="XXX";
    gsub(/" | "|address: |, /,"XXX",$0);
    sub(/ .*/,"",$8);      # get rid of trailing junk after udp port

    print $2,"logged in at", t, d, "as" ,$4, "from", $6":"$8;

    FS="[[:space:]]+"
}'

produziria resultados assim:

sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp
sarah the princes logged in at 21:54:01 2017-12-21 as guest from 111111111:udp

Para completar, aqui está uma maneira de fazer isso em perl usando o módulo de núcleo perl Text::ParseWords :

#!/usr/bin/perl

use strict;
use Text::ParseWords;

my $keep=1;  # keep " chars in output.  set to 0 to strip them.

while(<>) {
  my @F = quotewords('\s+', $keep, $_);

  $F[1] =~ s/\..*//;  # strip decimal fraction from time field
  $F[10] =~ s/,//;    # strip trailing comma from IP address field

  # remember: perl array indices start at zero, not one.
  printf "%s logged in at %s %s as %s from %s:%s\n", @F[5,1,0,7,10,13];
}

Isso usa a função quotewords() de Text::Parsewords para dividir cada linha de entrada em campos (armazenados em uma matriz chamada @F ), faz algumas pequenas limpezas em alguns dos campos e, em seguida, imprime os campos necessários com printf .

Como um one-liner, seria escrito como:

grep -i 'logged in' ./file | tail | perl -MText::ParseWords -n -e '
  @F = quotewords(q/\s+/, 1, $_);
  $F[1] =~ s/\..*//;
  $F[10] =~ s/,//;
  printf "%s logged in at %s %s as %s from %s:%s\n", @F[5,1,0,7,10,13]'

Perceba como mudei '/s+' para q/\s+/ - perl tem ótimos operadores de cotação que pode ser usado para evitar a pergunta simples dentro do problema de aspas simples .

    
por 22.12.2017 / 05:43
0

Experimente o tamanho:

sed -En '
  /^(....-..-..) (..:..:..)[^:]*nickname: "?([^":]+)"? username:.*logged in.*$/ {
    s// logged in at  on ./p
  }
' path-to-file | tail -n 10
    
por 21.12.2017 / 22:30

Tags