附录

附录

我有一个包含以下数据结构的 csv:

1111,2222,3333,4444,5555,6666,7777,2017-1-5 1:07:09,2017-1-5 1:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

我想显示日期、月和日,一如既往地为 2 位数字长。我还希望时间 Hour 字段始终为 2 位数字。

如果月/日/小时字段仅为单个数字(如上面的示例行所示),则本质上是添加前导零。

使用 awk,我将如何实现以下结果:

1111,2222,3333,4444,5555,6666,7777,2017-01-05 01:07:09,2017-01-05 01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

答案1

一个很棒的文本处理工具是awk。以下示例在 FreeBSD 11.1 上使用普通标准 awk。如果您更喜欢 GNU awk,@RomanPerekhrest 在另一个答案中有一个优雅的解决方案。

您的输入以逗号分隔。因此,我们awk使用参数进行调用-F,

然后我们可以使用该语句打印出列print$1是第一列。$2是第二列。

$ awk -F, '{ print $8 }' inputfile.csv
2017-1-5 1:07:09
2017-11-25 19:57:17

这为我们提供了每行的第 8 列。

这就是您要操作的日期字段。我们可以将其作为脚本的一部分来设置,而不是使用命令行参数来设置分隔符。 FS 用于输入分隔符,OFS 用于输出分隔符。

$ awk 'BEGIN { FS = "," } ; { print $8 }' inputfile.csv
2017-1-5 1:07:09
2017-11-25 19:57:17

在处理日期时,我通常更喜欢使用dateutil 来确保正确处理它们。我不需要担心我使用的是常规的还是 GNU awk。此外,如果日期解析不正确,我会遇到很大的失败。

有趣的参数是:

-j     Specify we do not want to set the date at all
-f     The format string we use for input
+      The format string we use for output

因此,如果我们运行这个日期:

$ date -j -f "%Y-%m-%d %H:%M:%S" +"%Y-%m-%d %H:%M:%S" "2017-1-5 1:07:09"
2017-01-05 01:07:09

然后我们可以将其与 awk 结合起来。注意引号是怎样的逃脱了。这可能是初学者最大的绊脚石。

$ awk -F, '{ system("date -j -f \"%Y-%m-%d %H:%M:%S\" +\"%Y-%m-%d %H:%M:%S\" \""$8"\"")}' inputfile.csv
2017-01-05 01:07:09
2017-11-25 19:57:17

系统调用似乎是正确的 - 但不幸的是它只允许我们捕获返回码并直接打印到输出。为了避免这种情况,我们使用该cmd | getline模式。以下简单示例将把当前日期读入 mydate:

$ awk 'BEGIN { cmd = "date"; cmd | getline mydate; close(cmd); print mydate }'
Thu Mar  1 16:26:15 CET 2018

我们使用BEGIN关键字是因为我们没有对此简单示例的输入。

那么让我们扩展一下:

awk 'BEGIN { FS=","; OFS=FS };
     { 
         cmd = "date -j -f \"%Y-%m-%d %H:%M:%S\" +\"%Y-%m-%d %H:%M:%S\" \""$8"\"";
         cmd | getline firstdate;
         close(cmd);
         cmd = "date -j -f \"%Y-%m-%d %H:%M:%S\" +\"%Y-%m-%d %H:%M:%S\" \""$9"\"";
         cmd | getline seconddate;
         close(cmd);
         print $1,$2,$3,$4,$5,$6,$7,firstdate,seconddate
     }' inputfile.csv

我们可以将其折叠成一行:

awk 'BEGIN {FS=",";OFS=FS};{cmd="date -j -f \"%Y-%m-%d %H:%M:%S\" +\"%Y-%m-%d %H:%M:%S\" \""$8"\"";cmd | getline firstdate;close(cmd);cmd="date -j -f \"%Y-%m-%d %H:%M:%S\" +\"%Y-%m-%d %H:%M:%S\" \""$9"\"";cmd | getline seconddate;close(cmd);print $1,$2,$3,$4,$5,$6,$7,firstdate,seconddate}' inputfile.csv

