简体   繁体   中英

print $1 =~ s/-// in perl

This results in the error "Modification of a read-only value attempted at..."

$mfgs = "AUDBMWCH-FO-TOY";
while( $mfgs =~ /(.{3})/g ) {
    print $1 =~ s/-// . "\n";
}

How do I do this without adding additional lines? As far I can tell Perl doesn't have a built-in str_replace() function. I could just write one, but as I said, I'm trying to figure out how to do this without additional lines of code.

This is not being used in a real project. This is only being used for learning purposes.



Let's take a step back

$mfgs = "AUDBMWCH-FO-TOY";
while( $mfgs =~ /(.{3})/g ) {
    print $1 =~ s/-// . "\n";
}

is simply reading the $mfgs string three characters at a time and removing hyphens.

One could re-write this as the following:

say for map { s|-||r }  $mfgs =~ /.../g ; # Works in Perl 5.14+

use List::MoreUtils 'apply';
say for apply { s|-|| } $mfgs =~ /.../g ; # If that 'r' flag isn't there

or use the transliteration operator ( tr/// ) seeing there is nothing inherently regexy going on:

say for map tr!-!!dr , $mfgs =~ /.../g ;

Both ways would give identical results if there is only up to one hyphen in a block of three characters. This is because tr/-//dr would remove all hyphens, and s/-//r removes only the first occurrence.


That answers how one could do it otherwise, so let's see why it wasn't working before

Why can't $1 be modified?

According to perldoc perlvar (emphasis added):

$<digits> ($1, $2, ...)

Contains the subpattern from the corresponding set of capturing parentheses from the last successful pattern match, not counting patterns matched in nested blocks that have been exited already.

These variables are read-only and dynamically-scoped.

In other words, $1 can't be modified, which is what the s/// was trying to do.

However, a copy of $1 can be modified, which is somewhat covered in Jonathan Leffler's solution .

I'm not sure why you'd want to do that - the data structure is sub-optimal compared with:

my @mfgs = ( "AUD", "BMW", "CH", "FO", "TOY" );

However, this works:

use strict;
use warnings;
my($mfgs, $x) = ("AUDBMWCH-FO-TOY");
while ($mfgs =~ /(.{3})/g)
{
    printf "%s\n", ($x = $1, $x =~ s/-//, $x);  
}

If you do without strictures or warnings, you can use:

my $mfgs = "AUDBMWCH-FO-TOY";
while ($mfgs =~ /(.{3})/g)
{
    printf "%s\n", ($x = $1, $x =~ s/-//, $x);
}

However, it is better not even to bother learning how you can (ab)use Perl without use strict; and use warnings; (or use diagnostics; ) in effect.

In Perl 5.14, there is a new /r modifier available for s/// substitutions (as well as tr/// / y/// transliterations), which causes the returned result to be a new string, not changing the original.

use 5.014;

$mfgs = "AUDBMWCH-FO-TOY";
while ($mfgs =~ /(.{3})/g) {
    say $1 =~ s/-//r;
}
print( do { ( my $x = $1 ) =~ s/-//; $x }, "\n" );
print( ( apply { s/-// } $1 ), "\n" );
say( do { ( my $x = $1 ) =~ s/-//; $x } );  # 5.10+
say( apply { s/-// } $1 );                  # 5.10+
say( $x =~ s/-//r );                        # 5.14+

apply comes from List::MoreUtils .

How do I do this without adding additional lines? As far I can tell Perl doesn't have a built-in str_replace() function. I could just write one, but as I said, I'm trying to figure out how to do this without additional lines of code.

Did you read perlfunc ? Also, this is not about functions, it's about $1 being a read-only variable. See perldoc perlvar .

$<digits> ($1, $2, ...)

Contains the subpattern from the corresponding set of capturing parentheses from the last successful pattern match, not counting patterns matched in nested blocks that have been exited already.

These variables are read-only and dynamically-scoped.


As near as I can tell, this is the most economic way to achieve the code in question:

tr/-//d, say for $mfgs =~ /.{3}/g;

Or the long version:

my $mfgs = "AUDBMWCH-FO-TOY";
for my $str ($mfgs =~ /.{3}/g) {
    $str =~ tr/-//d;
    say $str;
}

Note that capturing parentheses are not required in this case. From perldoc perlop :

The /g modifier specifies global pattern matching--that is, matching as many times as possible within the string. How it behaves depends on the context. In list context, it returns a list of the substrings matched by any capturing parentheses in the regular expression. If there are no parentheses, it returns a list of all the matched strings, as if there were parentheses around the whole pattern.

Also note the difference between while and for (foreach) . while will iterate over the matches as they appear (using /g in scalar context), with implicit use of the \\G assertion, but it will not store the match in a variable (except $1 ). for will extract all the matches at once (using /g in list context), and the match will be aliased in the loop variable ( $_ in the first case, my $str in the second).

In scalar context, each execution of m//g finds the next match, returning true if it matches, and false if there is no further match. The position after the last match can be read or set using the pos() function; see pos. A failed match normally resets the search position to the beginning of the string, but you can avoid that by adding the /c modifier (eg m//gc). Modifying the target string also resets the search position.

You could always just do this to get an array of items split by your delimiter..

@a = split("-", $mfgs);

To str_replace you use

$var =~ s/search/replace/g;

Like so

$mfgs =~ s/-/\n/g;
printf("%s\n", $mfgs);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM