0

I got some filenames containing characters that are regex special control ones.

I need to prepare a regex that considers all these characters literally.

Simplified test case:

strFilenameOnDB="some ( file ) name +.ok";
strFilenameToCheck="$strFilenameOnDB"; #code simplification
strRegex=".*${strFilenameToCheck}.*";
if [[ "$strFilenameOnDB" =~ $strRegex ]];then echo OK;fi

the above will (of course) fail.

in perl we can use /Q /E (https://stackoverflow.com/a/3971923/1422630) to turn the expanded $strRegex into literal, is there anything like that for bash?

Obs.: I will post what I am already doing, but I wonder if there is a better way?

2

In Bash’s =~ match operator, literal strings in the regex can be specified by putting them within double-quotes.

So in theory you’d just need to turn Perl’s \Q and \E into one double-quote each.

But, however, if your requirement is to use a regex that is partly variable (ie contains other shell variables to be expanded) and partly literal, and that is itself contained in a shell variable, then I’m afraid the only way out is to also use eval.

That is, your example code would become like this:

strFilenameOnDB="some ( file ) name +.ok";
strFilenameToCheck="$strFilenameOnDB"; #code simplification
strRegex=".*\"${strFilenameToCheck}\".*";  # <<--- note the backslash before each _inner_ double-quote: this is Bash’s syntax to embed a literal double-quote in a string _made by_ double-quotes

# then we shall use eval on the whole test operation

if eval '[[ "${strFilenameOnDB}" =~ '"${strRegex}"' ]]';then echo OK;fi

# or, using a fine Bash’s shortcut:

eval '[[ "${strFilenameOnDB}" =~ '"${strRegex}"' ]]' && echo OK

To sum it up, in order to embed literal strings in a partly variable regex contained in a shell variable, you need to:

  1. use \" and another \" in place of Perl’s \Q and \E
  2. embed the whole test command inside a carefully quoted eval

All this is required in order to expand the string containing the regex first, so that the two " in the shell variable are considered as start-end of literal part of the regex rather than as the usual Bash’s quoting characters, and then the entire match operation is executed on such resulting pattern.

(A well harder task comes when you have to include double-quotes or backslashes within a regex within a double-quoted shell variable..)

As a side-note, you don’t actually need the .* at beginning and end of a regex because those are usually implied in Bash’s regex operation. In fact you rather need to explicitly specify start-end anchors (^ and $) when you don’t want to imply other characters before and after a regex.

  • this worked on the terminal test case: strRegex=".*\"$strFilenameToCheck\".*". But when the if is inside a function, only the eval tip worked, thx! :) – Aquarius Power Mar 29 at 0:15
  • 1
    Ok, however I just noticed a typo I made when I first edited the answer..: please surround with double-quotes the ${strRegex} in the eval, otherwise in case of glob chars like * ? [ ] ( ) alone (ie with spaces around) they would be interpreted by the shell as file name expansion thus listing matching file names present in current directory. – LL3 Mar 29 at 1:11
  • cool thx again! – Aquarius Power Mar 29 at 2:06
2

Are you just looking to see if the filename contains a particular substring? Because if you do that with [[ =~ ]], you don't need the leading and trailing .* parts: the regex match is more like a search, it's enough for a match to be found anywhere in the string.

Also, in Bash, quoting (parts of) the pattern (or a variable containing the pattern) removes the special meanings of the quoted characters. So, e.g. this would match:

re=' + '
[[ "foo + doo" =~ "$re" ]] && echo match

while this doesn't (the plus is special now and doesn't match itself):

re=' + '
[[ "foo + doo" =~ $re ]] && echo match

In comparison, a non-regex match will require matching against the whole string, so you need a leading and a trailing *:

pattern=' * '
[[ "foo * doo" = *"$pattern"* ]] && echo match
2

Personally, I would not combine the string that you'd like to be literal with the regular expression bits that you'd like to be interpreted as regular expression pattern. The literal string bit of the expression should be double quoted, the bits that needs to be interpreted as a regular expression should not be.

[[ $strFilenameOnDB =~ .*"$strFilenameToCheck".* ]] && echo OK

In this case though, since regular expressions aren't by default anchored to the start or end of the string (unlike filename globbing patterns that are always matching a complete string), you could do without the flanking .* completely.

  • very interesting thx! but the regex is actually a parameter and comes from outside the function (that would be only the if line here). – Aquarius Power Mar 28 at 21:42
0

I change the matcher this way:

sedExact='s"(.)"[\1]"g';
strRegex=".*$(echo "$strFilenameToCheck" |sed -r "$sedExact").*";

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.