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)
一个很好的文本处理工具是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)
在处理日期时,我通常更喜欢使用date
util 来确保我正确处理它们。而且我不需要担心我使用的是普通的还是 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)
养成这样的习惯,您会注意到稍后引入错误处理会变得多么容易。
如果您有 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)
简单的 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)