【问题标题】:Can I restrict grep (or map) to first match when I'm expecting only one match?当我只期待一个匹配时,我可以将 grep(或 map)限制为第一个匹配吗?
【发布时间】:2020-01-06 20:23:09
【问题描述】:

我确实是 Perl 的新手。我有以下...

#!/usr/bin/perl

#config file
$config = "$ENV{'HOME'}/.config/.wallpaperrc";

open my $handle, '<', $config;
chomp(my @lines = <$handle>);
close $handle;

@regular = map(/^regular\.roll (.*)$/, @lines);

print(@regular);

它有效,但是当我只期待一场比赛并且只想要一场比赛时,使用数组似乎很尴尬和不合适。如果我使用 @regular 标量,那么该函数将返回匹配的数量。

我试图寻找答案,但结果被 Bash 中使用 Perl grep 的所有问题弄得一团糟。

【问题讨论】:

  • 提示:您应该始终使用use strict; use warnings;
  • 提示:你真的应该检查open 是否成功,如果只添加or die $!。这是最有可能失败的事情之一

标签: perl grep


【解决方案1】:

您可以通过分配给列表上下文中的标量来捕获单个匹配项

($regular) = map(/^regular\.roll (.*)$/, @lines);

左侧的括号很重要,否则您将在右侧大小上施加标量上下文,结果将是其他内容,例如元素的数量。

如果您尝试从grep(但不是map)捕获第一个匹配项,并且您更习惯使用Perl 模块,则List::Util 包中的first 函数会返回第一个匹配项,并且比调用grep 并丢弃所有多余的匹配项更有效。

use List::Util 'first';
...
$regular = first { /pattern/ } @input;

【讨论】:

  • 有点像在操作后强制转换类型?
  • first 可以返回捕获组吗?
【解决方案2】:

您可以将操作的结果分配给仅包含一个元素的列表:

my ($regular) = map(/^regular\.roll (.*)$/, @lines);
print $regular;

【讨论】:

  • 很好,它奏效了。你能解释一下你的语法在做什么吗?
  • 你可以确保使用my ($regular) = map(...) or die "No matching lines\n";找到一些东西
【解决方案3】:

注意 查看最后如何在第一场比赛后立即停止(一个语句,带模块)


为了让正则表达式match operator 自己返回捕获,它确实需要在列表context 中调用。但是您可以根据需要形成该列表 - 例如,仅使用一个标量,从返回的标量列表中仅捕获一个

my ($regular) = map { /^regular\.roll (.*)/ } @lines;

这里,LHS 上的 ($v1, $v2,...) 提供了 assignment operator 的列表上下文,并且只有一个变量,返回的 (.*) 捕获列表中的第一个被分配,其余的被丢弃。

以上大部分内容已经说明,但我认为对问题中的其他一些内容发表评论也很重要。

  • 总是在程序开头有use warnings;use strict;

  • open 语句必须进行失败测试,​​如果失败,则打印error。常见

    open my $fh, '<', $file  or die "Can't open $file: $!";
    
  • 我建议chomp单独声明

  • 没有理由在该正则表达式中使用$ 锚点(多行字符串和/m 修饰符除外)

  • 打印时,如果将其放在引号下,则会在其间插入空格 (see $,)

    say "@regular";
    

    或者,将每个元素打印在自己的行上

    say for @regular;
    

    为了能够使用say feature,您需要use feature qw(say);


由于只需要第一个匹配项,因此一旦找到匹配项,我们就不再遍历列表的其余部分。这可以使用来自List::MoreUtilsfirst_result 来实现(抄袭暴民的想法)

my $regular = firstres { my ($m) = /^regular\.roll (.*)/; $m } @lines;

块内的语法有点罗嗦,但在一个单独的正则表达式对我不起作用(?)之后返回$1。如果有两个语句很麻烦,可以缩短它,但以牺牲可读性为代价

my $regular = firstres { ( /^regular\.roll (.*)/ )[0] } @lines;

正则表达式周围的() 提供列表上下文,[0] 采用该列表的第一个元素。我在正则表达式周围添加了空格以尝试稍微减轻这种语法;不需要它们。

【讨论】:

  • 感谢您的信息。这让我很恼火 Perl 默认不只使用use strictuse warnings
  • @deanresin 我的观点也是如此。我的 vim 中有一个快捷方式(wss,warning-strict-say)可以输入它们。它在文档中的某个地方半开玩笑地说,warnings 没有被强制执行是一个错误
  • @deanresin 它不能向后兼容。也就是说,use 5.012; 及更高版本有效地为您执行use strict;。我真希望他们也能启用警告。 (如果您碰巧想要关闭它们,可以随时使用 no warnings; 关闭它们。)
  • @deanresin 有一些“框架”(我的意思是大模块)可以启用它们,例如Moose(或Moo)等。但是,是的,正如ikegami 所说的那样“我真希望……”……
【解决方案4】:

您可以使用标准的foreach 循环并在找到匹配项时终止它。

use strict; use warnings;

# sample array to be searched
my @array = qw( A B C );

my $match;  # variable to hold matching element
# "last" terminates the loop when /B/ pattern matches
# print below is only for debug purposes to show which elements are tested
print("? $_\n") and /B/ and $match = $_ and last foreach @array;
# below is short version
# /B/ and $match = $_ and last foreach @array;

# print $match if it is defined (if it have been assigned in foreach loop)
print "MATCH: $match\n" if defined($match);

【讨论】:

    猜你喜欢
    • 2020-03-03
    • 2012-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-30
    • 2012-02-18
    相关资源
    最近更新 更多