在日期和时间中添加前导零

Gus*_*ler 3 awk text-processing text-formatting

我有一个包含以下数据结构的 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
Run Code Online (Sandbox Code Playgroud)

我想将日期月和日显示为始终为 2 位数。我还希望时间小时字段始终为 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
Run Code Online (Sandbox Code Playgroud)

Cla*_*sen 8

一个很好的文本处理工具是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
Run Code Online (Sandbox Code Playgroud)

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

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

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

在处理日期时,我通常更喜欢使用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
Run Code Online (Sandbox Code Playgroud)

因此,如果我们在某个日期运行它:

$ 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
Run Code Online (Sandbox Code Playgroud)

然后我们可以将它与 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
Run Code Online (Sandbox Code Playgroud)

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

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

我们使用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
Run Code Online (Sandbox Code Playgroud)

我们可以将其折叠为单行:

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
Run Code Online (Sandbox Code Playgroud)

这给了我输出:

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
Run Code Online (Sandbox Code Playgroud)

附录

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

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
Run Code Online (Sandbox Code Playgroud)

养成这样的习惯,您会注意到稍后引入错误处理会变得多么容易。


ste*_*ver 5

如果您有 GNU awk,则可以将最终字段转换为以空格分隔的datespec字符串,然后根据需要使用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
Run Code Online (Sandbox Code Playgroud)

请参阅GNU awk 用户指南:时间函数


Rom*_*est 5

简单的 GNUawk解决方案:

awk 'BEGIN{ FS=OFS="," }{ gsub(/\<[0-9]\>/, "0&", $8); gsub(/\<[0-9]\>/, "0&", $9) }1' file
Run Code Online (Sandbox Code Playgroud)
  • 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
Run Code Online (Sandbox Code Playgroud)