【问题标题】:How can Perl split a line on whitespace except when the whitespace is in doublequotes?Perl 如何在空格上分割一行,除非空格在双引号中?
【发布时间】:2010-12-06 15:59:46
【问题描述】:

我有以下字符串:

StartProgram 1 ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout 1 30

我需要一个正则表达式来分割这一行,但在 Perl 中忽略双引号中的空格。

以下是我尝试过的,但它不起作用。

(".*?"|\S+)

【问题讨论】:

  • 需要考虑转义吗?
  • \s+(?=([^"]*""[^"]*"")*[^"]*$) 上拆分应该可以做到,尽管我也会使用一些现有的 API/方法来标记字符串。
  • @Manni 你在想perldoc.perl.org/…

标签: regex perl split


【解决方案1】:

曾几何时,我也尝试重新发明轮子,并自己解决这个问题。

现在我只使用Text::ParseWords 让它为我完成这项工作。

【讨论】:

  • 一个工作示例会很棒,因为我没有成功使用 Text::BalancedText::ParseWords 获得 6 个字段。 quotewords('"', 1, $_) 给我'StartProgram 1 ''"C:\\Program Files\\ABC\\ABC XYZ"''CleanProgramTimeout 1 30'
  • quotewords('\s+', 1, $_) 沿空格分割文件名并给出八个字段。
  • 通过阅读文档,您所要做的就是用 '\"' 代替单引号,用 '"' 代替双引号,quotewords() 应该可以正常工作。
  • 抱歉,为了提高可读性:通过阅读文档,您所要做的就是用'\"' 替换单引号,用'"' 替换双引号,quotewords() 应该可以正常工作。
  • @Oesor 和@Colin Fine:您能发布一个工作示例吗?
【解决方案2】:

更新:看起来这些字段实际上是制表符分隔的,而不是空格。如果可以保证,请在 \t 上拆分。

首先,让我们看看为什么(".*?"|\S+)“不起作用”。具体来说,看".*?" 这意味着用双引号括起来的零个或多个字符。好吧,给你带来问题的领域是""C:\Program Files\ABC\ABC XYZ""。请注意,该字段开头和结尾的每个"" 都将匹配".*?",因为"" 包含用双引号括起来的零个字符。

最好尽可能具体地匹配而不是拆分。因此,如果您有一个带有指令和固定格式的配置文件,请形成一个尽可能接近您尝试匹配的格式的正则表达式匹配。

如果您不想要引号,请将它们移到捕获括号之外。

#!/usr/bin/perl

use strict;
use warnings;

my $s = q{StartProgram 1 ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout 1 30};

my @parts = $s =~ m{\A(\w+) ([0-9]) (""[^"]+"") (\w+) ([0-9]) ([0-9]{2})};

use Data::Dumper;
print Dumper \@parts;

输出:

$VAR1 = [
          'StartProgram',
          '1',
          '""C:\\Program Files\\ABC\\ABC XYZ""',
          'CleanProgramTimeout',
          '1',
          '30'
        ];

在这种情况下,这里有一个更复杂的脚本:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my @strings = split /\n/, <<'EO_TEXT';
StartProgram 1 ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout 1 30
StartProgram 1 c:\opt\perl CleanProgramTimeout 1 30
EO_TEXT

my $re = qr{
    (?<directive>StartProgram)\s+
    (?<instance>[0-9][0-9]?)\s+
    (?<path>"".+?""|\S+)\s+
    (?<timeout_directive>CleanProgramTimeout)\s+
    (?<timeout_instance>[0-9][0-9]?)\s+(?<timeout_seconds>[0-9]{2})
}x;

for (@strings) {
    if ( $_ =~ $re ) {
        print Dumper \%+;
    }
}

输出:

$VAR1 = {
          'timeout_directive' => 'CleanProgramTimeout',
          'timeout_seconds' => '30',
          'path' => '""C:\\Program Files\\ABC\\ABC XYZ""',
          'directive' => 'StartProgram',
          'timeout_instance' => '1',
          'instance' => '1'
        };
$VAR1 = {
          'timeout_directive' => 'CleanProgramTimeout',
          'timeout_seconds' => '30',
          'path' => 'c:\\opt\\perl',
          'directive' => 'StartProgram',
          'timeout_instance' => '1',
          'instance' => '1'
        };

更新:我无法让 Text::BalancedText::ParseWords 正确解析此内容。我怀疑问题出在重复的引号中,这些引号描述了不应拆分的子字符串。以下代码是我通过使用拆分然后选择性地重新收集部分字符串来解决一般问题的最佳(不是很好)尝试。

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my $s = q{StartProgram 1 ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout 1 30};

my $t = q{StartProgram 1 c:\opt\perl CleanProgramTimeout 1 30};

print Dumper parse_line($s);
print Dumper parse_line($t);

sub parse_line {
    my ($line) = @_;
    my @parts = split /(\s+)/, $line;
    my @real_parts;

    for (my $i = 0; $i < @parts; $i += 1) {
        unless ( $parts[$i] =~ /^""/ ) {
            push @real_parts, $parts[$i] if $parts[$i] =~ /\S/;
            next;
        }
        my $part;
        do {
            $part .= $parts[$i++];
        } until ($part =~ /""$/);
        push @real_parts, $part;
    }
    return \@real_parts;
}

【讨论】:

  • 也许问题不清楚,但您的答案似乎与所问的不同。我认为他想要一种方法来找到一个正则表达式,它可以使用空格分割任何行,但忽略引号之间的空格。您的答案是解析一种特定格式的正则表达式。
  • @Kinopiko - 他的回答也是“与尝试在有问题的分隔符上拆分相比,这种方法更好,错误更少。考虑尝试它而不是你目前的做法,因为它实现了结果差不多。”
  • 问题是,问题不一定是有问题的分隔符。能够通过空格解析任意行而忽略带引号的字符串中的空格很有用,而且这个答案完全忽略了这个问题,说“你应该用制表符来解析”。虽然它在这种特定情况下很有用,但它没有回答如何用空格分割通用字符串,同时忽略带引号的字符串中的空格,
  • Oesor 我无法想出一个令人满意的工作方式来处理一般问题。我对 Colin Fine 的回答(我赞成)的评论是否不清楚?请发布解决 OP 问题的更好方法,我会支持它。
【解决方案3】:
 my $x = 'StartProgram 1    ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout    1   30';

 my @parts = $x =~ /("".*?""|[^\s]+?(?>\s|$))/g;

【讨论】:

  • [^\s]+?(?&gt;\s|$)可以简化为\S+\b
  • Bzzt!您对 \S 的看法是正确的,但 \b 与 (?>\s|$) 不同。
  • 我复制了 Sinan Unur 的部分答案,以演示使用不依赖于确切格式的正则表达式的不同方法。我还对他的回答发表了评论,解释了这一点。您的答案几乎与我的相同,包括正则表达式和变量名的形式,并且还包含 John Kugelman 的更正。我不明白你为什么要这样复制我的答案。
  • @Kinopiko 现在正在争论变量名?我的帖子使用@parts。您的帖子使用@parts。 @FM 的帖子使用了@parts。您答案的唯一原始部分是正则表达式模式。 @FM 编辑了模式,因此发布了原始答案。放松。
【解决方案4】:
my $str = 'StartProgram    1    ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout    1       30';

print "str:$str\n";

@A =  $str =~ /(".+"|\S+)/g;

foreach my $l (@A) {
        print "<$l>\n";
}

这给了我:

$ ./test.pl 
str:StartProgram    1   ""C:\Program Files\ABC\ABC XYZ"" CleanProgramTimeout    130
<StartProgram>
<1>
<""C:\Program Files\ABC\ABC XYZ"">
<CleanProgramTimeout>
<1>
<30>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-17
    • 2013-03-03
    • 2013-12-13
    • 1970-01-01
    • 1970-01-01
    • 2010-10-07
    • 2011-01-28
    • 2021-03-26
    相关资源
    最近更新 更多