通过 awk 从 date 得到错误的响应

通过 awk 从 date 得到错误的响应

我正在尝试创建一个脚本,可以将显示接口上最后输入和输出的思科交换机的输出转换为有用的格式。这是为了能够知道哪些接口没有被使用过,比如最近 6 个月。思科只是说接口的最后一个输入是10w4d

到目前为止我得到的是这样的输入文件:

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

我已经将所有状态为从不上次输入和输出的接口设置为46年前,这样我就可以获得有效的日期。是的,我确实有正常运行时间的记录,可以考虑到这一点。

我现在的问题是我正在尝试将其转换为有效日期。如果我这样做的话,这样做就可以了awk并且工作得很好:xargs

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

我得到这样的回应:

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

但如果我这样做,在我看来应该给出相同的结果:

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

我得到这样的回应:

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

我刚才将其添加cmd2到命令中以查看它要执行的操作。print只需运行该命令即可给我正确的日期。

我想我错过了一些简单的事情,但在一遍又一遍地尝试 3-4 小时后,现在我想再看一下它。

有人可以指出我正确的方向吗?

更新!正如斯蒂芬也指出的那样,只有三行输入文件,我自己没有遇到任何问题。很抱歉发布未经测试的场景。

答案1

当你这样做时:

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

在 中awk,第一个获取(然后从第一个)getline输出的第一行(记录),第二个获取第二行。到达输出末尾时,返回 0。cmdcmdgetlinegetline

在您的情况下cmd = "date -d \"46 years ago\"",由于该命令仅输出一行,因此第一个命令getline返回该行d2(和一个正数),但第二个到达eof,因此返回 0 并保持d2不变。

在这里,你需要:

 cmd | getline d2; close(cmd)

所以cmd每次都要运行。

或者将输出存储cmd在哈希中,以避免使用相同的参数多次运行相同的命令,例如

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

关闭cmd那里仍然是一个好主意,以避免打开大量文件描述符。


close(cmd)请注意,when中的歧义cmd同时用于getline < cmdcmd | getline。是否关闭cmd 命令或者cmd 文件或两者?

$ 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,

(由 Brian Kernighan(在)bwk-awk维护,其行为与基于 Solaris或 FreeBSD等的 awk 实现类似)。kawknawkawk

了解 busybox awk 如何不允许您"uname"同时用作文件和命令getline以及如何close("uname")不关闭uname 文件gawk

因此,最好确保不要同时使用同名的文件和命令。您可以"\n"在 a 的开头或结尾添加命令使其不太可能与文件

喜欢:

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

$FILE如果碰巧是的话,就会避免这个问题uname(不是如果$FILEuname<newline>,但可能性较小)。

答案2

所以这很有趣;似乎在这个结构中awk记住它之前已经进行过此调用并且没有再次执行此操作,因此没有将任何数据传递给readline.

我们可以通过这样做看到这一点strace -f -o xxx awk ....

通过跟踪我们可以看到:

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

因此,每个唯一的日期都会过去一次,但不会再过去。 CentOS 7 和 Debian 上的 GNU awk 会发生这种情况mawk,因此这似乎是预期的行为。

一个简单的技巧就是使每一行都独一无二;例如添加带有行号的注释。

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

生成的代码类似于:

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

一个更复杂的解决方案是将结果缓存在一个数组中,只有date当您之前没有见过这个日期时才调用。

答案3

将第 2 列和第 3 列中的相对日期替换为实际日期

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

我很惊讶直接getline $i不起作用:它要么改变两个字段中的一个字段,要么什么也不改变。所以我必须a
在定义的用户函数中包含额外的变量:

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

但如果函数是用局部变量定义的,cmd2(date, a)它会显示与上述相同的行为。

对于同质线任务可以轻松完成GNU sed

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

相关内容