0

I have the following lines of text in some configuration file, need to parse that file using Perl, find the File: line and replace its content using some regular expression to add some text etc.

File: logs/${byYearMonth}.log

The problem I have is with the regular expression, because I would like to use ${byYearMonth}.log in it, as it's easy to read, can easily be quoted etc. But this looks like variable interpolation to Perl and I get the following compilation error for the following simplified reg exp:

... =~ s/...\Q${byYearMonth}.log\E.../.../m;

Global symbol "$byYearMonth" requires explicit package name (did you forget to declare "my $byYearMonth"?)

Of course I can rework the reg exp to not let Perl think it's a variable name, but the provided version above is the most easiest to read and search for in my opinion. Thinking and researching about the problem, I didn't find any solution which would allow me to keep the reg exp as is and prevent the Perl compilation error by only adding some flag or whatever.

So, is there any (easy) way to tell Perl that some plain and already quoted text is not to be interpolated to get rid of the compilation error?

I have the feeling I'm missing something really easy, so thanks for your hints! :-)

Thorsten Schöning
  • 3,501
  • 2
  • 25
  • 46
  • 1
    This looks rather a lot like XML. Is it? – Sobrique Dec 14 '17 at 13:18
  • 1
    @Thorsten: You really shouldn't remove useful information from your question to prevent people from identifying that you have taken the wrong approach. Regular expressions are the *wrong tool* for processing XML, and such a solution is liable to backfire on you and your company. Making wholesale changes to your question like that also makes nonsense of people's existing comments and solutions. – Borodin Dec 15 '17 at 12:25
  • @Borodin The underlying textual file format is in no way needed for my question, it is not about how to properly parse XML, it is about variable interpolation in regular expressions in Perl. Your want to discuss things which are totally off-topic and unnecessary to the question. Something which my edit has easily proven... – Thorsten Schöning Dec 15 '17 at 16:47
  • 1
    @Thorsten: So it *is* XML? If so then `qr'...'` is not the answer you should be looking for: it is an error-prone hack. The best answers correct misconceptions in the question as well as providing a working solution to the stated problem. **Sobrique's** answer should not be down voted just because you insist on using a faulty solution. – Borodin Dec 15 '17 at 16:52
  • @Borodin You simply don't get it, `qr'...'` of course is the one best answer for this concrete problem: Just look at the `if` where Sobrique matches the regular expression and input my regexp from the question there. Exactly the same problem even with XML parser and only solved by `quotemeta`. But like the down-vote of the other answer suggesting to use `quotemeta` shows, this is not the best/easiest solution, which is `qr'..'`. This discussion is pointless and your revert of the removal of the XML example is pointless as well. – Thorsten Schöning Dec 15 '17 at 16:57
  • @Thorsten: It is you who isn't "getting it". You clearly have no real experience with handling XML data properly, and you should have sufficient humility to accept the advice of those who do. Please take your aggression elsewhere. – Borodin Dec 15 '17 at 16:58
  • @Borodin You don't have any clue about what I know or don't know or am I capable of doing or whatever... :-D That's easily proven by the fact that you are discussing a completely off-topic part of the question where my problem could have easily occurred as well. – Thorsten Schöning Dec 15 '17 at 17:02
  • @Thorsten: I'm afraid your capabilities with XML are clearly very limited as evidenced by your words and the position you're taking here. All you're doing is repeating nonsense, and I have no more to say. – Borodin Dec 15 '17 at 17:05
  • @Borodin Don't be afraid, you are just wrong, that happens to a lot of people on the internet. :-) Just ask you one question: Does my question makes sense without the XML example text? What benefit provides the XML example text to the question? It's pretty easy in the end, because the only correct answer of course is, that the XML example doesn't provide any benefit and the only error I made was using it simply because it was available in the beginning. I should have known the risk for this off-topic nonsense we are having right now. ;-) – Thorsten Schöning Dec 15 '17 at 17:10
  • 4
    Note: This question is [being discussed on meta](https://meta.stackoverflow.com/questions/360744/when-am-i-wiser-than-the-op/360745#360745). – Cerbrus Dec 15 '17 at 21:56
  • 5
    Contrary to popular belief, [using patterns on little, limited pieces of reasonably well-defined pieces of HTML is quick and easy](https://stackoverflow.com/a/4286326). The stigma here is unnecessary and wrong. Sure, if you want to perform complicated tasks on potentially malformed HTML (note that XML is much easier to deal with than HTML) you're probably better off with a parser, but if you just need to do a simple text replacement, you probably don't need a parser and RegEx is OK. –  Dec 15 '17 at 22:08
  • Related: *[How do I handle special characters in a Perl regex?](https://stackoverflow.com/questions/576435/how-do-i-handle-special-characters-in-a-perl-regex)*. – Peter Mortensen Apr 08 '18 at 13:11

3 Answers3

2

The \Q and \E are for quoting regex meta characters. They have nothing to do with variable interpolation.

If you construct your pattern with qr'' with single quotes '', Perl will not interpolate variables.

use feature 'say';

my $foo = 1;
my $bar = qr/$foo/;
say $bar;

Output:

(?^:1)

But with qr'':

my $foo = 1;
my $bar = qr'$foo';
say $bar;

Output:

(?^:$foo)
simbabque
  • 53,749
  • 8
  • 73
  • 136
  • 2
    Still this might cause an issue as $ is a special character for regex, and it is not escaped – Pradeep Dec 14 '17 at 11:00
  • @Pradeep combined with quotemeta or `\Q` and `\E` it should be fine. – simbabque Dec 14 '17 at 11:16
  • @AlvaroJoao there's no difference. You can pass a list of imports to `use` after the module name. `feature` is a pragma, but that's kind of like a module with a lower case name really. The `'say'` is just one scalar. If you do `qw/say/` it will create a list of scalars with words that are automatically quoted, with one scalar in it. In list context, everything is a list in Perl. So essentially they are two ways of importing one thing. People often use `qw//` directly, because `use Foo 'bar', 'baz'` looks weird, and `use Foo qw/bar baz/` is nicer to read. – simbabque Dec 16 '17 at 10:45
  • 1
    My reason for accepting this answer seems to have "magically" disappeared: `qr'...'` is exactly what I was looking for and is even documented to prevent my kind of problem: "If "'" is used as the delimiter, no variable interpolation is done." https://perldoc.perl.org/perlop.html#Regexp-Quote-Like-Operators – Thorsten Schöning Dec 16 '17 at 10:46
  • 1
    Combining `qr'...'` and `\Q...\E` doesn't work: `Unrecognized escape \E passed through in regex;[...]` Using `qr/\Q...\E/` with ... being the result of `qr''` doesn't work as well. Therefore one either needs to escape `$`, {`, ´}` and `.` individually or use `quotemeta` right from the start. `qr'...'` isn't saving anything in the end, because `quotemeta` doesn't suffer from my problem and quoting individually prevents Perl from thinking of variable interpolation as well. So this answers my question by word, but was actually not was I hoped to find. :-) – Thorsten Schöning Dec 29 '17 at 20:33
1

I am going to call the other answers wrong, on the basis that manipulating what looks a lot like XML via regex is a bad idea.

And if it's actually not XML, then using a data format that looks like XML but isn't is an even worse idea.

So the answer is 'use a parser' (or alternatively, hit whoever generated the file with a rolled up copy of the XML spec).

Something like this will change the contents of the <File> element:

#!/usr/bin/perl
use strict;
use warnings;

use XML::Twig;

my $xml = XML::Twig -> new -> parsefile ( 'your_file.xml'); 

my $file_elt = $xml -> get_xpath ('//File',0);

print "Original value:", $file_elt -> text,"\n";
$file_elt -> set_text('some/other/path/${byWeek}.log');

$xml -> set_pretty_print ( 'indented' ); 
$xml -> print;

Note - get_xpath only finds the first instance of <File> anywhere in the tree. If you need to be more specific, you can either iterate or add additional qualifiers to the xpath.

So for example:

my $target_text = quotemeta '${byYearMonth}'; 
my $search_regex = qr/$target_text/; 

foreach my $file_elt ( $xml -> get_xpath('//File') ) { 
  if ( $file_elt -> text =~ /$search_regex/ ) { 
      ## set it to something else. 
  }
}
Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • 4
    Down vote because the RegExp vs. XML-Parser discussion is off-topic and not relevant to my question, which is about variable interpolation only. Will change my text example to prevent that kind of discussion. – Thorsten Schöning Dec 14 '17 at 17:01
  • 1
    @Thorsten: Your downvote is highly inappropriate. **Sobrique** has shown you a *correct* way to process your data. – Borodin Dec 15 '17 at 12:26
  • 1
    @Borodin My question is not related to XML processing, but his answer focusses on that and doesn't even provide the answer I was really looking for, `qr'...'`. He is addressing my question only by the `quotemeta`side note. – Thorsten Schöning Dec 15 '17 at 16:43
  • @Thorsten: Are you saying that your data is *not* XML? – Borodin Dec 15 '17 at 16:44
  • 2
    @Borodin Nope, I'm saying that I don't asked how to properly parse XML and therefore it doesn't make sense to explain how to properly parse XML, while addressing the concrete question only in a side note, if at all. Other answerers didn't care for XML or not, but focussed on the concrete question... – Thorsten Schöning Dec 15 '17 at 16:52
  • 1
    The reason I answered about parsing XML, is because your first question actually was about parsing XML. There are other ways to select fields within XML that regex cannot do - `xpath` is more fully featured. You were tackling an [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). The fact that you've edited your question to remove that context and then down voted my answer afterwards is disappointing. – Sobrique Dec 18 '17 at 16:51
  • @Sobrique You are wrong, I first down voted and than edited and my comment makes that clear, so I'm not hiding anything. I know about XML and XPath and decided against their usage in this case for various reasons and can live with the risks just fine. How unnecessary and off-topic my XML example was is shown by the `if` in your example as well, my problem would have occurred there exactly as described in my question, so XPath didn't change anything and therefore it was valid to provide a better example. Should have done that from the beginning. – Thorsten Schöning Dec 20 '17 at 19:19
-1

Escape the pattern with quotemeta and then compile the regex. Something like this, it works...

my $s = q[<timestamp key="byYearMonth" datePattern="yyyy-MM" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <File>logs/${byYearMonth}.log</File>
    <Append>true</Append>

    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <Pattern>%date %-5level %logger.%M: %msg%n</Pattern>
    </encoder>
</appender>];

my $pat = quotemeta('${byYearMonth}.log');
my $re = qr[$pat];

$s =~ s/$re/pop/g;

print $s;
Pradeep
  • 3,093
  • 17
  • 21
  • 3
    This answer doesn't deserve all the down votes, `quotemeta` seems the only solution to handle escaping and non-variable-interpolation in one easy way. See my other comment: https://stackoverflow.com/questions/47809922/how-to-escape-plain-text-looking-like-variable-interpolation-in-perl-regular-exp#comment83026212_47810705 – Thorsten Schöning Dec 29 '17 at 20:35