我有一个文件看起来像
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
0.2000000000000028 0.2000000000000028 0.2000000000000028
0.2967599999999990 0.0641000000000034 0.1551499999999990
0.1033699999999982 0.3361099999999979 0.244990000000001
我需要一个脚本来修改底部的数字块(低于C
其原始值 30。有没有这样的方法可以做到这一点?
到目前为止,我得到的最好的是
$ awk '{if(NR>1){for(i=2;i<=NF;i++){$(i)=$(i)-10;}}print;}' data.txt | column -t
但是,那是从网上找来的,我不确定如何自己操作才能达到预期的效果。但是,这不会打印/覆盖当前的内容data.txt
,而这正是我想要的。
谢谢您的帮助!
答案1
如下所示data.awk
:
{
if (matched) {
for (i = 1; i <= NF; i++) {
$(i) = 30.0 - $(i)
}
}
print
}
/^C/ { matched = 1 }
BEGIN { CONVFMT = "%.20f" }
你得到:
$ awk -f data.awk data.txt
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
29.79999999999999715783 29.79999999999999715783 29.79999999999999715783
29.70324000000000097543 29.93589999999999662350 29.84485000000000098908
29.89663000000000181444 29.66389000000000208956 29.75500999999999862666
您的输入显然存在精度问题。因此您可能需要调用bc
命令进行实际计算(它支持任意精度):
{
if (matched) {
for (i = 1; i <= NF; i++) {
cmd = "echo 30.0 - " $(i) " | bc"
cmd | getline $(i)
close(cmd)
}
}
print
}
/^C/ { matched = 1 }
结果:
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
29.7999999999999972 29.7999999999999972 29.7999999999999972
29.7032400000000010 29.9358999999999966 29.8448500000000010
29.8966300000000018 29.6638900000000021 29.755009999999999
要用结果覆盖data.txt
,通常需要将其写入另一个文件,然后将其重命名为原始文件。
$ awk -f data.awk data.txt > data.txt.out
$ mv data.txt.out data.txt
或者sponge
使用更多工具。
$ sudo apt-get install moreutils
$ awk -f data.awk data.txt | sponge data.txt
答案2
这是我的 awk 版本:
awk '/^C/,0 {for (i=1;i<=NF;i++) { if ( $i != "C" ) printf "%.16f ",$i-30.0000};print"\n" }' data.txt
这里我们获取从 C 字符到文件末尾的所有内容,在每一列中减去 30,添加换行符,然后重复该过程。显然,添加 if 语句是为了避免从 C 中减去 30。
输出如下:
46)serg@ubuntu[/home/xieerqi]
>_ awk '/^C/,0 {for (i=1;i<=NF;i++) { if ( $i != "C" ) printf "%.16f ",$i-30.0000};print"\n" }' data.txt
-29.7999999999999972 -29.7999999999999972 -29.7999999999999972
-29.7032400000000010 -29.9358999999999966 -29.8448500000000010
-29.8966300000000018 -29.6638900000000021 -29.7550099999999986
这可以在原始文件中替换;或者,我们可以尝试使用 BEGIN { } 语句在 C 之前打印内容
答案3
使用python
:
#!/usr/bin/env python2
import decimal
with open('/path/to/data.txt') as f:
for line in f:
if line.rstrip() == 'C':
print line.rstrip()
break
else:
print line.rstrip()
for line in f:
print '\t'.join(['{0:.16f}'.format(decimal.Decimal(30 - float(part))) for part in line.rstrip().split()])
输出 :
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
29.7999999999999972 29.7999999999999972 29.7999999999999972
29.7032400000000010 29.9358999999999966 29.8448500000000010
29.8966300000000018 29.6638900000000021 29.7550099999999986
每次
python
读取文件的一行,指针就会增加一以指向下一行,我们利用这一点来读取并打印仅包含的行C
。对于线条,
C
我们将线条分成几部分line.rstrip().split()
,然后从中减去每一部分,30
以获得所需的结果。为了获得结果浮点数的精度,我们使用了
decimal
模块。
答案4
我无法提供问题的好的解决方案,但我会尝试深入描述问题是什么,并提供部分解决方案。
问题:
机器上的浮点数精度有限:简而言之,只有有限的浮点数子集(每个数量级)可以表示。
机器上的浮点数的表示方式严格遵循规范化符号± significand * base ^ exponent
(其中base
= 表示基数,significand
= 任何 > 0 且 <= 表示基数的实数,并且exponent
= 数量级):例如,在遵循标准的 32 位机器上IEEE 754
,单精度浮点数的表示方式是第一位表示符号,接下来的 8 位表示数量级,最后 23 位表示有效数字,而双精度浮点数的表示方式是第一位表示符号,接下来的 11 位表示数量级,最后 52 位表示有效数字(基数始终为 2,因此不表示)。为此,数字的有效数字必须始终使用 23 位(使用单精度)或 52 位(使用双精度)来表示。
这种在固定位数上表示浮点数的方式的一个特性是,由于每个数量级可表示的有效数字的数量始终相同,因此,相同数量级的可表示浮点数之间的平均“距离”会随着两者数量级的增加而增加。
对于上述情况,第一个问题是,如果浮点数的规范化符号的有效数字不在有限的可表示有效数字集中,则将其四舍五入为最接近(更高或更低)的可表示有效数字。
说到用相同数量级表示的数字,第二个问题是,即使浮点数可以精确表示,在其上加上/减去另一个[精确表示的]浮点数也可能会导致不能精确表示的浮点数,其有效数字将被四舍五入到最接近(更高或更低)的可表示有效数字。
最后,说到用不同数量级表示的数字,第三个问题(主要是由于 CPU 架构)是,为了能够在用不同数量级表示的浮点数之间执行加法/减法,需要首先使用相同的数量级来表示数字; 这意味着需要增加最小的数量级,并且(为了平衡这一点)它的有效数字需要向右移动,从而导致位数的损失超过可用的 23/52; 如果这还不够,那么在数量级上具有显着差异的浮点数一旦被添加/减去,就可能导致绝对值最高的数字,这对于已经提出的问题(差异不足以将不可表示的有效数字向上/向下移动到不同的更高/更低的可表示有效数字)并且随着两个数字的数量级进一步发散,情况会越来越糟。
所有这些的含义是:您永远无法确保使用浮点数学获得准确的结果,但是可以通过使用更高精度的表示来缓解这种情况。
部分解决方案:
对于上述情况,这些单行命令的结果awk
并不精确;这可以通过在命令中使用双精度来缓解printf
,但这不受支持。
这将减少30
第一行匹配之后每行中前 3 个空格分隔的数字的值C
,保持数字的格式。由于awk
Ubuntu 中包含的版本不支持就地编辑,因此您必须使用awk
并将其重定向到使用 运算符的stdout
文件或使用(GNU ) >= ;bash
>
gawk
awk
4.10.0
使用awk
:
awk 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}' data.txt > data_processed.txt
使用gawk
(GNU awk
)> =4.10.0
gawk -i inplace 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}' data.txt
NR==1, $0=="C";
:选择并打印从第一个到第一个匹配项之间的所有记录C
(含);$0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}
:选择第一个匹配项和最后一个匹配项之间的所有记录C
,并打印每个选定的不匹配记录的第 1、第 2 和第 3 个字段,以C
双倍空格分隔,并按30
原始数字的格式减少;
示例输出:
~/tmp$ cat data.txt
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
0.2000000000000028 0.2000000000000028 0.2000000000000028
0.2967599999999990 0.0641000000000034 0.1551499999999990
0.1033699999999982 0.3361099999999979 0.244990000000001
~/tmp$ awk 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}' data.txt
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
-29.7999999999999972 -29.7999999999999972 -29.7999999999999972
-29.7032400000000010 -29.9358999999999966 -29.8448500000000010
-29.8966300000000018 -29.6638900000000021 -29.7550099999999986