the_mandrill
the_mandrill

Reputation: 30832

Simple search and replace without regex

I've got a file with various wildcards in it that I want to be able to substitute from a (Bash) shell script. I've got the following which works great until one of the variables contains characters that are special to regexes:

VERSION="1.0"
perl -i -pe "s/VERSION/${VERSION}/g" txtfile.txt    # No problems here

APP_NAME="../../path/to/myapp"
perl -i -pe "s/APP_NAME/${APP_NAME}/g" txtfile.txt  # Error!

So instead I want something that just performs a literal text replacement rather than a regex. Are there any simple one-line invocations with Perl or another tool that will do this?

Upvotes: 27

Views: 8929

Answers (7)

sehe
sehe

Reputation: 392843

Use:

 perl -i -pe "\$r = qq/\Q${APP_NAME}\E/; s/APP_NAME/\$r/go"

Rationale: Escape sequences

\Q          quote (disable) pattern metacharacters until \E
\E          end either case modification or quoted section, think vi

The qq/abc/ syntax is used instead of "abc" to double-quote the string.

Upvotes: 3

Tom Fenech
Tom Fenech

Reputation: 74596

I managed to get a working solution, partly based on bits and pieces from other peoples' answers:

app_name='../../path/to/myapp'
perl -pe "\$r = q/${app_name//\//\\/}/; s/APP_NAME/\$r/g" <<<'APP_NAME'

This creates a Perl variable, $r, from the result of the shell parameter expansion:

${app_name//\//\\/}

${            # Open parameter expansion
app_name      # Variable name
//            # Start global substitution
\/            # Match / (backslash-escaped to avoid being interpreted as delimiter)
/             # Delimiter
\\/           # Replace with \/ (literal backslash needs to be escaped)
}             # Close parameter expansion

All that work is needed to prevent forward slashes inside the variable from being treated as Perl syntax, which would otherwise close the q// quotes around the string.

In the replacement part, use the variable $r (the $ is escaped, to prevent it from being treated as a shell variable within double quotes).

Testing it out:

$ app_name='../../path/to/myapp'
$ perl -pe "\$r = q/${app_name//\//\\/}/; s/APP_NAME/\$r/g" <<<'APP_NAME'
../../path/to/myapp

Upvotes: 0

Michael Goldshteyn
Michael Goldshteyn

Reputation: 74340

Use the following:

perl -i -pe "s|APP_NAME|\\Q${APP_NAME}|g" txtfile.txt

Since a vertical bar is not a legal character as part of a path, you are good to go.

Upvotes: 12

Jess
Jess

Reputation: 25039

I don't particularly like this answer because there should be a better way to do a literal replace in Perl. \Q is cryptic. Using quotemeta adds extra lines of code.

But... You can use substr to replace a portion of a string.

#!/usr/bin/perl
my $name = "Jess.*";
my $sentence = "Hi, my name is Jess.*, dude.\n";
my $new_name = "Prince//";
my $name_idx = index $sentence, $name;
if ($name_idx >= 0) {
    substr($sentence, $name_idx, length($name), $new_name);
}
print $sentence;

Output:

Hi, my name is Prince//, dude.

Upvotes: 9

glenn jackman
glenn jackman

Reputation: 246744

You don't have to use a regular expression for this (using substr(), index(), and length()):

perl -pe '
  foreach $var ("VERSION", "APP_NAME") {
    while (($i = index($_, $var)) != -1) {
      substr($_, $i, length($var)) = $ENV{$var};
    }
  }
'

Make sure you export your variables.

Upvotes: 6

Borodin
Borodin

Reputation: 126722

The 'proper' way to do this is to escape the contents of the shell variables so that they aren't seen as special regex characters. You can do this in Perl with \Q, as in

s/APP_NAME/\Q${APP_NAME}/g

but when called from a shell script the backslash must be doubled to avoid it being lost, like so

perl -i -pe "s/APP_NAME/\\Q${APP_NAME}/g" txtfile.txt

But I suggest that it would be far easier to write the entire script in Perl

Upvotes: 20

codemaker
codemaker

Reputation: 1732

You can use a regex but escape any special characters.

Something like this may work.

APP_NAME="../../path/to/myapp"
APP_NAME=`echo "$APP_NAME" | sed -e '{s:/:\/:}'`
perl -i -pe "s/APP_NAME/${APP_NAME}/g" txtfile.txt

Upvotes: 2

Related Questions