这给了我输出:

1111,2222,3333,4444,5555,6666,7777,2017-01-05 01:07:09,2017-01-05 01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

附录

由于这里的目的是学习好习惯,我最好更新这个答案。重复代码是一个坏习惯。当你开始这样做时,你应该将事情分成一个函数。您会注意到下面的代码立即变得更具可读性。

awk 'function convertdate(the_date) {
         cmd = "date -j -f \"%Y-%m-%d %H:%M:%S\" +\"%Y-%m-%d %H:%M:%S\" \""the_date"\"";
         cmd | getline formatted_date;
         close(cmd);
         return formatted_date
     }
     BEGIN { FS=","; OFS=FS };
     { 
         print $1,$2,$3,$4,$5,$6,$7,convertdate($8),convertdate($9)
     }' inputfile.csv

养成这个习惯,您会发现稍后引入错误处理会变得多么容易。

答案2

如果你有 GNU awk,你可以将最后一个字段转换为空格分隔的字段日期规范字符串,然后使用以下命令根据需要重新格式化它strftime

awk 'BEGIN{OFS=FS=","} {gsub(/[-:]/," ",$NF); $NF = strftime("%Y-%m-%d %H:%M:%S", mktime($NF))} 1' file
1111,2222,3333,4444,5555,6666,7777,2017-1-5 1:07:09,2017-01-05 01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

GNU awk 用户指南:时间函数

答案3

简单的 GNUawk解决方案:

awk 'BEGIN{ FS=OFS="," }{ gsub(/\<[0-9]\>/, "0&", $8); gsub(/\<[0-9]\>/, "0&", $9) }1' file
  • gsub(/\<[0-9]\>/, "0&", <field>)- 仅替换/补充其中的独立个位数约会时间细绳:
    • \<\>- 是单词边界
    • &- 代表正则表达式模式匹配的精确子字符串

输出:

1111,2222,3333,4444,5555,6666,7777,2017-01-05 01:07:09,2017-01-05 01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

答案4

日期工具包中有处理时间/日期格式数据的详细信息的代码。

# Utility functions: print-as-echo, print-line-with-visual-space.
pe() { for _i;do printf "%s" "$_i";done; printf "\n"; }
pl() { pe;pe "-----" ;pe "$*"; }

pl " Input data file $FILE:"
head $FILE

pl " Expected output:"
cat $E

pl " Results, to standard format:"
dateutils.dconv -S <$FILE

pl " Results, to standard format, omitting the \"T\":"
dateutils.dconv -S -f '%F %T' <$FILE

生产:

-----
 Input data file data1:
1111,2222,3333,4444,5555,6666,7777,2017-1-5 1:07:09,2017-1-5 1:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

-----
 Expected output:
1111,2222,3333,4444,5555,6666,7777,2017-01-05 01:07:09,2017-01-05 01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

-----
 Results, to standard format:
1111,2222,3333,4444,5555,6666,7777,2017-01-05T01:07:09,2017-01-05T01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25T19:57:17,2017-11-25T19:58:54

-----
 Results, to standard format, omitting the "T":
1111,2222,3333,4444,5555,6666,7777,2017-01-05 01:07:09,2017-01-05 01:11:53
1111,2222,3333,4444,5555,6666,7777,2017-11-25 19:57:17,2017-11-25 19:58:54

在这样的系统上:

OS, ker|rel, machine: Linux, 3.16.0-4-amd64, x86_64
Distribution        : Debian 8.9 (jessie) 
bash GNU bash 4.3.30
dateutils.dconv dconv 0.3.1

dconv 的一些详细信息:

dateutils.dconv Convert DATE/TIMEs between calendrical systems. (man)
Path    : /usr/bin/dateutils.dconv
Package : dateutils
Home    : http://www.fresse.org/dateutils
Version : 0.3.1
Type    : ELF 64-bit LSB shared object, x86-64, version 1 ( ...)
Help    : probably available with -h,--help
Home    : https://github.com/hroptatyr/dateutils (doc)

最美好的祝愿...干杯,drl

相关内容