【问题标题】:How to format a string date (with text and milliseconds) using AWK如何使用 AWK 格式化字符串日期(带有文本和毫秒)
【发布时间】:2017-03-11 17:00:53
【问题描述】:

我正在编写一个解析数百万行文本的 AWK 脚本。每行包含(除其他外)表单上的日期和时间:

16-FEB-2008 14:17:59.994669

我需要把它转换成下面的形式

20080216141759994669000

如果可能的话,我想避免手动将月份从文本转换为数值。在 bash 中,我可以简单地执行以下命令来获得所需的结果:

date -d "16-FEB-2008 14:17:59.994669" +"%Y%m%d%H%M%S%N"

我曾尝试在 AWK 中调用此命令,但我不知道如何操作。我想知道

  1. 是否可以单独使用 AWK 来实现?
  2. 如何在 AWK 脚本文件中使用这样的命令?

提前致谢

【问题讨论】:

  • 运行命令date... 数百万次。恕我直言,使用perl stdin/stdout 会更快...
  • 感谢您的建议!除了日期和时间转换之外,awk 非常适合我的任务。不过下次我会考虑perl!

标签: bash shell unix awk


【解决方案1】:

在 awk 中将月份名称转换为数字很容易,只要您不需要(额外的)验证 date“免费”,重新格式化也是如此:

$ echo this 16-FEB-2008 14:17:59.994669 that \
> | awk '{ split($2,d,"-"); split($3,t,"[:.]"); 
    m=sprintf("%02d",index("JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC",d[2])/4+1);
    print $1,d[3] m d[1] t[1] t[2] t[3] t[4] "000",$4 }'
this 20080216141759994669000 that
$ # or can put the script in a file and use with awk -f
$ # or the whole thing in a shebang file like #!/bin/awk -f

这并不比运行date 的代码长多少,并且对于“数百万行”来说效率更高。

【讨论】:

  • 这是非常好的反馈。毕竟我可能会做这样的事情。感谢您和所有其他 cmets。
【解决方案2】:

你可以这样调用外部命令:

awk '{
         cmd="date -d \""$0"\" +%Y%m%d%H%M%S%N"
         cmd | getline ts
         print $0, ts
         # awk opened a pipe for the communication with 
         # the command. close that pipe to avoid running
         # out of file descriptors
         close(cmd)
     }' <<< '16-FEB-2008 14:17:59.994669'

输出:

16-FEB-2008 14:17:59.994669 20080216141759994669000

感谢 dave_thompson_085 的评论,如果您拥有来自 GNU coreutils 的 date 和 gawk,您可以显着提高性能。 GNU 的date 支持从标准输入读取日期,gawk 支持协同进程,允许在后台启动date 的单个实例,写入标准输入并从标准输出读取:

{
    cmd = "stdbuf -oL date -f /dev/stdin +%Y%m%d%H%M%S%N"
    print $0 |& cmd 
    cmd |& getline ts
    print $0, ts
}

注意除了强制date逐行输出结果外,还需要使用stdbuf命令。

【讨论】:

  • 谢谢,我对如何使用管道和 getline 来实现这一点感到非常困惑。这很有帮助!
  • 我记得第一次用的时候也很迷茫;)不客气
  • 因为-d 无论如何都需要GNU coreutils date,如果最近你可以使用-f /dev/stdin 并将一系列值传递给一个进程,效率要低得多。在 GNU awk 中,您可以使用 |&amp;,但在任何 awk 中,您都可以将 fifo/named-pipe 和标准 getline 或 print[f] 管道结合起来,无论哪个方便。
  • @dave_thompson_085 补充说。效果很好,但需要stdbuf。否则cmd |&amp; getline ts 会挂起。
  • 没有必要调用date来重新排列字符串中的字符,如果你打算使用gawk来代替|&amp; "date",那么你应该只使用gawks内置的时间函数。
【解决方案3】:

使用 perl:

LANG=C perl -MTime::Piece -plE 's/\b(\d+-\w{3}-\d{4}\s+\d+:\d+:\d+)\.(\d+)\b/Time::Piece->strptime($1,q{%d-%b-%Y %H:%M:%S})->strftime(q{%Y%m%d%H%M%S}).$2/ge' < in >out

用重新格式化(和验证)的日期全局替换每个类似时间的模式。

核心模块Time::Piece不支持小数秒,所以解决方案有点hackish...

【讨论】:

    【解决方案4】:

    这里有很多很好的答案。这是一个使用awk 帮助函数重新格式化日期的方法。

    awk '
      BEGIN { 
        mi["JAN"]="01"; mi["FEB"]="02"; mi["MAR"]="03"; mi["APR"]="04"; mi["MAY"]="05"; mi["JUN"]="06"
        mi["JUL"]="07"; mi["AUG"]="08"; mi["SEP"]="09"; mi["OCT"]="10"; mi["NOV"]="11"; mi["DEC"]="12"
      }
      function reformatDate(dtStr, tmStr) {
        split(dtStr, dtParts, "-"); gsub(/[:.]/, "", tmStr)
        return dtParts[3] mi[dtParts[2]] sprintf("%02d", dtParts[1]) tmStr "000"
      }
      { print reformatDate($1, $2) }
    ' <<<'16-FEB-2008 14:17:59.994669'
    

    【讨论】:

      【解决方案5】:

      这里不需要调用日期,你只需要一个月查找

      $ awk -F'[- :.]' -v OFS='' '
           BEGIN {split("JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC",m);
                  for(i=1;i<=12;i++) a[m[i]]=i<10?"0"i:i}
                 {$2=a[$2]; y=$3; $3=$1; $1=y; print $0 "000"}' file
      

      【讨论】:

        猜你喜欢
        • 2011-11-08
        • 2013-02-21
        • 2022-11-23
        • 2018-12-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-21
        相关资源
        最近更新 更多