【问题标题】:unix awk subtracting integer field from month of date fieldunix awk 从日期字段的月份中减去整数字段
【发布时间】:2017-12-31 23:35:45
【问题描述】:

我有一个包含大约 10MM 记录的文件。这是我的 dateSample src 文件:

0000000566 2017/01/01 0
0000000055 2017/01/01 0
0000000109 2017/01/01 1
0000000940 2017/01/01 0
0000000566 2017/01/01 1
0000000055 2017/01/01 1
0000000109 2017/01/01 2

我基本上需要减去日期中月份的最后一个整数值并打印不带整数的新值,因此:

0000000566 2017/01/01
0000000055 2017/01/01
0000000109 2016/12/01
0000000940 2017/01/01
0000000566 2016/12/01
0000000055 2016/12/01
0000000109 2016/11/01

我在使用日期(或测试时在 macOS 上使用 gdate)时遇到了真正的问题,并且在过去几天里一直在徒劳地搜索。
它要么以零为前缀,要么删除 m 和 d 值:

awk '{ print (gdate -d $2 +"%Y/%m/%d") }' <$src

或以零为后缀并从年份中减去整数:

awk '{ print (gdate -d $2 +-$3 months +"%Y/%m/%d") }' <$src

或者把整个东西混在一起仍然是不正确的:

awk '{ print gdate -d (gdate -d $2 +"%Y/%m/%d") +-$3 months +"%Y/%m/%d" }' <$src

我发现了以下出色的回应: Increment date with AWK for few days and months 这正是我想要的,但它运行得非常非常慢,我假设这是因为命令中的命令。

这是当前的 awk(我正在使用 gdate,因为我现在在 macOS BSD 上运行):

awk '{ cmd=" gdate -d \"$(gdate -d \""$2"\")+\"-"$3"\"months\" \"+%Y/%m/%d\" ";
       cmd | getline fmtDate; close(cmd); 
       print $1, fmtDate
     }' <$src

所以我基本上需要以高效的方式输出。
提前感谢您的任何指导/重写。
干杯

【问题讨论】:

    标签: shell date unix awk


    【解决方案1】:

    如果您的 awk 支持 time functions mktimestrftime(这是 GNU 扩展),您可以这样做:

    awk -F'[ /]' '{print $1 " " strftime("%Y/%m/%d", mktime($2" "($3-$5)" "$4" 0 0 0"))}' file
    

    首先,我们将日期转换为 Unix 时间戳。 mktime 只接受 "YYYY MM DD HH MM SS" 格式的日期,这就是我们需要手动构造它的原因。但它会自动进行标准化,并且很乐意将"2017 -1 1 0 0 0" 转换为与"2016 11 1 0 0 0" 相同的时间戳。

    之后我们只需要将时间戳转换为“y/m/d”格式并打印出来。


    或者,您可以在不需要日期标准化的简单情况下“手动”执行日期算术 - 如果月份中的日期始终为 &lt;= 28。 (对于大于28 的天数,例如31,您还需要在下面的脚本中添加剪辑/钳位或溢出,但您必须注意闰年等)

    #!/usr/bin/awk -f
    
    BEGIN {
        FS = "[ /]";
    }
    
    {
        mm = $2 * 12 + ($3 - 1) - $5;
        y = int(mm / 12);
        m = mm % 12 + 1;
        d = $4;
        printf("%s %04d/%02d/%02d\n", $1, y, m, d);
    }
    

    所以,这个想法很简单。我们用空格和斜杠分割线,因此我们可以将年/月转换为总月数 (12 * y + m)。然后我们从最后一列中减去月份,并通过divmod 操作将总月数转换回年/月。

    输出:

    $ ./script.awk file
    0000000566 2017/01/01
    0000000055 2017/01/01
    0000000109 2016/12/01
    0000000940 2017/01/01
    0000000566 2016/12/01
    0000000055 2016/12/01
    0000000109 2016/11/01
    

    【讨论】:

    • 很棒的回应...谢谢您的详细解释。 strftime-mktime 组合非常好,而且运行速度非常快。还要感谢“手动”版本的完整性。谢谢您的帮助。干杯
    【解决方案2】:

    由于您要处理日期,最好在 shell 本身中执行此操作:

    while read -r str date n; do
       echo "$str $(date -d "$(date -d $date) -$n months" '+%Y/%m/%d')"
    done < file
    
    0000000566 2017/01/01
    0000000055 2017/01/01
    0000000109 2016/12/01
    0000000940 2017/01/01
    0000000566 2016/12/01
    0000000055 2016/12/01
    0000000109 2016/11/01
    

    【讨论】:

    • 这会不会也很慢,因为您在每行(两次)上调用外部date
    • 另外,你对date的内部调用不应该是:$(date -d "$date" '+%Y/%m/%d')吗?
    • 感谢@randomir,现在已修复。
    【解决方案3】:

    尝试一种更简单的方法来解决这个问题。

    awk 'BEGIN{
         split("01,02,03,04,05,06,07,08,09,10,11,12", month,",")
    }
    {
         split($2, array,"/");
           if(array[2]<=$3){
           array[2]=array[2]+12-$3;
           array[1]=array[1]-1
          }
           else{
           array[2]-$3
          };
         print $1,array[1]"/"array[2]"/"array[3]
    }
    '  Input_file
    

    【讨论】:

    • 嗨......感谢上述内容,但它不适用于 2017 年 5 月 1 日的日期。这是一个调整后的版本: awk '{ split($2, array,"/"); if($3 > 0) { if(array[2] == "01") { array[1] = array[1] - 1 array[2] = array[2] + 12 - $3; if(array[2]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-25
    • 2018-03-12
    • 2015-02-22
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多