从 Perl 中的文本文件中读取 key-values,跳过注释

Reading in key-values from a text file in Perl, skipping comments

我希望在一个充满文本文件的文件夹中阅读并将值存储在哈希表中。示例文本文件如下所示:

sample.txt

title: Sample Soft
version: 1
category: utility
os_support: Linux
date_added: 01/01/1998
# Lines beginning '#' are skipped
This is a new line.
This is <b>bold</b> to test HTML.

我编写了一个将文件名作为参数的 Perl 子例程。我的 Perl 子例程如下所示:

sub read_entry {
    my $key = $_[0];
    open (ENTRY, "$entrydir/$key") or die "Couldn't open $entrydir/$key";
    $key =~ s/\.txt//;

    while (<ENTRY>) {
        if (my @entrykv = split /:\s*/) {
            # store key-value pair here, key value is $key
            print @entrykv;
        } elsif (! /^#/) {
            say "NOT COMMENT";
            $desc{$key} += $_;
        }
    }

    close ENTRY; 
}

我用它做的是遍历文本文件中的每一行。当它匹配以冒号结尾的 descriptor(例如 title:)时,它将存储相应的 value该行的右侧(例如 Sample Soft)转换为哈希映射。

我有以这些从文本文件中读取的描述符命名的哈希图。文本文件的第一行应该被读入名为“title”的哈希图中。 .txt 被截断的文本文件名是这些哈希图的键值,即我代码中的变量 $key。例如,如果我们读入“sample.txt”,那么这个值就是“sample”。因此将有一个标题、版本、类别等的哈希图,其值由从文本文件名中提取的 $key 变量索引。

换句话说,这是子程序应该从文件中读取的内容:

$key = "sample";
$title{$key} = "Sample Soft";
$version{$key} = "1";
$category{$key} = "utility";
$os_support{$key} = "Linux";
$date_added{$key} = "01/01/1998";
$desc{$key} = "This is a new line.\nThis is <b>bold</b> to test HTML.";

输入还应忽略任何注释(以# 开头的行),文本文件的其余部分(不符合descriptor-value 格式)将存储在$desc{$key} 哈希图中。

拆分功能有效,与此同时我只是打印它刚刚拆分的内容。但是,这些值尚未存储在哈希图中。此外,if 条件不起作用。我仍然看到打印的评论和描述。

如果能帮助修复条件和完成 key-values 的阅读,我将不胜感激。

这就是我的处理方式。

构建正则表达式以从文件中提取已知条目。

跳过评论是第一件事。您不想调查以 octothorpe 开头的行。

使用哈希的哈希来存储详细信息。您不必根据要提取的细节进行分支。


#!/usr/bin/perl
use warnings;
use strict;

my $entrydir = '.';
my @recognised = qw( title version category os_support date_added );
my $regex = join '|', map quotemeta, @recognised;
$regex = qr/^($regex):/;  #/ stupid SO highlighter

sub read_entry {
    my ($key) = @_;

    open my $in, '<', "$entrydir/$key" or die $!;
    $key =~ s/\.txt$//;  #/ stupid SO highlighter

    my %details;
    while (<$in>) {
        next if /^#/;

        if (/$regex(.*)/) {
            $details{} = ;
        } else {
            $details{desc} .= $_;
        }
    }
    return $key, \%details
}

my %software;
for my $file (@ARGV) {
    my ($key, $details) = read_entry($file);
    $software{$key} = $details;
}
use Data::Dumper; print Dumper \%software;

这是解决方案。您可以使用散列的散列来存储数据值。

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;
use File::Basename;

no warnings 'uninitialized';

my $path  = '/home/location/mention/sample.txt'; #either pass as an argument $ARGV[0]
my $fname = basename($path);

my $f;
if ($fname =~ /(\w+).txt/) { $f = ; }

open my $fh, '<', $path or die "Cannot open file: $path\n";

my %hash = ();
my ($title, $ver, $category, $os, $date, $description);

while (<$fh>){
    chomp;

    next if($_ =~ /^#/);

    if( $_ =~ /:/){
        if( $_ =~ /title: (.*)/)      { $hash{$f}{'TITLE'}    = ; }
        if( $_ =~ /version: (\d+)/)   { $hash{$f}{'VERSION'}  = ; }
        if( $_ =~ /category: (\w+)/)  { $hash{$f}{'CATEGORY'} = ; }
        if( $_ =~ /os_support: (\w+)/){ $hash{$f}{'OS'}       = ; }
        if( $_ =~ /date_added: (.*)/) { $hash{$f}{'DATE'}     = ; }
    } else {
        $description = $_;
        $hash{$f}{'DESC'} = $hash{$f}{'DESC'}.$description;
    }
}
print Dumper(\%hash);

close $fh;

%hash Dumper 中,您可以看到内容的结构是如何存储的。

PS: 从每一行读取数据regex可以改进。

您有一些解决方案,但我认为解释您现有代码的一些问题会很有趣。

你说:

Also, the if conditional doesn't work. I still see comments and the description printed.

让我们从那个开始。

你的条件是:

if (my @entrykv = split /:\s*/) {
   ...
} else {
   ...
}

您希望这样可以区分您的 key/value 行和包含描述的行(因此不包含“:”)。我认为您误解了 split() 的作用。在该行包含“:”的情况下,split() returns 是一个包含两个项目的列表。该列表被放入数组 @entrykv,并且 if 语句在标量上下文中计算该数组,它给出值 2(因为数组中有两个元素)。 2 是真值,所以我们进入 if/else 语句的“真”部分。

如果行中没有“:”,split() 将 return 一个包含单个元素(包含整行)的列表。这再次被放入 @entrykv 并且 if 语句在标量上下文中对其进行评估。这给出了值 1 并且 1 仍然是一个真值,我们仍然在 if/else 语句的“真”部分结束。

所以你需要在这里做一些不同的事情。最好的方法可能是检查行中是否存在“:”。像这样:

if (/:/) {
  my @entrykv = split /:\s*/; #/ stupid SO highlighter
  print "@entrykv\n";
} elsif (! /^#/) {
  print "Not a comment\n";
  $desc{$key} .= $_;
}

哦,这是第二点。您使用 += 添加到描述的末尾。那是不对的。 Perl 使用 . 作为连接运算符 - 所以你在这里需要 .=

还有另一个问题直接触及设计的核心。每个属性都有单独的哈希值。这真是个坏主意。如果您有一个包含许多属性的数据项,您应该尝试将所有这些属性放在一个变量中。哈希是将所有这些属性存储在一起的完美方式。

您的 read_entry() 子例程读取单个文件。而且我认为每个文件只包含一个“键”(例如“样本”)。因此,我认为该子例程 return 对包含该文件中所有数据的散列的引用是有意义的。它可能看起来像这样:

sub read_entry {
  my ($filename) = @_;

  my $key = $filename;
  $key =~ s/\.txt$//; #/ stupid SO highlighter

  # use a) lexical filehandle and b) three-arg version of open()
  open my $fh, '<', $filename or die "Can't open $filename: $!\n";

  my %data;
  while (<$fh>) {
    # Skip comments
    next if /^#/;

    if (/:/) {
      chomp;
      my ($key, $val) = split /:\s*/; #/ stupid SO highlighter
      $data{$key} = $val;
    } else {
      $data{desc} .= $_;
    }
  }

  return \%data;
}