【问题标题】:Grep: Extract data from one file to update anotherGrep:从一个文件中提取数据以更新另一个文件
【发布时间】:2012-12-20 03:52:39
【问题描述】:

我正在编写一个 perl 脚本,它从一个带有消息定义的文件中提取值并使用它们来更新配置文件。

例如:

消息定义文件(ICD.txt):

MESSAGE: FOO_TELEM
latitude [-90,90]
longitude [-180,180]
MESSAGE: FOO_FREQUENCY
airPressure [0,50]
engineSpeed [0,65500]

其中 FOO_TELEM 消息的成员纬度范围为 -90 到 90,经度范围为 -180 到 180。

配置文件(Config.txt):

  MessageTable
    Message FOO_TELEM
      Member latitude  DOUBLE End-Member
      Member longitude DOUBLE End-Member
    End-Message
    Message FOO_FREQUENCY
      Member airPressure ULONG End-Member
      Member engineSpeed ULONG End-Member
    End-Message
  End-MessageTable

我希望能够使用消息定义文件 (ICD.txt) 中包含的约束值更新配置文件 (Config.txt),以便结果如下所示:

更新的配置文件(Config.txt):

  MessageTable
    Message FOO_TELEM
      Member latitude  DOUBLE CONSTRAINT -90 90 End-Member
      Member longitude DOUBLE CONSTRAINT -180 180 End-Member
    End-Message
    Message FOO_FREQUENCY
      Member airPressure ULONG CONSTRAINT 0 50 End-Member
      Member engineSpeed ULONG CONSTRAINT 0 65500 End-Member
    End-Message
  End-MessageTable

我尝试了各种形式的 grep 来获得我需要的结果,但都没有成功。任何建议将不胜感激。我愿意接受任何涉及 grep、sed 和/或 perl 的解决方案。

【问题讨论】:

  • 正则表达式会起作用吗?

标签: perl sed grep extract


【解决方案1】:

这是一个镜头。我将对我的文件重复使用DATA,因为我不应该编写打开/关闭逻辑。

use strict;
use warnings;

my %messages;
my $current;
CONSTRAINT:
while ( <DATA> ) { 
    last CONSTRAINT if m/^---$/;
    if ( my ( $message ) = m/^ MESSAGE: \s+ ( \S+ )/x ) { 
        $messages{ $message } = $current = {};
    }
    elsif ( my ( $name, $min, $max ) 
               = m/^ (\w+) \s+ \[ \s* (-?\d+), \s* (-?\d+) \s* \]/x 
          ) { 
      $current->{ $name } = [ $min, $max ];
    }   
}
while ( <DATA> ) { 
    chomp;
    if ( my ( $msg ) = m/Message \s+ ( \S+ )/x ) { 
        $current = $messages{ $msg };
    }
    elsif (   ref( $current )
          and my ( $before, $member, $after ) 
                  = m/^( \s* Member \s+ ( \w+ ) \s+ \w+ ) \s+ (.*) /x 
          ) {
        if ( my $vals = $current->{ $member } ) { 
            $_ = "$before CONSTRAINT @$vals $after";
        }
    }
    say;
}

__DATA__
MESSAGE: FOO_TELEM
latitude [-90,90]
longitude [-180,180]
MESSAGE: FOO_FREQUENCY
airPressure [0,50]
engineSpeed [0,65500]
---
MessageTable
  Message FOO_TELEM
    Member latitude  DOUBLE End-Member
    Member longitude DOUBLE End-Member
  End-Message
  Message FOO_FREQUENCY
    Member airPressure ULONG End-Member
    Member engineSpeed ULONG End-Member
  End-Message
End-MessageTable

【讨论】:

    【解决方案2】:

    首先,编写一个解析器,将数据加载到如下数据结构中:

    my %data = (
       FOO_TELEM => {
          latitude  => [  -90,  90 ],
          longitude => [ -180, 180 ],
       },
       FOO_FREQUENCY => {
          latitude  => [ 0,    50 ],
          longitude => [ 0, 65500 ],
       },
    );
    

    然后,为您的数据定义格式编写解析器。唯一的补充是在找到End-Member 时让它查找$data{$message_name}{$member_name}

    【讨论】:

      【解决方案3】:

      这些是否有某种标准格式?那会很有帮助。例如,如果您的 Config.txt 文件是 XML 格式,它看起来像这样:

      <messageTable>
          <message name="FOO_TELEM">
              <member name="latitude" type="DOUBLE"/>
              <member name="longitude" type="DOUBLE"/>
          </message>
          <message name="FOO_FREQUENCY">
              <member name="airPressure" type="ULONG"/>
              <member name="engineSpeed" type="ULONG"/>
          </message>
      </messageTable>
      

      如果您的文件不是任何特定的标准格式,您能否将它们转换为标准格式?不一定是 XML,YAML 也可以。

      我问的原因是 Perl 有大量的模块可以快速解析这些标准格式,从而使一切变得如此容易操作。如果没有,您将不得不手动解析数据以提取信息。

      最简单的做法是解析您的表并在 Perl 中创建一个复杂的数据结构来存储您的ICD.txt 文件的信息。 Perl 具有三种标准数据类型,标量(变量如$foo)、数组(变量如@foo)和散列(变量如%hash)。这些数据类型中的每一个都处理单独的值。标量只能包含一个单独的值,而数组和哈希处理这些值的列表。

      要处理更复杂的结构,需要使用Perl References。引用允许您拥有散列的散列或数组的数组,或数组的散列,或散列的数组等。

      例如:

      use strict;
      use warnings;
      use feature qw(say);
      use autodie;
      use Data::Dumper;
      
      open my $icd_fh, "<", "icd.txt";
      
      my %icd_data;
      my $message;
      while (my $line = <$icd_fh>) {
          if ($line =~ /^MESSAGE: (.*)/) {
              $message = $1;
          }
          else {
              $line =~ /(.*) \[(.*),(.*)\]/;
              my $message_type = $1;
              my $lower_limit = $2;
              my $upper_limit = $3;
              if (not exists $icd_data{$message}) {
                  $icd_data{$message} = {};
              }
              $icd_data{$message}->{$message_type} = {};
              $icd_data{$message}->{$message_type}->{LOWER} = $lower_limit;
              $icd_data{$message}->{$message_type}->{UPPER} = $upper_limit;
          }
      }
      say Dumper \%icd_data;
      

      这将获得这种形状的 ICD 数据:

      $VAR1 = {
                  'FOO_TELEM' => {
                      'longitude' => {
                          'LOWER' => '-180',
                          'UPPER' => '180'
                       },
                       'latitude' => {
                            'LOWER' => '-90',
                            'UPPER' => '90'
                       }
                  },
                  'FOO_FREQUENCY' => {
                      'airPressure' => {
                          'LOWER' => '0',
                          'UPPER' => '50'
                      },
                      'engineSpeed' => {
                          'LOWER' => '0',
                          'UPPER' => '65500'
                      }
                }
          };
      

      从那里,您应该能够解析 Config.txt 文件的行,并使用您需要的数据修改它们。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-03
        • 1970-01-01
        • 2013-09-10
        • 2017-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多