使用awk在列中插入日期

时间:2015-04-24 09:59:11

标签: bash date awk

我正在尝试格式化csv列中的日期。

输入类似于:28 April 1966

我想要这个输出:1966-04-28

可以使用以下代码获得:

date -d "28 April 1966" +%F

所以现在我想混合使用awk和这段代码来格式化整个列,但是我找不到如何。

编辑:

输入示例:(分隔符“|”实际上是制表符)

1 | 28 April 1966
2 | null
3 | null
4 | 30 June 1987 

预期产出:

1 | 1966-04-28
2 | null
3 | null
4 | 30 June 1987

5 个答案:

答案 0 :(得分:3)

一种简单的方法是

{
  cmd = "date -d \"" $3 "\" +%F 2> /dev/null"  # build shell command
  cmd | getline $3                             # run, capture output
  close(cmd)                                   # close pipe
}
1                                              # print

那是:

date

这是有效的,因为getline如果日期无效,则不会向其标题打印任何内容,因此$3失败且mktime未更改。

需要考虑的注意事项:

  1. 对于非常大的文件,这将在这些shell中生成大量shell和进程(每行一个)。这可能会成为明显的性能拖累。
  2. 警惕代码注入。如果CSV文件来自不值得信任的来源,这种方法很难防御攻击者,并且您可能最好远离攻击者,使用gawk' strftimeawk -F '\t' -v OFS='\t' '{ cmd = "date -d \"" $3 "\" +%F 2> /dev/null"; cmd | getline $3; close(cmd) } 1' filename 手动解析日期。
  3. 编辑:评论:要将标签用作分隔符,可以将命令更改为

    BEGIN {
      OFS = FS
    
      m["January"  ] =  1
      m["February" ] =  2
      m["March"    ] =  3
      m["April"    ] =  4
      m["May"      ] =  5
      m["June"     ] =  6
      m["July"     ] =  7
      m["August"   ] =  8
      m["September"] =  9
      m["October"  ] = 10
      m["November" ] = 11
      m["December" ] = 12
    }
    
    $3 !~ /null/ {
      split($3, a, " ")
      $3 = sprintf("%04d-%02d-%02d", a[3], m[a[2]], a[1])
    }
    1
    

    编辑重新:评论2:如果性能令人担忧,就像看起来那样,每行产生流程都不是一个好方法。在这种情况下,您必须手动进行解析。例如:

    foo.awk

    将其放入文件中,例如awk -F '\t' -f foo.awk filename.csv,然后运行auto ptr = static_cast< typename _DescriptorType::FeatureType (*)(const Instance &, const _DescriptorType &) >(getDescriptor<_DescriptorType>);

答案 1 :(得分:1)

这应该适用于您的输入

awk -F'\\|' -vOFS="|" '!/null/{cmd="date -d \""$3"\" +%F";cmd | getline $3;close(cmd)}1' file

输出

| 1 |1966-04-28
| 2 | null
| 3 | null
| 4 |1987-06-30

答案 2 :(得分:1)

我建议使用支持解析日期的语言,例如perl:

$ cat file
1       28 April 1966
2       null
3       null
4       30 June 1987
$ perl -F'\t' -MTime::Piece -lane 'print "$F[0]\t", 
  $F[1] eq "null" ? $F[1] : Time::Piece->strptime($F[1], "%d %B %Y")->strftime("%F")' file
1       1966-04-28
2       null
3       null
4       1987-06-30

Time::Piece核心模块允许您使用strftime的标准格式说明符来解析和格式化日期。此解决方案将选项卡分为选项卡字符,如果第二个字段不是&#34; null&#34;则修改格式。

这种方法比使用system调用或调用子进程要快得多,因为一切都是在本机perl中完成的。

答案 3 :(得分:0)

以下是如何在纯BASH中执行此操作,并避免从awk调用systemgetline

while IFS=$'\t' read -ra arr; do 
   [[ ${arr[1]} != "null" ]] && arr[1]=$(date -d "${arr[1]}" +%F)
   printf "%s\t%s\n" "${arr[0]}" "${arr[1]}"
done < file

1       1966-04-28
2       null
3       null
4       1987-06-30

答案 4 :(得分:0)

只有一次日期调用且无代码注入问题,请参阅以下内容:

此脚本将日期(使用awk)提取到临时文件中,通过一次“日期”调用处理它们并将结果合并(使用awk)。

代码

awk -F '\t' 'match($3,/null/) { $3 = "0000-01-01" } { print $3 }' input > temp.$$
date --file=temp.$$ +%F > dates.$$
awk -F '\t' -v OFS='\t' 'BEGIN {
                           while ( getline < "'"dates.$$"'" > 0 )
                           {
                              f1_counter++
                              if ($0 == "0000-01-01") {$0 = "null"}
                              date[f1_counter] = $0
                           }
                         }
                         {$3 = date[NR]}
                         1' input.$$
使用bash进程重定向

单线(无临时文件):

inputfile=/path/to/input
awk -F '\t' -v OFS='\t' 'BEGIN {while ( getline < "'<(date -f <(awk -F '\t' 'match($3,/null/) { $3 = "0000-01-01" } { print $3 }' "$inputfile") +%F)'" > 0 ){f1_counter++; if ($0 == "0000-01-01") {$0 = "null"}; date[f1_counter] = $0}}{$3 = date[NR]}1' "$inputfile"

详细

以下是它的使用方法:

# configuration
input=/path/to/input
temp1=temp.$$
temp2=dates.$$
output=output.$$
# create the sample file (optional)
#printf "\t%s\n" $'1\t28 April 1966' $'2\tnull' $'3\tnull'  $'4\t30 June 1987' > "$input"
# Extract all dates
awk -F '\t' 'match($3,/null/) { $3 = "0000-01-01" } { print $3 }' "$input" > "$temp1"
# transform the dates
date --file="$temp1" +%F > "$temp2"
# merge csv with transformed date
awk -F '\t' -v OFS='\t' 'BEGIN {while ( getline < "'"$temp2"'" > 0 ){f1_counter++; if ($0 == "0000-01-01") {$0 = "null"}; date[f1_counter] = $0}}{$3 = date[NR]}1' "$input" > "$output"
# print the output
cat "$output"
# cleanup
rm "$temp1" "$temp2" "$output"
#rm "$input"

注意事项

  • 使用“0000-01-01”作为无效(空)日期的临时占位符
  • 代码应该比其他多次调用“date”的方法更快,但是它会两次读取输入文件。