Como usar a linha Unindented como Record Separate no awk cli

2

Eu tenho um arquivo de log que se parece com:

2016-05-31 09:54:36 (16667) heritage_w?
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=290
  #accesses 3,435 (#welcome 415) since 03/07/2012
2016-05-31 09:54:41 (16677) heritage_w?w=
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?
  #accesses 3,436 (#welcome 416) since 03/07/2012
2016-06-01 04:07:06 (22190) heritage_w?m=MOD_IND;i=88
  From: ubunzeus
  User: user2 (wizard)
  Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=88
  #accesses 3,623 (#welcome 441) since 03/07/2012    
2016-06-01 04:07:38 (22255) heritage_w?m=MOD_FAM;i=28;ip=88
  From: ubunzeus
  User: user2 (wizard)
  Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?m=MOD_IND;i=88
  #accesses 3,624 (#welcome 441) since 03/07/2012

Estou tentando fazer com que as linhas recuadas como o Separador de Registros RS .

Usando código semelhante a:

$ gawk 'BEGIN{RS="^2016"}; /user1/ {print}'

Espero imprimir apenas os registros com "user1".

Atualmente, a linha de comando está imprimindo todo o arquivo ... todos os registros.

Esta é a saída esperada:

2016-05-31 09:54:36 (16667) heritage_w?
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=290
  #accesses 3,435 (#welcome 415) since 03/07/2012
2016-05-31 09:54:41 (16677) heritage_w?w=
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?
  #accesses 3,436 (#welcome 416) since 03/07/2012

Esclarecimentos sobre os detalhes desta questão

Aceitei a resposta John1024 , que permite a seleção dos registros necessários. No entanto, espero que alguém acabe tendo informações sobre como usar o recurso regex específico como a variável Record Separator (RS), que nesse caso seria a linha sem recuo.

Eu peguei a string que estava usando como descrito por John1024 e usei a expressão regex não branca em várias combinações, mas não funciona.

As linhas que eu uso que não filtram adequadamente os registros são:

$ gawk 'BEGIN{RS='\n\S'}; /user1/ {print}' event.log
$ gawk 'BEGIN{RS='\S'}; /user1/ {print}' event.log
$ gawk 'BEGIN{RS="\n^\S"}; /user1/ {print}' event.log
$ gawk 'BEGIN{RS="^\S"}; /user1/ {print}' event.log

Todas as combinações acima exibem todos os registros. Tenho certeza de que aspas simples '^\S' estão usando os charecters reais e não o significado de escape. As duplas cotadas "^\S" estão dando a mensagem de erro:

gawk: cmd. line:1: warning: escape sequence '\S' treated as plain 'S'

Eu sou capaz de verificar se "\ S" regexará os caracteres da primeira coluna não brancos. Mostra on-line as linhas sem recuo:

$ egrep "^\S" event.log

Saída do cli acima:

2016-05-31 09:54:36 (16667) heritage_w?
2016-05-31 09:54:41 (16677) heritage_w?w=
2016-06-01 04:07:06 (22190) heritage_w?m=MOD_IND;i=88
2016-06-01 04:07:38 (22255) heritage_w?m=MOD_FAM;i=28;ip=88

Com a ajuda da resposta aceita ... o código de nova linha e endereçando o erro de caractere de escape usando uma barra invertida dupla, o seguinte filtra os registros desejados:

$ gawk 'BEGIN{RS="\n\S"}; /user1/ {print}' event.log
    
por L. D. James 01.06.2016 / 18:43

4 respostas

2

Tente:

$ gawk 'BEGIN{RS="\n2016"}; /user1/ {print}' input

Isso produz a saída;

2016-05-31 09:54:36 (16667) heritage_w?
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=290
  #accesses 3,435 (#welcome 415) since 03/07/2012
-05-31 09:54:41 (16677) heritage_w?w=
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?
  #accesses 3,436 (#welcome 416) since 03/07/2012

Observe que o segundo registro não possui o 2016 inicial. Isso é. é claro, porque 2016 se tornou parte do separador de registros. Se você deseja restaurar essa parte antes de iniciar qualquer processamento de um registro:

gawk 'BEGIN{RS="\n2016"} NR>1{$0="2016" $0;} /user1/ {print}' input

Melhoria

Esta versão restaura o texto conforme necessário para o início de cada linha:

gawk '{$0=substr(last,2)$0;} /user1/{print} {last=RT}' RS='\n[^[:space:]]' input

Como funciona:

  • {$0=substr(last,2)$0;} prepends para $0 o texto que foi removido pelo separador de registro. substr é usado para remover a nova linha anterior.

  • /user1/{print} imprime os registros nos quais estamos interessados.

  • {last=RT} salva o separador de registros real para que parte dele possa ser anexada ao próximo registro. RT é uma extensão GNU e não é suportada por outras versões do awk.

  • RS='\n[^[:space:]]' define o separador de registro como uma nova linha, seguido por qualquer não-espaço. Usar um regex como um separador de registro funciona com o GNU awk.

Exemplo:

