我该如何将Perl字符串仅在最后一个分隔符处拆分?

13

我的 $str="1:2:3:4:5"; my ($a,$b)=split(':',$str,2);

在上面的代码中,我使用了限制为 2,所以 $a 将包含 1,其余元素将在 $b 中。 像这样,我希望最后一个元素应该在一个变量中,而倒数第二个元素之前的元素应该在另一个变量中。

示例

$str = "1:2:3:4:5" ; 
# $a should have "1:2:3:4"  and $b should have "5" 
$str =  "2:3:4:5:3:2:5:5:3:2" 
# $a should have "2:3:4:5:3:2:5:5:3" and $b should have "2"

1
重复:http://stackoverflow.com/questions/1098295/perl-is-there-a-way-to-split-on-the-last-regex-match-only - Zaid
6个回答

18
split(/:([^:]+)$/, $str)

2
这个问题会受益于一个解释,就像Francisco Zarabozo在另一个答案中提供的那样。 - Richlv

10

你可以使用模式匹配而不是split()函数:

my ($a, $b) = $str =~ /(.*):(.*)/;

第一组贪婪地捕获最后一个出现的':'之前的所有内容,第二组捕获剩余的内容。

如果字符串中没有':',Perl会聪明地检测到并在不回溯的情况下失败匹配。


我会把第二个 .* 改成 .?,以确保安全。 - Zaid

8
你也可以使用rindex(),例如:
my $str="1:2:3:4:5";
$i=rindex($str,":");
$a=substr($str,0,$i);
$b=substr($str,$i+1);
print "\$a:$a, \$b: $b\n";

输出

$ perl perl.pl
$a:1:2:3:4, $b: 5

由于在此情况下分割分隔符非常简单,因此这比使用正则表达式从整个表达式中解析绑定到 $ 更快的解决方案。 - Ether

6

我知道,这个问题已经四年了。但是我发现来自YOU的答案非常有趣,因为我不知道split可以像那样工作。所以,为了新读者的利益,我想用perldoc split中的一段摘录来扩展它,解释这种行为。

my $str = "1:2:3:4:5";
my ($a, $b) = split /:([^:]+)$/, $str;
# Capturing everything after ':' that is not ':' and until the end of the string
# Now $a = '1:2:3:4' and $b = '5';

来自Perldoc:

如果PATTERN包含捕获组,则对于每个分隔符,都会为由组捕获的每个子字符串(按照后向引用指定的顺序)产生一个附加字段;如果任何组不匹配,则它捕获undef值而不是子字符串。此外,请注意,只要有分隔符(也就是说,每当发生拆分时),就会生成任何这样的附加字段,并且这样的附加字段不计入LIMIT。考虑在列表上下文中评估以下表达式(每个返回的列表均在相关注释中提供):

split(/-|,/, "1-10,20", 3)
# ('1', '10', '20')

split(/(-|,)/, "1-10,20", 3)
# ('1', '-', '10', ',', '20')

split(/-|(,)/, "1-10,20", 3)
# ('1', undef, '10', ',', '20')

split(/(-)|,/, "1-10,20", 3)
# ('1', '-', '10', undef, '20')

split(/(-)|(,)/, "1-10,20", 3)
# ('1', '-', undef, '10', undef, ',', '20')

2
你可以使用split和reverse来实现,具体方法如下:
my $str="1:2:3:4:5";
my ($a,$b)=split(':',reverse($str),2); # reverse and split.

$a = reverse($a); # reverse each piece.
$b = reverse($b);

($a,$b) = ($b,$a); # swap a and b

现在$a将变为1:2:3:4,而$b将变为5

一个更简单、更清晰的方法是使用正则表达式,就像Mark在他的回答中所做的那样。


1
虽然这是一种可能性,但并不是特别高效,尤其是当一行代码就能达到同样的效果时。例如 my ($a,$b) = ($str =~ /(.*):(.?)/); - Zaid

-1

我对这个问题有点晚了,但是我整理了一个更通用的解决方案:

# Similar to split() except pattern is applied backwards from the end of the string
# The only exception is that the pattern must be a precompiled regex (i.e. qr/pattern/)
# Example:
#   rsplit(qr/:/, 'John:Smith:123:ABC', 3) => ('John:Smith', '123', 'ABC')
sub rsplit {
    my $pattern = shift(@_);    # Precompiled regex pattern (i.e. qr/pattern/)
    my $expr    = shift(@_);    # String to split
    my $limit   = shift(@_);    # Number of chunks to split into

    # 1) Reverse the input string
    # 2) split() it
    # 3) Reverse split()'s result array element order
    # 4) Reverse each string within the result array
    map { scalar reverse($_) } reverse split(/$pattern/, scalar reverse($expr), $limit);
}

它接受类似于split()的参数,只不过是以相反的顺序进行分割。如果需要指定结果元素的数量,它还接受限制子句。

注意:此子程序期望第一个参数为预编译正则表达式
Perl的split是内置的,并且会正确解释/pat/,但尝试将/pat/传递给子例程将被视为sub($_ =~ /pat/)

这个子程序并不是万无一失的!对于简单的分隔符,它足够好用,但更复杂的模式可能会引起问题。模式本身不能被反转,只能反转它匹配的表达式。


示例:

rsplit(qr/:/, 'One:Two:Three', 2); # => ('One:Two', 'Three')

rsplit(qr/:+/, 'One:Two::Three:::Four', 3); # => ('One:Two', 'Three', 'Four')

# Discards leading blank elements just like split() discards trailing blanks
rsplit(qr/:/, ':::foo:bar:baz'); # => ('foo', 'bar', 'baz')

我不明白这个解决方案的重点或“更通用”的方面,它在内部使用了split和两个对reverse的调用。当my ($a, $b) = split /:([^:]+)$/, $str;可以完美地工作时,这一切的意义何在? - Francisco Zarabozo
@FranciscoZarabozo,对于你的问题,答案是潜在的效率问题,但这需要进行基准测试 - 不确定。具体来说,当你只需要从一个非常长的字符串的右侧找到一小部分字符时(除非Perl在使用$作为分割正则表达式时足够聪明地从字符串的末尾向前搜索)。 - Michael Goldshteyn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接