Awk 文本处理:基于另一列值偏移列中的日期值

Awk 文本处理:基于另一列值偏移列中的日期值,awk,Awk,我在第4列有一个日期值为YYYYMMDD格式的文本文件,在第5列有一个月偏移量 a1|b1|c1|20190101|1|1|11|A|D a1|b1|c1|20190101|2|2|12|B|E a1|b1|c1|20190101|3|3|13|C|F a2|b2|c2|20190101|1|4|14|G|J a2|b2|c2|20190101|2|5|15|H|K a2|b2|c2|20190101|3|6|16|I|L 我想将第5列中的值替换为 日期-d'(第4列中的值)-(第5列中的值)

我在第4列有一个日期值为YYYYMMDD格式的文本文件,在第5列有一个月偏移量

a1|b1|c1|20190101|1|1|11|A|D
a1|b1|c1|20190101|2|2|12|B|E
a1|b1|c1|20190101|3|3|13|C|F
a2|b2|c2|20190101|1|4|14|G|J
a2|b2|c2|20190101|2|5|15|H|K
a2|b2|c2|20190101|3|6|16|I|L
我想将第5列中的值替换为 日期-d'(第4列中的值)-(第5列中的值)月+1个月'+'%Y%m%d'

所以最终结果应该如下所示

a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20190101|4|14|G|J
a2|b2|c2|20190101|20181201|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L
我用awk-f offsetMonths.awk打成平局

BEGIN{
    FS="|"
    OFS = FS
}
{
#   Date field is in column 4, offset is in column 5
#   Replace column 5 with the offset date
    "date -d '"$4" -"$5" months +1 months'  +'%Y%m%d' " | getline l
    $5 = l
    print $0
}
我得到的是

a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20181101|4|14|G|J
a2|b2|c2|20190101|20181101|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L

请注意,使用GNU awk第4行到第6行中的月偏移值不正确,任何日期转换都应使用提供的日期函数执行。此问题的两个有用时间函数是
mktime
strftime

  • mktime(datespec)
    :将格式为
    YYYY-MM-DD-hh-MM-ss
    的日期规范字符串
    datespec
    转换为Unix纪元时间,即自1970 01 UTC以来的总秒数。由于gawk-4.2.1,您可以使用
    utc标志
    指示
    datespec
    是否处于utc

  • strftime(格式,时间戳)
    :将历元时间
    时间戳
    转换为格式化字符串(格式与
    日期
    命令相同)。您可以使用
    utc标志
    指示返回的时间应为utc或本地时区

更多信息请访问

代码现在变为:
convert.awk

BEGIN {FS=OFS="|"}
{ d=$4
  time=mktime(substr(d,1,4)" "substr(d,5,2)+1-$5" "substr(d,7,2)" 00 00 00")
  $5=strftime("%Y%m%d",time)
  print
}' file
您可以通过以下方式运行此功能:

$ awk -f convert.awk file.txt
mktime
具有显著的相加性。因此,您传递的格式为
YYYY-MM-DD-hh-MM-ss
的字符串不需要是正确的日期,您可以有不正确的值。例如,字符串
2019 01 32 00 00
相当于
2019 02 01 00 00
2019 5-10 00 00
相当于
2019 04 20 00 00
,甚至
2019-19-10 00
相当于
2017 04 20 00


注意:因为我们直接在
mktime
中修改时间,所以我们不需要担心夏令时(参见注释)。

除了kvantour的答案之外(这可能比我的答案更简洁,因为我更喜欢使用awk内置函数,而不是在awk中调用bash命令),以下是您的代码修复:

BEGIN{
    FS="|"
    OFS = FS
}
{
#   Date field is in column 4, offset is in column 5
#   Replace column 5 with the offset date

    cmd = "date -d '"$4" -"$5-1" months'  +'%Y%m%d'" # as suggested by @kvantour
    cmd | getline result
    close(cmd)
    $5 = result
    print $0
}
结果:

a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20190101|4|14|G|J
a2|b2|c2|20190101|20181201|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L
这里有更多

在不调用close()的情况下,awk创建子进程来运行命令,直到最终耗尽更多管道的文件描述符


您不需要为此调用外部
date
实用程序或特定于gawk的时间函数,这只是数学:

$ cat tst.awk
BEGIN { FS=OFS="|" }
{
    delta = $5 - 1
    year  = substr($4,1,4)
    month = substr($4,5,2)
    day   = substr($4,7)
    month = month - delta
    if (month <= 0) {
        year--
        month += 12
    }
    $5 = sprintf("%04d%02d%02d",year,month,day)
    print
}
$
$ awk -f tst.awk file
a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20190101|4|14|G|J
a2|b2|c2|20190101|20181201|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L
$cat tst.awk
开始{FS=OFS=“|”}
{
增量=$5-1
年份=次级资源(4,1,4美元)
月=子月($4,5,2)
日=分包(4.7美元)
月=月-增量

如果(月)我会明确提到关闭的原因。另外,你可以使用
$5-1
而不是“-”$5”月+1个月。谢谢@kvantour,我引用了使用
关闭的原因(而不是试图解释它并出错:p)它需要是
$5=(cmd|getline result)>0?结果:“N/A”);关闭(cmd)
或类似于明智地处理日期/getline故障。请参见@EdMorton Aha。只有在通过减去秒来更改
mktime
之外的时间时,这似乎才是危险的(
$TZ='Europe/Monaco'awk'BEGIN{t=mktime(“2019 05 01 00 00 00”);打印标准时间(“%FT%t”,t);打印标准时间(“%FT%t”,t-86400*120)
)首先,祝你好运。为了安全起见,
month
可能大于12,同时,也可能是几年后。
if(month>0){year+=int((month-1)/12);month=(month-1)%12+1};if(month<1){year-=int(month/12)-1;month=month%12+12}
是的,这是可能的。如果OP说这可能发生,我会更新。谢谢。