roho has asked for the wisdom of the Perl Monks concerning the following question:

I've run across an unexpected error in a regex substitution. The text to be modified is a directory path in Windows with backslashes. The file name starts with "C", so when the regex sees the final directory path backslash followed by the "C" in the filename it returns the following error message:

\C no longer supported in regex; marked by <-- HERE in m/C:\Book\ <-- HERE C0001-Chapter-001.mp3/ at C:\tmpx\xx.pl line 15.

I have a workaround, which is to change the backslashes to forward slashes before performing the regex substitution, then changing the forward slashes back to backslashes, which seems kind of kludgy.

Any ideas on how to overcome the "\C" error message? I have tried running the text through "qr" but that does not help. I have tried both "e" and "ee" regex suffixes but that does not help either.

In the following code, comment out the "Workaround" lines that change backslashes to forward slashes to see the error.

#!/usr/bin/perl use strict; use warnings; my $txt = "C:\\Book\\C0001-Chapter-001.mp3"; my $new = "C:\\Book\\NEW-C0001-Chapter-001.mp3"; print "\n"; print "Before: $txt\n"; # Workaround -- Change backslashes to forward slashes. $txt =~ s/\\/\//g; $new =~ s/\\/\//g; $txt =~ s/$txt/$new/; # Workaround -- Change forward slashes back to baskslashes. $txt =~ s/\//\\/g; $new =~ s/\//\\/g; print "After: $txt\n"; print "\n";

"It's not how hard you work, it's how much you get done."

Replies are listed 'Best First'.
Re: Unexpected Error: \C no longer supported in regex (updated x2)
by AnomalousMonk (Archbishop) on Aug 20, 2022 at 02:31 UTC

    In general, a \ (backslash) introduces a regex zero-width assertion. Some such are \A \B \b \Z \z and there are others. Why do you need backslashes in your paths? Even in Perl running under Windows, forwardslashes work just as well unless you need to construct a command that is to be given to the Windows shell. I don't recall what assertion \C used to be, but clearly Perl does.

    Update 1:

    ... in m/C:\Book\ <-- HERE C0001-Chapter-001.mp3/ ...
    Note that the \B assertion is also present in the
        m/C:\Book\C0001-Chapter-001.mp3/
    regex with which you are trying to construct a match for a file name, so the regex will not match what you think it will even if \C was not a problem. Would a simple string equality eq comparator be better here? (Update: Oops... Forgot you want to do a substitution.)

    Update 2: See quotemeta:

    Win8 Strawberry 5.8.9.5 (32) Fri 08/19/2022 19:42:14 C:\@Work\Perl\monks >perl use strict; use warnings; my $txt = "C:\\Book\\C0001-Chapter-001.mp3"; my $new = "C:\\Book\\NEW-C0001-Chapter-001.mp3"; print "\n"; print "Before: '$txt' \n"; # Workaround -- use \Q \E -- see quotemeta. $txt =~ s/\Q$txt\E/$new/; print "After: '$txt' \n"; print "\n"; ^Z Before: 'C:\Book\C0001-Chapter-001.mp3' After: 'C:\Book\NEW-C0001-Chapter-001.mp3'


    Give a man a fish:  <%-{-{-{-<

      Thank you AnomalousMonk! I forgot about quotemeta. That was what I needed.

      Regarding the backslashes, I know Perl can use forward slashes just fine, even on Windows, but there are other apps that reference these files that require the Windows standard backslashes.

      Thanks again for your quick reply.

      "It's not how hard you work, it's how much you get done."

Re: Unexpected Error: \C no longer supported in regex
by kcott (Archbishop) on Aug 20, 2022 at 09:18 UTC

    G'day roho,

    I see you've got a solution using quotemeta. Here's some additional information.

    A '\C' used to match a single byte. Last documented in "perlrebackslash v5.22.4: All the sequences and escapes".

    It was removed in v5.24 according to "perl5240delta: The /\C/ character class has been removed."; however, although documented as such, [perl #41916] indicates this didn't happen until v5.26.

    Use of '\C' is now fatal in regexes (as you've found out) and gives a warning in strings:

    $ perl -wE 'say "p" =~ /\C0/ ? "Y" : "N"' \C no longer supported in regex; marked by <-- HERE in m/\ <-- HERE C0 +/ at -e line 1. $ perl -wE 'say "\C0"' Unrecognized escape \C passed through at -e line 1. C0

    Probably also worth a mention is the lowercase '\c'.

    It's documented, with a fair amount of detail, in "perlop: Quote and Quote-like Operators: Note 5".

    Some restrictions on its use were introduced in v5.20 — see "perl5200delta: Quote-like escape changes".

    Depending on what character follows '\c', you may get a warning with useful information.

    $ perl -wE 'say "\c0"' "\c0" is more clearly written simply as "p" at -e line 1. p $ perl -wE 'say "p" =~ /\c0/ ? "Y" : "N"' "\c0" is more clearly written simply as "p" in regex; marked by <-- HE +RE in m/\c0 <-- HERE / at -e line 1. Y

    [I used Perl v5.36 for all of the example code above.]

    — Ken

      Thank you for the additional information kcott. I appreciate everything that leads me to a better understanding of regexes.

      "It's not how hard you work, it's how much you get done."

Re: Unexpected Error: \C no longer supported in regex
by atcroft (Abbot) on Aug 20, 2022 at 22:30 UTC

    I understand the question and the problem you found, but I feel like it is a sign you are using the wrong tool for the job. Not only does perl understand both forward ('/') and back ('\') slashes as path separators on most platforms (and you don't have to escape them if you use single-quotes, by the way), but it includes File::Spec::Win32 in the core, which appears to be in perl on multiple platforms. (I checked and it appears to be present on the Windows/Strawberry, Windows/Cygwin, and Linux/Fedora systems I checked with perl installed.) File::Basename is another core module that can separate file names and paths, to might allow you to work on just the piece you want (but I haven't thought through that one in this case).

    I know your code is a simple example (and thus may not reflect *WHY* you are trying to do this), but consider the following code to do something similar.

    # File::Spec::Win32 example # #!/usr/bin/perl use strict; use warnings; use File::Spec::Win32; my @txt = ( 'C:\Book\C0001-Chapter-001.mp3', ); my @re = ( [ qr{^(C\d+)}msx => '"NEW-$1"', ], [ qr{(-Ch)}msx => 'lc("$1")', ], ); foreach my $fn ( @text ) { print qq{Before: }, $fn, qq{\n}; # # @fp - file parts (volume, directory, filename) # @dl - directory path to $fn my @fp = File::Spec::Win32->splitpath( $fn ); my @dl = File::Spec::Win32->splitdir( $fp[1] ); # # Apply regexes to file name # (could also add logic to modify some part of the # directory path in @dl as well). foreach my $regex ( @re ) { $fp[3] =~ s/$regex->[0]/$regex->[1]/gee; } # # Rebuild file name $fp[1] = File::Spec::Win32->catdir( @dl ); my $ffn = File::Spec::Win32->catpath( @fp ); print qq{After: }, $ffn, qq{\n}; } # # Output: # # Before: C:\Book\C000-Chapter-001.mp3 # After: C:\Book\NEW-C000-chapter-001.mp3

    Hope that helps.

      Thank you for the alternative approach atcroft. My sample code is an exact representation of what I was processing. It was a simple, straightforward (or so I thought) substitution in a string that happened to be a file path, and the part of the path that contained "\C" was the culprit, as AnomalousMonk pointed out. One of the things I've always liked about Perl is timtowtdi. Thanks again for your input.

      "It's not how hard you work, it's how much you get done."