解释:

解释:

我有一个大文件,其中列出了一长列 unix 乘以每行一个值,以 0.01 秒的间隔递增。对于一天的数据,这相当于 864 万行。

135699840000
135699840001
135699840002
135699840003
135699840004

我想对此文件的每一行运行一个命令,计算每行的序列日期号 - 来自 matlab 用于时间的参考年 01/01/0000 的日计数器。

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003472
735235.0000004629

我是编码新手,但已设法使用 while 循环使其工作。然而,这是极其低效的,并且需要几个小时才能运行。

while read epochtimerange; do
echo "scale=10; (($epochtimerange/(100*86400))+719529)" |bc
done < epochtimerangetmp.txt > serialdaterangetmp.txt

我认为必须有一种方法可以使用 awk 运行它,但我无法让它工作。重要的是我能够在输出中保持小数点后 10 位的精度。

有人可以帮助我吗?谢谢。

答案1

众所周知,shell 的处理速度非常慢。
您所要求的可以在 shell 中实现,如下所示:

#!/bin/bash
while read line; do
    bc <<<"scale=10;($line/(100*86400))+719529"
done <datafile

处理1000行大约需要1.1秒。
全部864万张大约需要2小时41分钟。

此外,bc 的数值结果四舍五入不正确。
您的示例中的五行将产生以下值:

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003472
735235.0000004629

让我们将精度更改为 20 以查看更多数字:

735235.00000000000000000000
735235.00000011574074074074
735235.00000023148148148148
735235.00000034722222222222
735235.00000046296296296296

例如,以 结尾的第三个数字2314被错误地舍入,显示的下一个数字48,它应该被舍入为5

AWK

使用 awk 我们可能有更快的解决方案。在 awk 中实现您所要求的内容将如下所示:

$ awk '{printf ("%.10f\n",($0/(100*86400))+719529)}' datafile

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003473
735235.0000004630

处理 1000 行只需要 0.006(6 毫秒)。全部 864 万行应该在大约 50 秒内处理完毕。
但 awk 已经超出了它的精度范围。默认情况下,它使用 64 位浮点值表示。那个代表精度大约为 15 位小数。您的数据结果的整数部分为 6 位,小数部分只能估计为正确到第 8 位。
事实上,如果我们尝试扩展位数:

awk '{printf ("%.20f\n",($0/(100*86400))+719529)}' datafile

我们得到的只是噪音:

735235.00000000000000000000
735235.00000011571682989597
735235.00000023143365979195
735235.00000034726690500975
735235.00000046298373490572

与更精确的 bc 结果进行比较:

735235.00000000000000000000
735235.00000000000000000000

735235.00000011571682989597
735235.00000011574074074074

735235.00000023143365979195
735235.00000023148148148148

735235.00000034726690500975
735235.00000034722222222222

735235.00000046298373490572
735235.00000046296296296296

为了真正解决这个问题,我们需要一个更精确的awk。

多精度AWK

如果您使用 GNU awk(我在这里将其称为 gawk)并且它是用 MPFR(多精度浮点库)编译的,您可以获得更高的精度。

检查您的 awk 是否有该库(只需询问其版本):

$ awk --version
GNU Awk 4.1.3, API: 1.1 (GNU MPFR 3.1.5, GNU MP 6.1.1)
Copyright (C) 1989, 1991-2015 Free Software Foundation.

并修改 awk 命令以使用可用的精度:

gawk -M -v PREC=100 '{printf ("%.20f\n",($0/(100*86400))+719529)}' datafile

735235.00000000000000000000
735235.00000011574074074074
735235.00000023148148148148
735235.00000034722222222222
735235.00000046296296296296

结果与高精度bc的结果相同。
在这种情况下,我们得到了 awk 的速度和 bc 的精度。

您要求的 10 位十进制数字的最终命令是:

gawk -M -v PREC=100 '{printf ("%.10f\n",($0/(100*86400))+719529)}' datafile

735235.0000000000
735235.0000001157
735235.0000002315
735235.0000003472
735235.0000004630

所有值均正确舍入。

答案2

简单的方法:使用ex修改行并将整个缓冲区(修改后的文件)传递给bc.然后打印修改后的版本。

printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' %p 'q!' | ex file.txt

示例文件的输出:

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003472
735235.0000004629

或者保存更改而不仅仅是打印它们:

printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' x | ex file.txt

解释:

要查看传递给 的命令ex,请printf单独运行该命令:

$ printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' %p 'q!'
%s:.*:&/8640000+719529:
0a
scale=10
.
%!bc
%p
q!

ex现在让我们将它们分解为命令。第一个比较复杂,所以我专门格式化解释:

%s:.*:&/8640000+719529:
%  - For every line of the buffer (file)
 s  - Run a substitute command
  :  - Using ':' as the regex delimiter
   .*  - Match each entire line
     :  - and replace with
      &  - The entire line, followed by
       /8640000+719529  - this text
                      :  - End command

0a表示“在第 0 行之后追加文本”,换句话说,在缓冲区(文件)的开头。

文本scale=10是要附加的文字文本。

一行.本身结束“追加”命令。

该命令%!bc将整个缓冲区的内容作为标准输入传递给外部命令bc,并用生成的输出替换整个缓冲区。

%p打印整个缓冲区(到标准输出)的方法。

q!表示退出而不保存更改。


如果你有一个非常非常大文件有数千万行,这显然会带来麻烦。我已经研究了这种使用的可能解决方案ex,并且有一些方法可以完成了,但我最终放弃了这种方法,转而采用一种非常非常简单的方法,该方法仍然只使用POSIX指定工具

使用split将文件分割成块,然后对每个块运行之前指定的命令以及cat生成的输出:

split -l 1000000 -a 3 file.txt myprefix.
for f in myprefix.???; do
  printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' %p 'q!' |
    ex "$f"
done > myoutputfile.txt
rm myprefix.???

split此处使用该命令将其拆分file.txt为多个块,每个块的长度为一百万行(当然,其余部分也放入文件中)。由于-a 3已指定,块上的后缀将为 3 个字符长。 myprefix.aaamyprefix.aab, ETC。

然后可以单独处理每个文件ex,并且无需保存更改,因为我们只需将整个循环的输出重定向到myoutputfile.txt(然后删除块文件,以保持整洁)。

答案3

在 shell 中执行此操作会非常非常慢。

$ awk '{printf "%.10f\n", (($1/(100*86400))+719529)}' filename
735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003473
735235.0000004630

正如您从最后一个条目中看到的,您将得到略有不同的舍入结果。

相关内容