awk getline from a Pipe '"cmd" | awk getline 来自管道 '"cmd" | getline var' 缓存其值

awk getline from a Pipe '"cmd" | awk getline 来自管道 '"cmd" | getline var' 缓存其值

作为较大脚本的一部分,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从更改12明显禁用了任何正在进行的缓存。但是f2back 更改为1并没有给我第一个的缓存副本f=1,而是重复了 的值f=2。将命令字符串更改为新值并f=3触发对 的新调用date

为什么?

答案1

GNU awk 的手册提到那:

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.

相关内容