Recursive Search and replace with / and ' - perl

I have searched and not found a solution so sorry if this has been answered before, I'm not great at shell.
I'm trying to do a recursive search and replace in all files via SSH.
So far I've got this:
find . -type f | xargs -d "\n" perl -pi -e 's/$this->helper('catalog/product')->getPriceHtml/$this->getPriceHtml/g'
I'm trying to replace this:
$this->helper('catalog/product')->getPriceHtml
with this:
$this->getPriceHtml
But I think its not working because of the slashes and single quotes. I have tried escaping these with \ but to no avail, any ideas?

An alternate delimiter for the s operator could be used to avoid picket fences. $this will be considered to be a variable unless the $ is escaped. The parentheses have to be escaped as well. Else they form a capture group. Since it is a one-liner and quotes have been exhausted, single-quotes have been encoded using hexadecimal escapes. The following should work:
s{\$this->helper\(\x{27}catalog/product\x{27}\)->getPriceHtml}{\$this->getPriceHtml}g;
Or:
s{(?<=\$this)->helper\(\x{27}catalog/product\x{27}\)(?=->getPriceHtml)}{}g;

Related

What do I miss in this sed Expression?

I'd like to replace the database server of a horde config file from "localhost" to a remote server (I use "database.contoso.com" as a placeholder).
The file in question is /var/www/horde/config/conf.php.
The line in the file looks like this:
$conf['sql']['hostspec'] = 'localhost';
Now I have created a sed line like so:
sed s/\$conf\[\'sql\'\]\[\'hostspec\'\]\ \=\ \'localhost\'\;/\$conf\[\'sql\'\]\[\
'hostspec\'\]\ \=\ \'database\.contoso\.com\'\;/ /var/www/horde/config/conf.php
But for whatever reason, it does not work -I spare out the -i option for later.
While trying to figure out, why it does not work, I did this:
echo "\$conf['sql']['hostspec'] = 'localhost';"|sed s/\$conf\[\'sql\'\]\[\'hostspec\'\]\ \=\ \'localhost\'\;/\$conf\[\'sql\'\]\[\'hostspec\'\]\ \=\ \'database\.contoso\.com\'\;/
which returns this:
$conf['sql']['hostspec'] = 'localhost';
but it should return:
$conf['sql']['hostspec'] = 'database.contoso.com';
What am I missing?
From Escape a string for a sed replace pattern in this case it would work:
KEYWORD="\$conf['sql']['hostspec'] = 'localhost';"
REPLACE="\$conf['sql']['hostspec'] = 'database.contoso.com';"
ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/"
The immediate problem is that you are not quoting enough. To match a regex metacharacter literally, you need to pass in a literal backslash \\ followed by a literal, like for example \[. But the simplest solution by far is to use single quotes around your expression, and then only backslash the characters which are regex metacharacters.
Literal single quotes inside single quotes are still challenging. Here, I have chosen to end the single-quoted string, insert a backslash-escaped but otherwise unquoted single quote, and add an opening single quote to continue with another single-quoted string. The shell glues these together into a single string.
echo "\$conf['sql']['hostspec'] = 'localhost';" |
sed 's/\$conf\['\''sql'\''\]\['\''hostspec'\''\] = '\''localhost'\'';/$conf['\''sql'\'']['\''hostspec'\''] = '\''database.contoso.com'\'';/'
A better solution generally is to use backreferences to quote back part of the matched string so you don't have to repeat it.
echo "\$conf['sql']['hostspec'] = 'localhost';" |
sed 's/\(\$conf\['\''sql'\''\]\['\''hostspec'\''\] = '\''\)[^'\'']*'\'';/\1database.contoso.com'\'';/'
Demo: https://ideone.com/RA0MSi
A much much much better solution is to change your PHP script so that this setting can be overridden with an option, an environment variable, and/or a configuration file.
This might work for you (GNU sed & shell):
sed -E 's/(\$conf\[('\'')sql\2]\[\2hostspec\2\] = )\2localhost\2;/\1\2database.contoso.com\2;/' file
Use pattern matching to match and replace.
N.B. Certain metacharacters must be escaped/quoted i.e. $,[,] and then because the sed commands are surrounded by single quotes, each single quote (within the substitution command) must be replaced by '\'' (see here for reasoning). Also, back references can be used both in the RHS and the LHS of the substitution command. The back references in the LHS especially allow for the shortening of the overall command and perhaps make the regexp more readable.

