使用其他命令修改 awk 参数

问题描述

我有一个这样的文件,其中包含多行的多个字段。我想显示其中一些,同时用另一个命令处理其中一个

TITLE,OpenVPN ...
HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Virtual IPv6 Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username,Client ID,Peer ID
CLIENT_LIST,name1,1.1.1.1:1,10.0.0.1,2692253,3765861,Wed Jun 23 12:51:08 2021,1624452668,4727,0
CLIENT_LIST,name2,2.2.2.2:2,10.0.0.2,1571221,2080242,Thu Jul  1 19:24:10 2021,1625167450,5625,name3,3.3.3.3:3,10.0.0.3,2670410,3736957,Wed Jun 23 16:20:51 2021,1624465251,4747,0
...

预期的输出是这样的:

name1  10.0.0.1  2021-06-23 12:51:08
name2  10.0.0.2  2021-07-01 19:24:10
name3  10.0.0.3  2021-06-23 16:20:51

我现在的命令是这样的:

grep '^CLIENT_LIST,' /var/run/ovpn-server.status |awk -F',' '{print $2 $4 $9}' |sort

它打印所需的字段,但不会将时间戳转换为格式化的时间。这是为此的命令:

date -d @1624452668 +"%Y-%m-%d %H:%M:%s"

如何将 date 命令集成到 awk 脚本中?或者还有什么其他解决方案可以实现这一点?

我还打算使用 column 命令将输出放入列/表格布局中,我之前已经这样做过,所以这不是问题的一部分。

解决方法

您可以使用此awk

awk -F,-v OFS='\t' '$1 == "CLIENT_LIST" {
   cmd = "date +\047%Y-%m-%d %H:%M:%S\047 -d\047@" $9 "\047"
   print $2,$4,((cmd | getline dt) > 0 ? dt : $9)
   close(dt)
}' file

name1   10.0.0.1    2021-06-23 08:51:08
name2   10.0.0.2    2021-07-01 15:24:10
name3   10.0.0.3    2021-06-23 12:20:51

说明:

  • -F,-v OFS='\t':将输入字段分隔符设置为,,将输出字段分隔符设置为制表符
  • '$1 == "CLIENT_LIST":当第一个字段是 CLIENT_LIST 时执行
  • cmd = "date +\047%Y-%m-%d %H:%M:%S\047 -d\047@" $9 "\047":使用 date
  • 格式化 $9 命令
  • cmd | getline dt 调用外部 date 命令
  • (cmd | getline dt) > 0:当 date 命令成功时
  • print:打印 date 字段的第 2、4 和输出
,

如果您实际上只想重新格式化 $8 中的日期 + 时间,而不是将自纪元以来的秒数从 $9 转换为日期 + 时间,那么您只需执行以下操作即可比调用 date 的速度要快得多,因为这需要 awk 在每个输入行中生成一个子外壳,以便从该子外壳调用 date,这将非常慢。

在每个 Unix 机器上的任何 shell 中使用任何 awk:

$ cat tst.awk
BEGIN { FS=","; OFS="\t" }
NR > 2 {
    split($8,t," ")
    mthNr = (index("JanFebMarAprMayJunJulAugSepOctNovDec",t[2])+2)/3
    print $2,sprintf("%04d-%02d-%02d %s",t[5],mthNr,t[3],t[4])
}

$ awk -f tst.awk file
name1   10.0.0.1        2021-06-23 12:51:08
name2   10.0.0.2        2021-07-01 19:24:10
name3   10.0.0.3        2021-06-23 16:20:51

或者如果你真的想使用 $9 的纪元秒,那么使用 GNU awk for strftime() 这样你就不必产生子 shell 来调用 date(但请注意,输出现在变成了 TZ -dependent,就像 date):

$ cat tst.awk
BEGIN { FS=","; OFS="\t" }
NR > 2 {
    print $2,strftime("%F %T",$9)
}

$ awk -f tst.awk file
name1   10.0.0.1        2021-06-23 07:51:08
name2   10.0.0.2        2021-07-01 14:24:10
name3   10.0.0.3        2021-06-23 11:20:51

$ TZ=UTC awk -f tst.awk file
name1   10.0.0.1        2021-06-23 12:51:08
name2   10.0.0.2        2021-07-01 19:24:10
name3   10.0.0.3        2021-06-23 16:20:51

或者在 strftime() 中设置 UTC 标志,如果 UTC 是您的数据中的内容:

$ cat tst.awk
BEGIN { FS=",$9,1)
}

$ awk -f tst.awk file
name1   10.0.0.1        2021-06-23 12:51:08
name2   10.0.0.2        2021-07-01 19:24:10
name3   10.0.0.3        2021-06-23 16:20:51
,

也许不是最有效的 awk 实现,但在无法弄清楚 @anubhava 解决方案如何工作后,我想出了这个 hack。

awk -F"," '{ if (NR > 2 && NR < 6){ print $2,$8 }}' $file | sed -r 's/Mon|Tue|Wed|Thu|Fri|Sat|Sun//' | awk '{if ($4~/1/) $4="01"}{if ($3~/Jun/) $3="06" }{if ($3~/Jul/) $3="07"}{ print $1,$2,$6"-"$3"-"$4,$5}'

name1 10.0.0.1 2021-06-23 12:51:08
name2 10.0.0.2 2021-07-01 19:24:10
name3 10.0.0.3 2021-06-23 16:20:51