如何调整文本文件中的数字字段

如何调整文本文件中的数字字段

假设我有以下<Tab>分隔的文本文件:

file name    size      owner    
file1.txt    12.345    root
file2.txt    0.172222  user1
file3.txt    2.46e2    user2
file4.txt    12345     root
file5.txt    21        user3
file6.txt    246.0     user1
file name    owner     last modified    last accessed
text4.txt    root      12.73            13.53
text5.txt    user3     15.3333          34
file1.txt    root      23               31.0032

该文件由几个“表”组成,每个表都以标题行开头,然后包含一些数据行。有些列是数字,但每个表可以有不同的数字以及不同类型的列。列的类型事先未知,无法根据表头确定。

表中的数值使用各种格式 - 可能有整数、浮点十进制数或科学记数法的数字。

我的问题是如何将此表中的所有数字字段转换为相同的格式。例如,我可能希望使用"%.2f"printf 格式说明符格式化每个数字字段。当然,其他非数字字段必须保持不变。

另外,我希望能够任意调整(例如添加 42,然后乘以 7)此文件中包含的每个数字字段。

我正在寻找的解决方案应该是基于现场的。它应该扫描整个文件,并且对于每个字段,它应该确定它是否是数字。如果它是数字,它应该打印其调整和格式化的值。否则,应该只打印原件。

我知道可以用 来完成类似的事情awk。但如果我没记错的话,awk用于double数字的内部表示,因此它可能会出现精度和较大值的问题。因此,理想情况下,我想使用其他东西,应该正确处理至少 64 位整数的东西。

有什么简单的方法可以实现这一目标吗?

答案1

Scalar::Utilperl 有一个名为(自 v5.8 起包含在 perl 中)的模块,该模块有一个名为 的有用函数looks_like_number(),可用于检测字段是否为数字。

looks_like_number并不完美,但已经相当不错了。

一个简单的 Perl 程序来完成你想要的事情的简单轮廓可能看起来像这样:

#! /usr/bin/perl

use Scalar::Util qw(looks_like_number);

while(<>) {
  chomp;
  my @fields=split("\t");
  foreach my $f (0..scalar @fields-1) {
    if (looks_like_number($fields[$f])) {
      $fields[$f] += 42;
      $fields[$f] *= 7;
      $fields[$f] = sprintf("%.2f",$fields[$f]);
    }
  }
  print join("\t",@fields),"\n";
}

如果将上面的示例数据作为输入,它会打印以下内容:

file name   size    owner    
file1.txt   380.41  root
file2.txt   295.21  user1
file3.txt   2016.00 user2
file4.txt   86709.00    root
file5.txt   441.00  user3
file6.txt   2016.00 user1
file name   owner   last modified   last accessed
text4.txt   root    383.11  388.71
text5.txt   user3   401.33  532.00
file1.txt   root    455.00  511.02

这是该脚本的另一个版本,它使用 Math::BigFloat 进行所有计算,将小数四舍五入为 2 位数字。

#! /usr/bin/perl

use Scalar::Util qw(looks_like_number);
use Math::BigFloat;

while(<>) {
  chomp;
  my @fields=split("\t");
  foreach my $f (0..scalar @fields-1) {
    if (looks_like_number($fields[$f])) {
      my $BF = Math::BigFloat->new($fields[$f]);
      $BF->badd(42);
      $BF->bmul(7);
      $BF->ffround(-2);

      $fields[$f] = $BF->bstr();
    }
  }
  print join("\t",@fields),"\n";
}

输入示例:

file name   owner   last modified   last accessed
text4.txt   root    12.73   13.53
text5.txt   user3   15.3333 34
file6.txt   root    903709792518875002.42857142857142857142 903709792518875002
file7.txt   root    6659166111488656281486807152009765625   539422123247359763587428687890625

输出:

file name   owner   last modified   last accessed
text4.txt   root    383.11  388.71
text5.txt   user3   401.33  532.00
file6.txt   root    6325968547632125311.00  6325968547632125308.00
file7.txt   root    46614162780420593970407650064068359669.00   3775954862731518345112000815234669.00

相关内容