Issue with sed command in Bash for string replacement

I've been looking for a way to find-and-replace strings in all fortran files in my current directory. Most answers on here are along the lines of using:
sed -i 's/INCLUDE \'atm/params.inc\'/USE params/g' *.f
or
perl -pi -w -e 's/INCLUDE \'atm/params.inc\'/USE params/g' *.f
However, when I use either of these the bash line continuation > pops up on the next line as if it's expecting input or another argument. I haven't seen anyone else encounter this and I am not sure what to do with it. Are my commands incorrect; am I missing something?
There are two problems with the original:
You weren't protecting your / in the literal data from being parsed by sed rather than treated as data. One very readable and explicit way to do this is with [/].
You were trying to use \' to put a literal ' in a single-quoted string. That doesn't work. The common idiom is '"'"', which, character-by-character, does the following:
' - exits the original single-quoted context
" - opens a double-quoted context
' - adds a literal single-quote (protected by the surrounding double quotes)
" - ends that double-quoted context
' - resumes the outer single-quoted context.
Thus, consider:
# note: this works with GNU sed, not MacOS sed or others
sed -i 's/INCLUDE '"'"'atm[/]params.inc'"'"'/USE params/g' *.f

why is my perl command line replace not working?

I'm running this but it doesn't seem to replace anything:
perl -p -i -e 's/page?user_id=/page?uid=/g' *
What am I doing wrong here?
I want to replace page?user_id= with page?uid=
The '?' is a special character, indicating that the e needs to be matched 0 or once, so it needs to be escaped if you want to search for a '?' instead of an optional 'e'. Escaping with '\':
try
s/page\?user_id=/page?uid=/g
You can also use Quotemeta:
perl -pi -e 's/\Qpage?user_id=\E/page?uid=/g' file
As a side note I thought why don't you change only user_id to uid.

How to get sed to include a double \\ that's included in a variable

