Obtendo resposta errada da data até o awk

1

Estou tentando criar um script que possa transformar uma saída de um switch Cisco mostrando a última entrada e saída em uma interface para um formato útil. Isso é para saber quais interfaces não foram usadas, digamos, nos últimos 6 meses. A Cisco apenas diz que uma última entrada de interfaces foi 10w4d .

O que eu vim até agora é um arquivo de entrada como este:

FastEthernet0/4,3 days ago,3 days ago
FastEthernet0/8,46 years ago ,46 years ago 
FastEthernet0/10,46 years ago ,46 years ago 
FastEthernet0/11,46 years ago ,46 years ago 
FastEthernet0/12,08:47:53,04:00:32
FastEthernet0/13,1 year ago 46 weeks ago ,1 year ago 46 weeks ago 
FastEthernet0/14,46 years ago ,46 years ago 
FastEthernet0/15,13 weeks ago 4 days ago ,13 weeks ago 4 days ago 
FastEthernet0/16,46 years ago ,46 years ago 
FastEthernet0/18,46 years ago ,46 years ago 
FastEthernet0/19,46 years ago ,46 years ago 
FastEthernet0/20,46 years ago ,46 years ago 
FastEthernet0/22,46 years ago ,1 year ago 50 weeks ago 
FastEthernet0/24,46 years ago ,46 years ago

Eu configurei todas as interfaces com o status de nunca na última entrada e saída para 46 anos atrás, para que eu possa obter uma data válida. E sim, eu tenho um registro de tempo de atividade para poder levar isso em conta.

Meu problema agora é que estou tentando converter isso em uma data válida. Fazer isso através de awk e xargs funciona bem, se eu fizer assim:

awk -F, '{print $2}' tst | xargs -i date -d "{}" +%Y-%m-%d

Recebo uma resposta como esta:

2016-08-18
1970-08-21
1970-08-21
1970-08-21
2016-08-21
2014-10-03
1970-08-21
2016-05-18
1970-08-21
1970-08-21
1970-08-21
1970-08-21
1970-08-21
1970-08-21

Mas se eu fizer assim, o que em minha mente deve dar o mesmo resultado:

awk -F, -v OFS="," '{
    cmd2=("date \"+'%Y-%m-%d'\" -d \""$2 "\"")
    cmd2|getline d2
    print cmd2,d2
}' tst

Recebo uma resposta como esta:

date "+%Y-%m-%d" -d "3 days ago",2016-08-18
date "+%Y-%m-%d" -d "46 years ago ",1970-08-21
date "+%Y-%m-%d" -d "46 years ago ",1970-08-21
date "+%Y-%m-%d" -d "46 years ago ",1970-08-21
date "+%Y-%m-%d" -d "08:47:53",2016-08-21
date "+%Y-%m-%d" -d "1 year ago 46 weeks ago ",2014-10-03
date "+%Y-%m-%d" -d "46 years ago ",2014-10-03
date "+%Y-%m-%d" -d "13 weeks ago 4 days ago ",2016-05-18
date "+%Y-%m-%d" -d "46 years ago ",2016-05-18
date "+%Y-%m-%d" -d "46 years ago ",2016-05-18
date "+%Y-%m-%d" -d "46 years ago ",2016-05-18
date "+%Y-%m-%d" -d "46 years ago ",2016-05-18
date "+%Y-%m-%d" -d "46 years ago ",2016-05-18
date "+%Y-%m-%d" -d "46 years ago ",2016-05-18

Eu adicionei o cmd2 ao comando print agora para ver o que ele estava tentando fazer. Apenas executar esse comando me fornece a data correta.

Acho que perdi algo simples, mas depois de tentar de novo e de novo por 3-4 horas, gostaria de ter outro par de olhos nele.

Alguém aí que pode me apontar na direção certa?

Atualizado! Com apenas três linhas do arquivo de entrada, eu mesmo não estava tendo problemas, como Stephen também apontou. Desculpe por postar um cenário não testado.

    
por Rune 21.08.2016 / 22:41

3 respostas

2

Quando você faz:

cmd = "some command"
cmd | getline d2
cmd | getline d2

em awk , o primeiro getline obtém a primeira linha (registro) da saída de cmd ( cmd é iniciado com base nessa primeira getline ) e a segunda obtém a segunda linha . Ao chegar ao final da saída, getline retorna 0.

No seu caso de cmd = "date -d \"46 years ago\"" , como esse comando produz apenas uma linha, o primeiro getline retorna essa linha em d2 (e um número positivo), mas o segundo alcança eof , então retorna 0 e deixa d2 intocado.

Aqui, você precisaria:

 cmd | getline d2; close(cmd)

Portanto, cmd seja executado a cada vez.

Ou armazene a saída de cmd em um hash para evitar a execução do mesmo comando várias vezes com os mesmos argumentos, como

if (!($2 in cache)) {cmd=...; cmd | getline cache[$2]; close(cmd)}
print cache[$2]

Ainda é uma boa ideia fechar cmd para evitar que um grande número de descritores de arquivos seja aberto.

