读取两个YAML文件并比较两者中出现的模块

问题描述 投票:2回答:1

我试图比较两个YAML文件如下

  • 忽略所有注释行
  • 如果文件具有相同的模块,则比较这些模块的版本
  • 如果版本不同,则将差异打印到文件output.log并引发错误

file1.yml

Modules:
     python:
         PATH: /cfg/python/version-1.0
     c:
         PATH: /cfg/c/release-1.2.0
     c++:
         PATH: /cfg/c++/release-1.1.5
     java:
         PATH: /cfg/java/version-2.157
     #connect:
     #    PATH: /cfg/connect/release-1.2.3

file2.yml

Modules:
     python:
         PATH: /cfg/python/version-1.1
     c:
         PATH: /cfg/c/release-1.2.0
     java:
         PATH: /cfg/java/version-2.161
     eclipse:
         PATH: /cfg/eclipse/version-4.5
     #connect:
     #    PATH: /cfg/connect/release-1.2.0

Desired output

Error:
  File1.yml - python: PATH: /cfg/python/verison-1.0
  file2.yml - python: PATH: /cfg/python/verison-1.1
Error:
  file1.yml - java: PATH: /cfg/java/version-2.157
  file2.yml - java: PATH: /cfg/java/version-2.161

My code

use strict;
use warnings;

open f1, "file1.yml" or die "couldn't open the file: $! \n ";
my @line1 = <f1>;
close(f1);

open f2, "file2.yml" or die "couldn't open the file: $! \n ";
my @line2 = <f2>;
close(f2);

open( OUT, ">", "error.txt" );

for ( my $i = 0; $i < @line1; $i++ ) {

    for ( my $j = 0; $j < @line2; $j++ ) {

        if ( $line1[$i] =~ $line2[$j] ) {
            print OUT "Match Found: \n $line1[$i] \n $line2[$j]";
        }
    }
}
close OUT;    

output

Match Found:
    Modules:
    Modules:
 Match Found:
    python:
    python:
 Match Found:
    c:
    c: 
 Match Found:
    java:
    java:
 Match Found:
    #connect:
    #connect:
perl yaml difference
1个回答
4
投票

从技术上讲,你的程序没有任何问题,所以我不会提供修复或解释。您编写的代码与您尝试解决的代码不同。

相反,我将逐步向您展示一种不同的方法。首先,请记住YAML是一种数据结构描述。您可以将其读入Perl并将其转换为Perl可以理解的本机数据结构,而不是使用文本比较。你的文件都有类似的结构,这使得这很简单。

读取数据结构并将其转换为不同的格式很难。幸运的是,Perl的许多优点之一是已经解决了很多问题,并且可以在CPAN上使用。

use strict;
use warnings;
use YAML 'LoadFile';

my $file1 = LoadFile('file1.yml');
my $file2 = LoadFile('file2.yml');

该程序将读取您的文件并使用LoadFile from the YAML module将YAML数据结构内部转换为Perl哈希引用。

就其本身而言当然不是很有用。那么让我们来看看这些结构是什么样的。 Data::Dumper对此非常有用,它随您的Perl安装而来。 (我个人会使用Data::Printer,因为我认为输出更具可读性,但这取决于你)。

use Data::Dumper;

print Dumper $file1;
print Dumper $file2;

这将告诉我们:

$VAR1 = {
          'Modules' => {
                       'java' => {
                                 'PATH' => '/cfg/java/version-2.157'
                               },
                       'c' => {
                              'PATH' => '/cfg/c/release-1.2.0'
                            },
                       'c++' => {
                                'PATH' => '/cfg/c++/release-1.1.5'
                              },
                       'python' => {
                                   'PATH' => '/cfg/python/verison-1.0'
                                 }
                     }
        };
