
Temos um arquivo de log que costumo acompanhar ao vivo com tail e uso grep para filtrar as linhas nas quais estou interessado. No entanto, as linhas contêm muitos dados nos quais nem sempre estou interessado, mas têm sido difíceis para mim. analisar para que eu veja apenas as partes da linha que desejo. O formato de cada entrada de linha é principalmente uma lista de tags e os dados (às vezes contendo espaços) entre aspas. Aqui estão alguns exemplos de linhas de registro (higienizadas):
2017:11:29-11:29:56 filter-1 httpproxy[3194]: id="0001" severity="info" sys="SecureWeb" sub="http" name="http access" action="pass" method="CONNECT" srcip="10.11.12.13" dstip="14.3.1.4" user="" group="" ad_domain="" statuscode="200" cached="0" profile="REF_HttPro1234 (Campus2)" filteraction="REF_HttStu (Allow Policy)" size="6518" request="0x915a3e00" url="https://website.net/" referer="" error="" authtime="0" dnstime="1" cattime="73" avscantime="0" fullreqtime="61576999" device="0" auth="6" ua="" exceptions="" category="9998" reputation="unverified" categoryname="Uncategorized" country="United States" application="krux" app-id="826"
2017:11:29-11:29:56 filter-1 httpproxy[3194]: id="0001" severity="info" sys="SecureWeb" sub="http" name="http access" action="pass" method="GET" srcip="10.13.14.15" dstip="154.6.75.10" user="" group="" ad_domain="" statuscode="200" cached="0" profile="REF_HttPro1235 (Campus1)" filteraction="REF_HttStu (Allow Policy)" size="3161" request="0x6b4d5610" url="http://host.com/mini_banner.png" referer="http://www.web.com/computers.htm" error="" authtime="0" dnstime="0" cattime="64" avscantime="848" fullreqtime="50046" device="0" auth="6" ua="Mozilla/5.0 (X11; CrOS x86_64 9765.85.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.123 Safari/537.36" exceptions="" category="111" reputation="trusted" categoryname="Education/Reference" sandbox="-" content-type="image/png"
Uma coisa a notar é que nem todas as tags estão presentes em todas as linhas. Por exemplo, application e app-id estão presentes na primeira linha, mas não na segunda.
Usando as linhas acima como exemplo de entrada, um exemplo do que eu gostaria de ter como saída seria mostrar apenas as tags srcip, categoryname e url nessa ordem. A saída desejada seria algo assim:
10.11.12.13 Uncategorized https://website.net/
10.13.14.15 Education/Reference http://host.com/mini_banner.png
Estou procurando uma solução que seja facilmente adaptável para que eu possa ajustar rapidamente quais tags são exibidas.
Responder1
Seus dados são altamente estruturados comochave = "valor", para que você possa escrever um pequeno script de shell usando gnu awk que usa como argumento uma lista de nomes de chaves e apenas imprime esses valores. Por exemplo, myscript
:
#!/bin/bash
awk -v lhs="$*" '
BEGIN{ FPAT = "[a-z-]*=\"[^\"]*\""
nwant = split(lhs,want)
}
{ for(i=1;i<=NF;i++){
start = match($i,/([a-z-]*)="([^"]*)"/,a)
key[a[1]] = a[2]
}
for(i=1;i<=nwant;i++){printf "%s ",key[want[i]]; key[want[i]] = ""}
printf "\n"
}'
que você chama como myscript srcip categoryname url
. Isso define a variável awk lhs
para os argumentos como uma única string, que é dividida em um array want
no início. As linhas são divididas pelo awk em campos que correspondem ao padrãochave = "valor"usando a FPAT
variável interna.
Em cada linha, para cada campo dividimos match()
em 2 grupos capturados, para a chave e para a parte entre aspas duplas. Eles são colocados por awk em array a
e os salvamos em um array associativo key
indexado pela string-chave.
Então, para cada chave desejada, imprimimos o valor e limpamos para a próxima linha (caso essa linha não possua esta chave). Obviamente, isso pressupõe que todos os dados tenham a estrutura necessária e precisarão de alterações para manipular (") dentro do valor ou chaves com caracteres não alfabéticos.
Versões do gnu awk (gawk) anteriores à 4.0 não possuem o FPAT
recurso interno para dividir a linha em campos que correspondam a um padrão, então você deve fazer isso sozinho:
#!/bin/bash
awk -v lhs="$*" '
BEGIN{ nwant = split(lhs,want) }
{ input = $0
while(match(input,"[a-z-]*=\"[^\"]*\"")>0){
field = substr(input,RSTART,RLENGTH)
input = substr(input,RSTART+RLENGTH)
start = match(field,/([a-z-]*)="([^"]*)"/,a)
key[a[1]] = a[2]
}
for(i=1;i<=nwant;i++){printf "%s ",key[want[i]]; key[want[i]] = ""}
printf "\n"
}'
Obviamente, você poderia combinar as duas chamadas de jogo em uma, mas isso mostra a diferença com o original.
Responder2
Usando (compatível com POSIX) sed
...
sed 's/.* srcip="\([^"]*\)" .* url="\([^"]*\)" .* categoryname="\([^"]*\)" .*/\1 \3 \2/' logfile
Nada sofisticado aqui, apenas encontre as chaves e coloque os valores entre parênteses, \(..\)
o que permite que sejam usados como referências anteriores. Em seguida, substituímos a string apenas pelas referências anteriores, delimitadas por espaço, ordenadas de acordo com sua necessidade: \1 \3 \2
.
Saída:
10.11.12.13 Uncategorized https://website.net/
10.13.14.15 Education/Reference http://host.com/mini_banner.png
Se os logs contiverem strings que não possuem todas essas chaves, você poderá usar:
sed -n 's/.* srcip="\([^"]*\)" .* url="\([^"]*\)" .* categoryname="\([^"]*\)" .*/\1 \3 \2/p' logfile
Isso imprimirá apenas linhas que correspondam ao padrão.
E, claro, se você quiser usá-los em streaming, basta remover o nome do arquivo e fazer[something sending logs to stdout] | sed ...