Observe a ambiguidade em close(cmd) quando cmd é usado para getline < cmd e cmd | getline . Fecha o comando cmd ou o arquivo cmd ou ambos?

$ cat test.awk
BEGIN{
  OFS=","
  getline a1 < "uname"
  "uname" | getline b1
  close("uname")

  getline a2 < "uname"
  "uname" | getline b2
  print a1,b1,a2,b2
}
$ echo test > uname
$ mawk -f test.awk
test,Linux,test,Linux
$ bwk-awk -f test.awk
test,Linux,test,Linux
$ gawk -f test.awk
test,Linux,,Linux
$ busybox awk -f test.awk
test,,test,

( bwk-awk sendo o mantido por Brian Kernighan (o k em awk ), o comportamento é similar com implementações awk baseadas nisso como Solaris nawk ou FreeBSD awk ).

Veja como o busybox awk não permite usar "uname" como um arquivo e comando com getline e como close("uname") não fecha o arquivo uname em gawk .

Portanto, é uma boa ideia certificar-se de não usar um arquivo e um comando com o mesmo nome ao mesmo tempo. Você pode adicionar um "\n" no início ou no final de um comando para tornar improvável que seja confundido com um arquivo .

Como:

 awk 'BEGIN {getline foo < $ENVIRON["FILE"]; "uname\n" | getline system}'

evitaria o problema se $FILE fosse uname (não se $FILE fosse uname<newline> , mas isso fosse menos provável).

    
por 22.08.2016 / 10:45
2

Então isso é interessante; parece que nessa estrutura awk está lembrando que ele fez essa chamada antes e não a fez novamente, e portanto não passou nenhum dado para readline .

Podemos ver isso fazendo strace -f -o xxx awk ....

Olhando através do traço, vemos:

3401  execve("/bin/date", ["date", "+%Y-%m-%d", "-d", "3 days ago"], [/* 50 vars */]) = 0
3403  execve("/bin/date", ["date", "+%Y-%m-%d", "-d", "46 years ago "], [/* 50 vars */]) = 0
3405  execve("/bin/date", ["date", "+%Y-%m-%d", "-d", "08:47:53"], [/* 50 vars */]) = 0
3407  execve("/bin/date", ["date", "+%Y-%m-%d", "-d", "1 year ago 46 weeks ago "], [/* 50 vars */]) = 0
3409  execve("/bin/date", ["date", "+%Y-%m-%d", "-d", "13 weeks ago 4 days ago "], [/* 50 vars */]) = 0

Assim, cada data única é passada uma vez, mas nunca mais. Isso acontece com o GNU awk no CentOS 7 e mawk no Debian, então isso parece ser um comportamento esperado.

Um hack simples é tornar cada linha única; Por exemplo, adicione um comentário com o número da linha.

cmd2="date \"+'%Y-%m-%d'\" -d \""$2 "\" ; # " NR 

O código resultante é parecido com:

awk -F, -v OFS="," '{cmd2="date \"+'%Y-%m-%d'\" -d \""$2 "\" ; # " NR ;
                     cmd2|getline d2;
                     print cmd2,d2}' x
date "+%Y-%m-%d" -d "3 days ago" ; # 1,2016-08-18
date "+%Y-%m-%d" -d "46 years ago " ; # 2,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 3,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 4,1970-08-21
date "+%Y-%m-%d" -d "08:47:53" ; # 5,2016-08-21
date "+%Y-%m-%d" -d "1 year ago 46 weeks ago " ; # 6,2014-10-03
date "+%Y-%m-%d" -d "46 years ago " ; # 7,1970-08-21
date "+%Y-%m-%d" -d "13 weeks ago 4 days ago " ; # 8,2016-05-18
date "+%Y-%m-%d" -d "46 years ago " ; # 9,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 10,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 11,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 12,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 13,1970-08-21
date "+%Y-%m-%d" -d "46 years ago " ; # 14,1970-08-21

Uma solução mais complicada seria armazenar em cache os resultados em uma matriz e chamar apenas date se você não viu essa data antes.

    
por 21.08.2016 / 23:52
1

Para substituir as datas relativas na coluna 2 e 3 por datas reais

awk -F, '{for(i=2;i<4;i++){"date -I -d \""$i"\"" | getline a; $i=a}}1' OFS=, tst

Fiquei surpreso que o getline $i direto não funcionou: ele alterou um campo de dois ou nada. Então eu tenho que incluir a variável adicional a
Com a função do usuário definida:

awk -F, '
    function cmd2(date){
        "date -I -d \""date"\"" | getline a
        return a
    }
    {
        $2=cmd2($2)
        $3=cmd2($3)
        print
    }' OFS=, tst

Mas se a função estiver definida com a variável local cmd2(date, a) , mostrará o mesmo comportamento, conforme mencionado acima.

Para linhas homogêneas, a tarefa pode ser facilmente executada com o GNU sed

tr ',' '\n' tst |
sed '1~3!{s/^/date -I -d "/;s/$/"/e;}' |
paste -sd ',,\n'
    
por 22.08.2016 / 10:08

Tags