我有一个大文件,其中列出了一长列 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
被错误地舍入,显示的下一个数字4
是8
,它应该被舍入为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.aaa
,myprefix.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
正如您从最后一个条目中看到的,您将得到略有不同的舍入结果。