2017-07-25 52 views
2

我有一个大约10MM记录的文件。这是我的dateSample SRC文件:unix awk从日期字段中减去整数字段

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上更新日期)遇到真正的麻烦,并在过去的几天中一直在徒劳地搜索。
这是不是一个零前缀和丢弃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 

所以基本上,我需要的是一个高性能的方式输出。
预先感谢您的任何指导/重写。
干杯

回答

2

如果您awk支持time functionsmktimestrftime(这是一个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”格式并打印出来。


或者,你可以做,不需要日期正常化的简单情况的日期计算“手动” - 如果这个月的日子总是<= 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 
+1

真棒回应...感谢您的详细解释。 strftime-mktime组合非常好,工作速度超快。 也感谢“手工”版本的完整性。 谢谢你的帮助。欢呼声 – sigmazen

+0

不客气,我很高兴它解决了你的问题。 – randomir

1

既然你处理日期,最好是做这个壳本身:

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 
+1

由于您在每行上调用外部'日期'(两次),这不会太慢吗? – randomir

+1

另外,不应该对'date'进行内部调用:'$(date -d“$ date”'+%Y /%m /%d')'? – randomir

+0

感谢@randomir现在已经修复。 – anubhava

1

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

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 
+0

嗨...感谢上述,但它不适用于2017-05-01的日期。这里是一个调整版本: awk'{ \t split($ 2,array,“/”); \t如果($ 90元> 0){ \t如果(阵列[2] == “01”){ \t阵列[1] =阵列[1] - 1 \t阵列[2] =阵列[2] + 12 - $ 3; \t如果(阵列[2] <10){阵列[2] = “0” 数组[2]} \t}否则{ \t \t阵列[2] =阵列[2] - $ 3 \t如果(阵列[ 2] <10){array [2] =“0”array [2]} \t}; \t} \t打印$ 1,阵列[1] “/” 阵列[2] “/” 阵列[3] \t}” <$ SRC 干杯 – sigmazen