作为较大脚本的一部分,awk
我需要将任意日期字符串转换为自纪元以来的秒数。这不能作为awk
函数使用,所以我想我可以调用date
每行输入。 (事后看来,我本来可以使用perl
,但让我们放弃这个想法。)
在看到一些意想不到的结果后,我将问题简化为这个(bash
和 GNU awk
)
for f in {1..5}; do echo $f; sleep 2; done | awk '{ "date" | getline x; printf ">>%s<<\n", x }'
所有相同的结果,即使我确认循环awk
确实每两秒只运行一次
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
>>29 Jun 2020 10:38:24<<
也许是getline
缓存。所以我尝试了这个
for f in {1..5}; do echo $f; sleep 2; done | awk '{ "date; : " NR | getline x; printf ">>NR=%d - %s<<\n", NR, x }'
>>NR=1 - 29 Jun 2020 10:44:05<<
>>NR=2 - 29 Jun 2020 10:44:07<<
>>NR=3 - 29 Jun 2020 10:44:09<<
>>NR=4 - 29 Jun 2020 10:44:11<<
>>NR=5 - 29 Jun 2020 10:44:13<<
一切看起来都不错。缓存(如果是这样的话)被禁用,我从date
.
然后,我再次沿着这条路径继续前进,在通过管道传输到的命令中提供重复的值getline
for f in 1 2 1 1 2 3; do echo $f; sleep 2; done | awk '{ "date; : " $1 | getline x; printf ">>NR=%d - f=%d - %s<<\n", NR, $1, x }'
>>NR=1 - f=1 - 29 Jun 2020 10:43:01<<
>>NR=2 - f=2 - 29 Jun 2020 10:43:03<<
>>NR=3 - f=1 - 29 Jun 2020 10:43:03<<
>>NR=4 - f=1 - 29 Jun 2020 10:43:03<<
>>NR=5 - f=2 - 29 Jun 2020 10:43:03<<
>>NR=6 - f=3 - 29 Jun 2020 10:43:11<<
我预计第 3 行要么导致命令的新评估(提供新的日期值),要么重复第一行的值。两者都没有发生。
这让我难住了。我不明白为什么我在第 2-5 行得到相同的值。f
从更改1
为2
明显禁用了任何正在进行的缓存。但是f
从2
back 更改为1
并没有给我第一个的缓存副本f=1
,而是重复了 的值f=2
。将命令字符串更改为新值并f=3
触发对 的新调用date
。
为什么?
答案1
getline
如果在 awk 程序执行期间多次使用相同的文件名或相同的 shell 命令(请参阅显式输入getline
),仅在第一次打开文件(或执行命令)。此时,将从该文件或命令中读取第一条输入记录。下次使用相同的文件或命令时getline
,将从中读取另一条记录,依此类推。
因此它只运行命令一次,并在进一步读取时获取 EOF,保持旧值x
不变。比较一下如果我们x
在每次阅读后都将其丢弃会发生什么:
$ for f in {1..3}; do echo $f; sleep 2; done |
awk '{ "date" | getline x; printf ">>%s<<\n", x; x ="done" }'
>>Mon Jun 29 13:37:53 EEST 2020<<
>>done<<
>>done<<
如果我们date
用保存命令运行时间记录的命令替换此处的命令,我们还可以看到记录显示它只执行一次。
getline
在 EOF 处返回 0,在出错时返回 -1,因此我们可以检查:
$ for f in {1..3}; do echo $f; sleep 2; done |
awk '{ if (("date" | getline x) > 0) printf ">>%s<<\n", x;
else printf "error or eof\n"; }'
>>Mon Jun 29 13:46:58 EEST 2020<<
error or eof
error or eof
您需要close()
显式地使用管道才能让 awk 下次重新打开它。
$ for f in {1..3}; do echo $f; sleep 2; done |
awk '{ "date" | getline x; printf ">>%s<<\n", x; x = "done"; close("date") }'
>>Mon Jun 29 13:39:19 EEST 2020<<
>>Mon Jun 29 13:39:21 EEST 2020<<
>>Mon Jun 29 13:39:23 EEST 2020<<
使用"date; : " NR | getline x;
,所有命令行都是不同的,因此每个命令行都有一个单独的管道。
使用 时"date; : " $1 | getline x;
,当$1
重复时,您会遇到与第一种情况相同的问题,对同一管道的第二次读取会遇到 EOF。
答案2
我不清楚“任意格式化”的含义,但 GNU/awk 时间例程可以执行 date 命令可以执行的任何操作,甚至更多。如果您愿意展示您的实际输入,我可以解释如何将此演示应用到实际应用程序中。
此脚本演示如何将文本日期从某种任意顺序(包括月份名称)转换为 datespec 格式,然后转换为自纪元以来的秒数(然后使用外部日期命令验证),然后转换为 ISO 格式,然后通过以下方式进行调整:任意月份、日期和分钟。
剧本:
#! /bin/bash
AWK='
BEGIN {
#.. Set up conversion from month names to numeric.
split ("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", V, / /);
for (k in V) iMth[V[k]] = k;
#.. These are only needed to verify the epoch using /bin/date.
fmtSys = "echo -n \047/bin/date: \047; date -d \047 %s + %d secs\047 \047%s\047\n";
Base = "1970-01-01 00:00:00 UTC";
Date = "+%Y-%m-%d %T";
}
function Show (V, Local, ds, epoch) {
printf ("\n");
ds = sprintf ("%d %d %d %d %d %d %d", V[8], V[10], V[2], V[4], V[5], V[6], -1);
epoch = mktime (ds);
printf ("datespec: %s; epoch: %d\n", ds, epoch);
printf ("ISO: %s\n", strftime ("%F %T %Z Week %W Day %w", epoch));
#.. Call date command to verify.
system (sprintf (fmtSys, Base, epoch, Date));
}
function Fix (tx, Local, ds, V) {
split (tx, V, /[^A-Za-z0-9]/);
V[10] = iMth[V[3]];
Show( V);
printf ("\n.. Go back 10 months and 43 days\n");
V[10] -= 10; V[2] -= 43;
Show( V);
printf ("\n.. and forward 427 minutes\n");
V[5] += 427;
Show( V);
}
{ printf ("\n.. Input date ::%s::\n", $0); }
{ Fix( $0); }
'
printf 'Mon 29 Jun 16:04:42 BST 2020\n' | awk "${AWK}"
考试:
paul $ ./myDate
.. Input date ::Mon 29 Jun 16:04:42 BST 2020::
datespec: 2020 6 29 16 4 42 -1; epoch: 1593443082
ISO: 2020-06-29 16:04:42 BST Week 26 Day 1
/bin/date: 2020-06-29 16:04:42
.. Go back 10 months and 43 days
datespec: 2020 -4 -14 16 4 42 -1; epoch: 1563375882
ISO: 2019-07-17 16:04:42 BST Week 28 Day 3
/bin/date: 2019-07-17 16:04:42
.. and forward 427 minutes
datespec: 2020 -4 -14 16 431 42 -1; epoch: 1563401502
ISO: 2019-07-17 23:11:42 BST Week 28 Day 3
/bin/date: 2019-07-17 23:11:42
paul $
答案3
在处理包含 ISO8601 格式的日期字段的日志条目的文件时,我们遇到了类似的问题我们试图找到连续行之间的差异,以找出哪个步骤花费时间
以下是最初尝试并在某些地方出现问题的代码
BEGIN {
FS="|"
}
{
#Not Working Script - Reason close() command was not done in date function
"date -d " "\"" $1 "\"" " \"" "+%s%3N" "\""| getline curr_rec_ts;
close(curr_rec_ts)
elapsed=curr_rec_ts-prev_rec_ts
print prev_rec_ts"|"curr_rec_ts"|"elapsed
prev_rec_ts=curr_rec_ts
prev_rec=$1"|"$7"|"$8"|"$12
}END {
}
输入文件包含如下记录
# Input File
2024-03-08T18:34:09,669
2024-03-08T18:34:09,679
2024-03-08T18:34:09,679
2024-03-08T18:34:09,621
2024-03-08T18:34:09,621
2024-03-08T18:34:09,621
2024-03-08T18:34:09,667
2024-03-08T18:34:09,667
2024-03-08T18:34:09,668
2024-03-08T18:34:09,668
2024-03-08T18:34:09,669 // Many more
2024-03-08T18:34:09,669
2024-03-08T18:34:09,669
2024-03-08T18:34:09,669
2024-03-08T18:34:09,669 //Issue occured at this comparison
2024-03-08T18:34:09,861
2024-03-08T18:34:09,861
给出的差异是 193 毫秒,理想情况下应该是 192
问题是由于 getline 缓存重复的日期调用并为后续操作提供类似的响应
解决方案是
#Apply Command and store in a variable
cmd="date -d " "\"" $1 "\"" " \"" "+%s%3N" "\""
#
cmd| getline curr_rec_ts;
# Key was this Below
close(cmd)
摘要 以下内容不适用于 close
情况1
Getline:: "date -d " "\"" $1 "\"" " \"" "+%s%3N" "\""| getline curr_rec_ts;
Close :: close("date") Didnt work as Original Command is different
案例2
Getline :: "date -d " "\"" $1 "\"" " \"" "+%s%3N" "\""| getline curr_rec_ts;
Close :: close(curr_rec_ts) // Didnt work as it as no mean to close variable
案例3
GetLine :: cmd="date -d " "\"" $1 "\"" " \"" "+%s%3N" "\""
Close :: close(cmd) // work as full command was passed.