从 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;
}
我希望在一个充满文本文件的文件夹中阅读并将值存储在哈希表中。示例文本文件如下所示:
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;
}