【问题标题】:replace a string of characters with the line number用行号替换字符串
【发布时间】:2013-09-23 12:38:07
【问题描述】:

我有一个大约 3,000 行的文本文件。 99% 的时间我需要全部 3,000 行。但是,我会定期 grep 出我需要的行并将输出定向到另一个文本文件以供使用。

我这样做的唯一问题是:嵌入在文本文件中的是一个 6 字符的数字字符串,表示行号。为了使用文件,这个区域需要正确重新编号...(我不需要重新排序数据,但是我需要用新的行号替换当前的六个字符。并且必须填充零!不幸的是,整行都是一长行数据,没有字段分隔符!

例如,我的前三行可能类似于:

20130918082020ZZ000001RANDOMDATAFOLLOWSAFTERTHISABCDEFGH
20130810112000ZZ000999MORERANDOMDATAFOLLOWSAFTERTHISABCD
20130810112000ZZ000027SILLMORERANDOMDATAFOLLOWSAFTERTHIS

位置 17-22 的六个字符(紧跟“ZZ”),需要根据当前行号重新编号......所以上面需要看起来像:

20130918082020ZZ000001RANDOMDATAFOLLOWSAFTERTHISABCDEFGH
20130810112000ZZ000002MORERANDOMDATAFOLLOWSAFTERTHISABCD
20130810112000ZZ000003SILLMORERANDOMDATAFOLLOWSAFTERTHIS

任何想法将不胜感激!

谢谢, KSL。

【问题讨论】:

  • RANDOMDATAFOLLOWS 会一直是 A-Z 还是数字?
  • 行号后面的文本是格式化的STRING数据和NUMBERS。读取此文件的软件解析出数据并将数据导入数据库中的各种表中。如果我的序列不正常或有序,软件会检测到这一点并进行更正;但会为每一行不正确的行填充一个错误(在这种情况下,是所有行!)。谢谢。

标签: perl sed awk printf


【解决方案1】:

这是我用 Perl 提出的解决方案。它假定编号始终是ZZ 序列之后的 6 位数字。

在convert.pl中:

use strict; 
use warnings;

my $i = 1; # or the value you want to start numbering
while (<STDIN>) {
    my $replace = sprintf("%06d", $i++);
    $_ =~ s/ZZ\d{6}/ZZ$replace/g;
    print $_;
}

在data.dat中:

20130918082020ZZ000001RANDOMDATAFOLLOWSAFTERTHISABCDEFGH
20130810112000ZZ000999MORERANDOMDATAFOLLOWSAFTERTHISABCD
20130810112000ZZ000027SILLMORERANDOMDATAFOLLOWSAFTERTHIS

运行:

cat data.dat | perl convert.pl

输出

20130918082020ZZ000001RANDOMDATAFOLLOWSAFTERTHISABCDEFGH
20130810112000ZZ000002MORERANDOMDATAFOLLOWSAFTERTHISABCD
20130810112000ZZ000003SILLMORERANDOMDATAFOLLOWSAFTERTHIS

【讨论】:

  • 不客气。请接受答案,以便其他人知道您的问题已解决。
  • +1。如果您将while (&lt;STDIN&gt;) 更改为while (&lt;&gt;),除了您的样式之外,您还可以像perl convert.pl data.dat 这样调用您的脚本。
  • 对于打高尔夫球的人群,这可以归结为:perl -pe 's/ZZ\K\d{6}/sprintf "%06d",++$i/e'
  • 呵呵,可以保存2个字符:perl -pe 's/ZZ\K\d{6}/sprintf "%06d",$./e'
【解决方案2】:

如果我能解决这个问题,我会创建一个简单的 Python 脚本,通过像 grep 一样过滤并使用 Python 脚本内部的内部计数器来读取这些行。

作为简单的提示,您可以读取字符串中的每一行并使用 variablename[17:22] 访问它们(17:22 是您尝试使用的字符串的位置)。

现在,python 中的字符串中有一个方法可以进行替换,只需将值替换为您创建的计数器。

我希望这会有所帮助。

【讨论】:

  • 我从来没有用过 Python..."Google'ing" 现在...但是看起来很复杂;我希望使用 SED 或 awk,但不确定如何填充 6 个字符...感谢您的及时回复! KSL。
【解决方案3】:

在 awk 中执行此操作:

awk '{print substr($0,1,16) sprintf("%06d", NR) substr($0,23)}'

gawk 'match($0,/^(.*ZZ)[0-9]{6}(.*)/,a) {print a[1] sprintf("%06d",NR) a[2]}'

【讨论】:

    【解决方案4】:

    这正是unpack 有用的类型。

    #!/usr/bin/env perl
    use v5.10.0;
    use strict;
    use warnings;
    
    while( my $line = <> ){
      chomp $line;
      my @elem = unpack 'A16 A6 A*', $line;
    
      $elem[1] = sprintf '%06d', $.;
      # $. is the line number for the last used file handle
      say @elem;
    }
    

    实际上看这些行,前14个字符中似乎存储了日期信息。
    假设在某些时候您可能出于某种原因想要解析这些行,您可以使用以下示例来说明如何使用 unpack 来拆分这些行。

    #!/usr/bin/env perl
    use v5.10.0; # say()
    use strict;
    use warnings;
    use DateTime;
    
    my @date_elem = qw'
      year month day
      hour minute second
    ';
    my @elem_names = ( @date_elem, qw'
      ZZ
      line_number
      random_data
    ');
    
    while( my $line = <> ){
      chomp $line;
      my %data;
      @data{ @elem_names } = unpack 'A4 (A2)6 A6 A*', $line;
    
      # choose either this:
    
      $data{line_number} = sprintf '%06d', $.;
      say @data{@elem_names};
    
      # or this:
    
      $data{line_number} =  $.;
      printf '%04d' . ('%02d'x5) . "%2s%06d%s\n", @data{ @elem_names };
    
      # the choice will affect the contents of %data
    
      # this just shows the contents of %data
      for( @elem_names ){
        printf qq'%12s: "%s"\n', $_, $data{$_};
      }
    
      # you can create a DateTime object with the date elements
      my $dt = DateTime->new(
        (map{ $_, $data{$_} } @date_elem),
        time_zone => 'floating',
      );
    
      say $dt;
    
      print "\n";
    }
    

    虽然使用正则表达式会更好,这样你就可以丢弃虚假数据。

    use v5.14; # /a modifier
    
    ...
    
    my $rdate = join '', map{"(\\d{$_})"} 4, (2)x5;
    my $rx = qr'$rdate (ZZ) (\d{6}) (.*)'xa;
    
    while( my $line = <> ){
      chomp $line;
      my %data;
      unless( @data{ @elem_names } = $line =~ $rx ){
        die qq'unable to parse line "$line" ($.)';
      }
    
    ...
    

    这样会更好;使用5.10中添加的named capture groups

    ...
    
    my $rx = qr'
      (?<year> \d{4} ) (?<month> \d{2} ) (?<day> \d{2} )
      (?<hour> \d{2} ) (?<minute> \d{2} ) (?<second> \d{2} )
      ZZ
      (?<line_number> \d{6} )
      (?<random_data> .* )
    'xa;
    
    while( my $line = <> ){
      chomp $line;
      unless( $line =~ $rx ){
        die qq'unable to parse line "$line" ($.)';
      }
      my %data = %+;
    
      # for compatibility with previous examples
      $data{ZZ} = 'ZZ';
    
    ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-06
      • 1970-01-01
      • 2019-08-13
      • 2014-09-07
      • 2023-03-23
      • 2018-01-14
      • 2022-11-17
      相关资源
      最近更新 更多