【问题标题】:Using scalars in regex search在正则表达式搜索中使用标量
【发布时间】:2014-05-21 16:46:29
【问题描述】:

我写了这段代码:

my $id = shift;
my $file = shift;
unless(open (INFO, $file)) { print "cant open file\n"; return 0; }
#this is how i do it - i didn't copy the code directly last time:
while(my $line = <info>)
{
    if($line =~ /d\s+S+\s\Q$id\disk\d+s\d+/g)
    {
        print "yay i found it";
        close(INFO);
        return 1;
    }
}
close(INFO);
return 0;

一个很好的例子是:

2:     Apple_HFS 0x123456789ABC   999.9 GB   disk2s2

(你可以看到 $id 是“0x123456789ABC”)

我的问题:它不起作用 - 它打开文件并读取行但加工不好。请告诉我我在这里错过了什么?我想我的正则表达式是错误的,但我无法修复它。

我尝试了谷歌和(当然)堆栈溢出(How to evaluate a word saved in a scalar via regular expression in Perl?Detect exact string value of scalar in regex matchingUse variable as RegEx pattern)但没有运气。 我确定我缺少一些基础知识,但这不是我的第一个正则表达式 - 只是其中包含标量的第一个。

谢谢

【问题讨论】:

  • $file 是文件的名称。您打开它,但从未真正读取其中的任何内容。而且您的正则表达式没有考虑每条记录中的大小字段999.9 GB
  • 谢谢大家。我原来的问题是正则表达式本身。你们所有人都为此提供了帮助。 @Borodin 的回答也为我提供了一个更“令人愉快”的解决方案。他的 cmets 还教会了我一些重要的东西。所以我将他的答案标记为最佳。

标签: regex perl scalar


【解决方案1】:

直接的问题是$file 是文件名。您打开它,但实际上从未从中读取任何内容。

这里有一些关于你的代码的进一步的 cmets

  • 像这样收集子程序的参数是一种常见的做法,而且更整洁

    my ($id, $file) = @_
    

    这也有复制值的好处,这样调用中的实际参数被修改的危险就更小了

  • 你应该使用open的三参数形式和词法文件句柄,像这样

    open my $fh, '<', $file
    

    特别是,在您的情况下,当子例程退出时文件保持打开状态,因为您选择了全局文件句柄。词法句柄在超出范围时隐式关闭

  • 您应该在open 错误消息中使用$! 内置变量来提供有关为什么失败的信息

  • 错误通常由一个空的return 指示,它返回undef 或一个空列表,具体取决于上下文。列表上下文中的return 0 会生成列表(0),如果将其分配给数组,则会产生 true

  • 除非您真的需要能够一次访问所有文件,通常最好使用while 循环逐行读取和处理它 p>

  • /g 正则表达式匹配修饰符用于查找字符串中模式的所有次出现。如果您只想检查模式是否出现在字符串中的任何地方

  • ,那是不必要且浪费的

你的正则表达式也有很多问题。如果我添加 /x 修饰符,那么我可以添加空格以更好地向您展示您所写的内容

/ d \s+ S+ \s \Q$id \d isk \d+ s \d+ /x

匹配的

  • 单个d 字符
  • 一个或多个空格字符
  • 一个或多个S 字符
  • 单个空格字符
  • \Q 没有终止,因此字符串的其余部分按字面意思匹配。如果您有 \Q$id\E,那么该模式的其余部分将匹配
  • 一位数
  • 字符串isk
  • 一位或多位数字
  • 单个s 字符
  • 一位或多位数字

这与您显示的记录格式不匹配。重要的是要记住,您的模式不需要匹配字符串的 all,因此您可能需要像 /\b\Q$id\E\b/ 这样的东西,它检查您的 ID 是否在字符串中的某个位置,单词边界为每一端。我没有看到像 0x123456789ABC 这样的字符串出现在其他地方并给出误报

我认为最好的解决方案是在空格上分割每条记录,并检查第三个字段是否与传入的ID匹配

你的子程序应该是这样的

sub routine {
  my ($id, $file) = @_;

  open my $fh, '<', $file or do {
    warn "Unable to open '$file' for input: $!";
    return;
  };

  while (my $line = <$fh>) {
    my @fields = split ' ', $line;
    if ($fields[2] eq $id) {
      print "Yay! I found it!\n";
      return 1;
    }
  }

  return;
}

【讨论】:

    【解决方案2】:

    代替

    my @lines = split(/\n/, $file);
    

    试试

    my @lines = <INFO>;
    

    甚至更好,

    unless(open (my $INFO, "<", $file)) { print "cant open file\n"; return 0; }
    while (my $line = <$INFO>)
    {
      # ..
    }
    

    你也忘了结束字符串的引用,即。 \Q$string\E

    if($line =~ /d\s+S+\s\Q$id\Edisk\d+s\d+/g)
    

    【讨论】:

      【解决方案3】:

      我认为正则表达式不正确。我不太确定您要匹配什么,所以我根据示例进行了尝试:

      \d+:.*S\s+\Q$id\E.+disk\d+s\d+

      这将匹配:

      d+: 一个数字后跟一个冒号

      .*S\s+“Apple_HFS”中“S”和空格之前的所有内容

      \Q$id\E你要找的id字符串

      .+“磁盘”之前的所有内容

      disk\d+s\d+diskXXXsXXX

      在这个 sn-p 中工作:

      $id = "0x123456789ABC";
      $line = "2:     Apple_HFS 0x123456789ABC   999.9 GB   disk2s2";
      
      if($line =~ /\d+:.*S\s+\Q$id\E.+disk\d+s\d+/g)
      {
              print "yay i found it";
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-05
        • 2011-05-20
        • 2011-09-02
        • 1970-01-01
        • 2013-11-02
        相关资源
        最近更新 更多