$VAR1 = {
          'Modules' => {
                       'c' => {
                              'PATH' => '/cfg/c/release-1.2.0'
                            },
                       'java' => {
                                 'PATH' => '/cfg/java/version-2.161'
                               },
                       'python' => {
                                   'PATH' => '/cfg/python/verison-1.1'
                                 },
                       'eclipse' => {
                                    'PATH' => '/cfg/eclipse/version-4.5'
                                  }
                     }
        };

请注意密钥的顺序与文件中的顺序不同。那是因为Perl中的哈希值是无序的。这是一个功能。为了获得始终相同的输出,我们需要在以后使用sort这些键。

所以我们看到两个结构都有一个关键的模块。在内部,还有另一个哈希引用,它将语言作为键,并且在每个哈希引用中,还有另一个哈希引用,其中只有一个PATH键和一个值。我们感兴趣的是每种语言的每种PATH的价值。

为了获得这些语言,我们迭代两个文件之一的模块的keys。然后我们可以将这些值一直比较下来。如果它们是not equal,我们打印错误消息。

use v5.10; # to get say

# get shortcuts so the lines are not as long
my $modules1 = $file1->{Modules};
my $modules2 = $file2->{Modules};
foreach my $language (sort keys %{ $file1->{Modules} }) {
    if ($modules1->{$language}->{PATH} ne $modules2->{$language}->{PATH} ) {
        say "Error:";
        say "file1.yml - $language: PATH: $modules1->{$language}->{PATH}";
        say "file2.yml - $language: PATH: $modules2->{$language}->{PATH}";
    }
}

我正在使用say而不是print,它最后为我们添加了换行符。额外的变量$modules1$modules2只是为了使代码更容易阅读,因为$file1->{Modules}->{$language}->{PATH}很长。

现在如果我们运行我们的程序到目前为止,我们将得到以下输出。

Error:
file1.yml - c++: PATH: /cfg/c++/release-1.1.5
file2.yml - c++: PATH: 
Error:
file1.yml - java: PATH: /cfg/java/version-2.157
file2.yml - java: PATH: /cfg/java/version-2.161
Error:
file1.yml - python: PATH: /cfg/python/verison-1.0
file2.yml - python: PATH: /cfg/python/verison-1.1
Use of uninitialized value in string ne at /home/simbabque/code/scratch/scratch.pl line 133.
Use of uninitialized value in concatenation (.) or string at /home/simbabque/code/scratch/scratch.pl line 136.

这几乎是我们想要的,但是有一些讨厌的警告(因为我们打开了use warnings),并且没有c ++的价值。我们需要过滤掉仅存在于两个文件之一中的语言。

已经不考虑仅存在于file2.yml中的语言,因为我们只查看来自file1.yml的键。要从第一个文件中删除它们,我们必须在访问它们之前检查它们是否在另一个文件中是exist

next unless exists $modules2->{$language};

unless keyword就像if (not ...),我认为它更容易阅读,特别是在这个非常简洁的post-fix notation。现在程序给出了预期的输出。

Error:
file1.yml - java: PATH: /cfg/java/version-2.157
file2.yml - java: PATH: /cfg/java/version-2.161
Error:
file1.yml - python: PATH: /cfg/python/verison-1.0
file2.yml - python: PATH: /cfg/python/verison-1.1

这是整个事情。不要忘记从代码中删除Data :: Dumper,因为它在生产程序中无关。我们仅将其用作调试助手。

use strict;
use warnings;
use YAML 'LoadFile';

my $file1 = LoadFile('file1.yml');
my $file2 = LoadFile('file2.yml');

use v5.10;

# get shortcuts so the lines are not as long
my $modules1 = $file1->{Modules};
my $modules2 = $file2->{Modules};
foreach my $language (sort keys %{ $file1->{Modules} }) {
    next unless exists $modules2->{$language};
    if ($modules1->{$language}->{PATH} ne $modules2->{$language}->{PATH} ) {
        say "Error:";
        say "file1.yml - $language: PATH: $modules1->{$language}->{PATH}";
        say "file2.yml - $language: PATH: $modules2->{$language}->{PATH}";
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.