awk "日期" | getline var 缓存它的值 - 但只是有时

roa*_*ima 9 awk

作为更大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 }'
Run Code Online (Sandbox Code Playgroud)

所有相同的结果,即使我确认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<<
Run Code Online (Sandbox Code Playgroud)

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

一切似乎都很好。缓存(如果是这样的话)被禁用,我从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<<
Run Code Online (Sandbox Code Playgroud)

我预计第 3 行要么导致对命令的新评估(提供新的日期值),要么重复第一行中的值。两者都不会发生。

这让我很难过。我不明白为什么我在第 2-5 行得到相同的值。改变f来自12明确禁止这是怎么回事任何缓存。但是f2back更改为1并没有给我第一个的缓存副本f=1,而是重复了f=2. 将命令字符串更改为新值并f=3触发对 的新调用date

为什么?

ilk*_*chu 8

GNU awk 的手册提到

如果在执行 awk 程序期间多次将相同的文件名或相同的 shell 命令与 getline 一起使用(请参阅使用 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<<
Run Code Online (Sandbox Code Playgroud)

如果我们将date此处的命令替换为记录运行时间的内容,我们还可以看到记录显示它只执行一次。

getline 也确实在 EOF 时返回零,错误时返回 -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
Run Code Online (Sandbox Code Playgroud)

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

使用"date; : " NR | getline x;,所有命令行都是不同的,因此每个命令行都有一个单独的管道。

使用"date; : " $1 | getline x;,当$1重复时,您会遇到与第一种情况相同的问题,对同一管道的第二次读取会遇到 EOF。

  • 因此,如果 getline 无法获取一行,它会使目标变量保持不变?啊! 曙光初现!谢谢 (2认同)
  • @roaima,显然是这样。我认为你应该检查返回值:) (2认同)
  • `("date" | getline x &gt; 0)` 是不明确的,它需要是 `(("date" | getline x) &gt; 0)` 才能移植。请参阅 http://awk.freeshell.org/AllAboutGetline 了解何时/如何使用 getline。 (2认同)