I'm writing a script in bash that performs a simple transform of a file we'll call storage.config.
Parameters are passed from our automation system (VCAC\AppD) to our action script, which performs the transform using sed.
To keep things simple, I'll use the following example
storage.config - To be transformed
url=jdbc:sqlserver://#myDB
Transform Script
myDB='serverxyz\\instance'
sed -i -e "s,#myDB,$myDB,g" storage.config
I would expect the resulting storage.config to look like this;
url=jdbc:sqlserver://serverxyz\\instance
However, it looks like this instead;
url=jdbc:sqlserver://serverxyz\instance
I've read through the answers on this site, as well as others. And have found a lot of useful information on how to include variable, single vs. double quotes, but nothing on how to retain a double \ in a variable. I'd like to get sed to interpret correctly, rather than type something like;
myDB='serverxyz\\\\instance'
This value will be entered by Solutions Engineers, who might enter improperly as they don't recognize it as a valid SQL instance.
sed is interpreting it correctly.
You are sticking \\ in the replacement as a literal string.
sed doesn't know it was a variable from somewhere else and not just typed out manually.
Escaping it is the answer.
You can do it at expansion time with ${myDB//\\/\\\\} if you want though.
Additionally, as #abesto quite correctly indicated in his answer, you are loosing the doubled slash before sed even sees it. Use single quotes in your assignment to preserve it.
myDB='server\\instance'
The double \ doesn't even get as far as sed. Your shell sees \\, and reads it as "oh, you want a \, because you escaped it". You can try it out like this:
myDB="serverxyz\\instance"
echo "$myDB"
# output: serverxyz\instance
So in conclusion (Thanks to all that responded. Special thanks to #Etan);
myDB='serverxyz\\instance'
sed -i -e "s,#myDB,${myDB//\\/\\\\},g" storage.config
The above code results in the proper translation\transform of my storage.config file. The resulting storage.config is as follows;
url=jdbc:sqlserver://serverxyz\\instance

What is the correct usage of (nested | double | simple) quotes

I'm sure this question may seem foolish to some of you, but I'm here to learn.
Are these assumptions true for most of the languages ?
EDIT : OK, let's assume I'm talking about Perl/Bash scripting.
'Single quotes'
=> No interpretation at all (e.g. '$' or any metacharacter will be considered as a character and will be printed on screen)
"Double quotes"
=> Variable interpretation
To be more precise about my concerns, I'm writing some shell scripts (in which quotes can sometimes be a big hassle), and wrote this line :
CODIR=`pwd | sed -e "s/$MODNAME//"`
If I had used single quotes in my sed, my pattern would have been '$MODNAME', right ? (and not the actual value of $MODNAME, which is `alpha' in this particular case)
Another problem I had, with an awk inside an echo :
USAGE=`echo -ne "\
Usage : ./\`basename $0\` [-hnvV]\n\
\`ls -l ${MODPATH}/reference/ | awk -F " " '$8 ~ /\w+/{print "> ",$8}'\`"`
I spent some time debugging that one. I came to the conclusion that backticks were escaped so that the interpreter doesn't "split" the command (and stop right before «basename»). In the awk commmand, '$8' is successfully interpreted by awk, thus not by shell. What if I wanted to use a shell variable ? Would I write awk -F "\"$MY_SHELL_VAR\"" ? Because $MY_SHELL_VAR as is, will be interpreted by awk, won't it ?
Don't hesitate to add any information about quoting or backticks !
Thank you ! :)
It varies massively by language. For example, in the C/Java/C++/C# etc family, you can't use single quotes for a string at all - they're only for single characters.
I think it's far better to learn the rules properly for the languages you're actually interested in than to try to generalise.
Are these assumptions true for most of the languages ?
Answer: No
In bash scripting, backticks are deprecated in favor of $() in part because it is non-obvious how nested quotes and escaping are supposed to work. You may also want to take a look at Bash Pitfalls.
It's definitely not the same for all languages. In Python, for example, single and double quotes are interchangeable. The only difference is that you can include single quotes within a double-quoted string without escaping them and vice versa ("How's it going?").
Also, there are triple-quoted strings that can span multiple lines.
In Perl, you also have q() and qq() to help you in nested quoting situations:
my $x = q(a string with 'single quotes');
my $y = qq(an $interpreted string with "double quotes");
These certainly will help you avoid "\"needlessly\"" '\'escaping\'' internal quotes.
Yes, something like awk -F "\"$MY_SHELL_VAR\"" will work, however in this case you wouldn't be able to use variables in awk, since they will be interpreted by shell, so the way to go is something like this (I will use command simpler than yours, if you don't mind :) ):
awk -F " " '$8 ~ /\w+/{print "> ",$8, '$SOME_SHELL_VAR'}'
Note the single quotes terminating and restarting.
The trickiest part, usually, is to pass a quote in the argument to the command. In this case you need to terminate single quote, add escaped quote character, start quote again, like this:
awk '$1 ~ '\''{print}'
Note, that single quote can't be escaped inside single quotes, since the "\" won't be treated as an escape character.
This is probably not related directly to your quiestion, but still useful.
I don't know about perl, but for bash you don't need to backslash the newline.
As for quotes, I have a (very personal) pattern that I call the "five quotes" pattern. It helps to put one quote in a string enclosed by the same kind of quotes
For instance:
doublequoted="some things "'"'"quoted"'"'" and some not"
simplequoted='again '"'"'quote this'"'"' but not that'
Note that you can freely append strings with different kinds of quotes, which is useful when you want the shell to interprete some vars but not some others:
awk -F " " '$8 ~ /\w+/{print "> ",$8, '"$SOME_SHELL_VAR"'}'
Also, I don't use the backtick anymore but the $(...)pattern which is more legible and can be nested.
USAGE=$(echo -ne "
Usage : ./$(basename $0) [-hnvV]\n
$(ls -l ${MODPATH}/reference/ | awk -F " " '$8 ~ /\w+/{print "> ",$8}')")
In perl, double quoted strings will have their variables expanded.
If you write that for instance:
my $email = "foo#bar.com" ;
perl will try to expand #bar. If you use strict, you'll see an complain about the array bar not existing. If you don't, you'll just see a weird behavior.
So it's better to write:
my $email = 'foo#bar.com' ;
For these types of reason, my advice is to always use single quote for strings, unless you know that you need variable expansion.