$ gawk '{$0=substr(last,2)$0;} /user1/{print} {last=RT}' RS='\n[^[:space:]]' input
2016-05-31 09:54:36 (16667) heritage_w?
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=290
  #accesses 3,435 (#welcome 415) since 03/07/2012
2016-05-31 09:54:41 (16677) heritage_w?w=
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?
  #accesses 3,436 (#welcome 416) since 03/07/2012
    
por 01.06.2016 / 18:50
2

Aqui está uma estratégia ligeiramente diferente. Nós acumulamos cada linha recuada em um buffer de retenção. Quando uma linha não recuada é lida, chamamos uma função que imprime o buffer se contiver o padrão desejado e, em seguida, substitui o conteúdo do buffer pela nova linha de cabeçalho. Também precisamos chamar essa função quando o final do arquivo for atingido.

#!/usr/bin/awk -f
#   Select records from a file 
#   Each record header line is unindented and each record body line is indented
#   Written by PM 2Ring 2015.06.02

function ShowSelected()
{
    if (hold ~ /User: user1/)
        printf "%s", hold
    hold = $0 ORS
}

/^ /{hold = hold $0 ORS; next}

{ShowSelected()}

END{ShowSelected()}

Aqui está uma versão de uma linha:

awk 'function S(){if(h~/User: user1/)printf "%s",h; h=$0 ORS}; /^ /{h=h $0 ORS; next}; {S()};END{S()}'

Apenas por diversão, aqui está uma versão sed. Ele usa essencialmente o mesmo algoritmo.

sed '/^ /!bA;H;$bA;d;:A;x;/User: user1/!d'

Aqui está a mesma coisa, com comentários.

#!/bin/sed -f    
#   Select records from a file 
#   Each record header line is unindented and each record body line is indented
#   Written by PM 2Ring 2015.06.02

# If line doesn't start with a space, branch to the select & display routine
/^ /!bA

# Append pattern space (i.e., the current line) to the hold space
H

# If this is the last line, branch to the select & display routine
$bA

# Delete the pattern space and start the next cycle
d

# The select & display routine
:A

# Exchange the contents of the hold and pattern spaces
x

# Delete the pattern if it doesn't contain the regex /User: user1/
# if the pattern isn't deleted it will be printed
/User: user1/!d

Aqui está uma abordagem híbrida sed-awk, inspirada na idéia de Thor de usar sed para fazer um pré-processamento. Prefixamos cada linha não recuada com um caractere \xff e usamos isso como o separador de registro awk. Isso não funcionará corretamente se o arquivo de log usar esse caractere \xff , mas esperamos que não seja o caso. :)

<logfile sed 's/^[^ ]/\xff&/' | awk 'BEGIN{RS="\xff";ORS=""};/User: user1/'
    
por 01.06.2016 / 22:24
1

Eu pré-processaria o arquivo com, por exemplo, %código%. Então, para extrair a segunda linha de cada registro, faça algo assim:

<infile sed 's/^[^ ]/&\n/' | awk '{ print $2 }' RS= FS='\n'

Saída:

  From: ip68-8-49-100.sd.sd.cox.net
  From: ip68-8-49-100.sd.sd.cox.net
  From: ubunzeus
  From: ubunzeus

Editar - Como imprimir todos os registros em que sed contém $3 :

<infile sed '1!s/^[^ ]/\n&/' | awk '$3 ~ /user1/' RS= FS='\n'

Saída:

2016-05-31 09:54:36 (16667) heritage_w?                                
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=290
  #accesses 3,435 (#welcome 415) since 03/07/2012
2016-05-31 09:54:41 (16677) heritage_w?w=
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?
  #accesses 3,436 (#welcome 416) since 03/07/2012
    
por 01.06.2016 / 19:12
0

IMO, a maneira mais fácil é usar sed para transformar a entrada em registros separados por parágrafo (uma ou mais linhas em branco entre cada registro). Em outras palavras, pulando a primeira linha, insira uma nova linha antes de cada linha que não começar com um espaço em branco (espaço ou tabulação).

Em seguida, você pode apenas informar awk para usar duas ou mais novas linhas como o separador de registro de entrada (RS) com RS='\n\n+' .

BTW, não há necessidade de definir o separador de registro de saída (ORS) como o mesmo, a menos que você queira que a saída também esteja em parágrafos. Você não pediu por isso, então eu não incluí isso. Se é isso que você quer (por exemplo, porque você quer fazer algum processamento adicional na saída), adicione -v ORS='\n\n' às opções awk .

$ sed -e '2,$ s/^[^[:blank:]]/\n&/' ldjames.txt | 
    awk -v RS='\n\n+' '/user1/ {print}'
2016-05-31 09:54:36 (16667) heritage_w?
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?i=290
  #accesses 3,435 (#welcome 415) since 03/07/2012
2016-05-31 09:54:41 (16677) heritage_w?w=
  From: ip68-8-49-100.sd.sd.cox.net
  User: user1wizard (wizard)
  Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
  Referer: http://dbase.apollo3.com/heritage_w?
  #accesses 3,436 (#welcome 416) since 03/07/2012
    
por 02.06.2016 / 04:38