Substitute string in BASH with Sed (when it has special characters) - bash

It's a fairly simple question and I'm sure the gurus here can figure it out right away, however I don't seem to be able to make it work (probably some quotes issue.
I want to place all instances of:
`which cat`
With the following:
/bin/cat
I am running the following command:
for file in $(find . -iname 'PATCH*'); do sed 's/\`which cat\`/\'\/bin\/cat/g' $file; done
I believe I have escaped all characters that don't need to be treated as special ones, however it doesn't seem to do the trick.
Please help :)

It is generally not a good idea to iterate over the output of find since file names can contain the $IFS which would break the loop. Use the -exec option of find instead:
find -iname 'PATCH*' -exec sed -i 's#`which cat`#/bin/cat#g' {} \;

Use a different sed delimiter.
sed 's~`which cat`~/bin/cat~g' file
Example:
$ echo '`which cat`' | sed 's~`which cat`~/bin/cat~g'
/bin/cat

Related

Error using sed with -i flag inside a bash for loop [duplicate]

Being forced to use CVS for a current client and the address changed for the remote repo. The only way I can find to change the remote address in my local code is a recursive search and replace.
However, with the sed command I'd expect to work:
find ./ -type f -exec sed -i "s/192.168.20.1/new.domain.com/" {} \;
I get an error for every file:
sed: 1: ".//file/path ...": invalid command code .
I've tried to escape the periods in the sed match/replacement but that doesn't solve anything.
If you are on a OS X, this probably has nothing to do with the sed command. On the OSX version of sed, the -i option expects an extension argument so your command is actually parsed as the extension argument and the file path is interpreted as the command code.
Try adding the -e argument explicitly and giving '' as argument to -i:
find ./ -type f -exec sed -i '' -e "s/192.168.20.1/new.domain.com/" {} \;
See this.
On OS X nothing helps poor builtin sed to become adequate. The solution is:
brew install gnu-sed
And then use gsed instead of sed, which will just work as expected.
You simply forgot to supply an argument to -i. Just change -i to -i ''.
Of course that means you don't want your files to be backed up; otherwise supply your extension of choice, like -i .bak.
Simply add an extension to the -i flag. This basically creates a backup file with the original file.
sed -i.bakup 's/linenumber/number/' ~/.vimrc
sed will execute without the error
It is not the case for the OP but it was for me and could help someone else.
If you are using ' to enclose regex, double check the ' characters. I was copying and pasting the script from word processing and it was pasting ' as ’ in bash.
Probably your new domain contain / ? If so, try using separator other than / in sed, e.g. #, , etc.
find ./ -type f -exec sed -i 's#192.168.20.1#new.domain.com#' {} \;
It would also be good to enclose s/// in single quote rather than double quote to avoid variable substitution or any other unexpected behaviour

How to overwrite the contents in the sed, without having backup file

I have a command like this:
sed -i -e '/console.log/ s/^\/*/\/\//' *.js
which does comments out all console.log statements. But there are two things
It keeps the backup file like test.js-e , I doesn't want to do that.
Say I want to the same process recursive to the folder, how to do it?
You don't have to use -e option in this particular case as it is unnecessary. This will solve your 1st problem (as -e seems to be going as suffix for -i option).
For the 2nd part, u can try something like this:
for i in $(find . -type f -name "*.js"); do sed -i '/console.log/ s/^\/*/\/\//' $i; done;
Use find to recursively find all .js files and do the replacement.
When checking sed's help, -i takes a suffix and uses it as a backup,
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if SUFFIX supplied)
and the output backup seems to be samefile + -e which is the second argument you're sending, try removing the space and see if that would work
sed -ie '/console.log/ s/^\/*/\/\//' *.js
As for the recursion, you could use find with -exec or xargs, please modify the find command and test it before running exec
find -name 'console.log' -type f -exec sed -ie '/console.log/ s/^\/*/\/\//' *.js \;
From your original post I presume you just want to make a C-style comment leading like:
/*
to a double back-slash style like:
//
right?
Then you can do it with this command
find . -name "*.js" -type f -exec sed -i '/console.log/ s#^/\*#//#g' '{}' \;
To be awared that:
in sed the split character normally be / but if you found that annoying to Escape when your replacing or matching string contains a / . You can change the split character to # or | as you like, I found it very useful trick.
if you do want to do is what I presumed, be sure that you should Escape the character *, because a combination of regex /* just means to match a pattern that / occurs one time or many times or none at all, that will match everything, it's very dangerous!

Sed over BASH escaping

I am looking to go through our site and remove the encoded hard paths and replace them with $_SERVER['DOCUMENT_ROOT'] over a shell connection, but I am not sure how to escape it correctly.
Need to replace
"/home/imprint/public_html/template
With
$_SERVER['DOCUMENT_ROOT']."/template
Here is what I found to do it, but I also need to include .htm files and I am not sure what I need to escape.
find . -name '*.php' -exec sed -i 's/"/home/imprint/public_html/template/$_SERVER['DOCUMENT_ROOT']."/template/g' {} \;
Also, what does the -i option do in sed?
You can combine find clauses with -o ("or")
If you use different delimiters for the sed s command, you don't need to escape anything.
search='"/home/imprint/public_html/template'
replace='$_SERVER['DOCUMENT_ROOT']."/template'
find . -name '*.php' -o -name '*.htm' \
-exec sed -i "s#${search}#${replace}#g" {} +
To gain efficiency by reducing the number of times sed is invoked, use -exec ... + instead of -exec ... \;
If you're replacing a fixed string with another one, you can use sed with single quotes rather than doubles, as it will prevent any interpretation of the $ sign or other unpredicted funkiness.
Also since you're replacing pathes, fyi you can use other chars than / as sed's delimiter (i.e. sed "s=abc=def=g"), which is probably clearer.
From the man page :
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
Maybe you can try the next
shopt -s globstar
perl -i.bak -pe 's:/home/imprint/public_html/(template):\$_SERVER['DOCUMENT_ROOT']/$1:' ./**/*.php
will create backup file .bak
the ./**/*.php will search for all php files recusively if the globstar option is set

Replacing a line from multiple files, limiting to a line number range

I have a large number of files and I want to replace some lines from all of these files. I don't know the exact contents of the lines, all I know is all of them contain two known words - let's say for example 'Programmer' and 'Bob'. So the lines I want to replace could be something like:
Created by Programmer Bob
Programmer extraordinaire Bob, such an awesome guy
Copyright programmer bob, all rights reserved
So far this sounds easy, but the problem is I only want to replace the lines that are contained within a line range - for example in the beginning of the file (where typically one would find comments regarding the file). I can't replace lines found in the later parts of the file, because I don't want to accidentally replace actual code.
So far I have tried:
find . -exec grep -il -E 'Programmer.*Bob' {} \; | xargs sed -i '1,10 /Programmer.*Bob/Ic\LINE REPLACED'
(I'm using find because grep ran into an infinite recursion - I think. Not the point here.)
However it seems that I can't use address ranges with c\ (change line). Feel free to point out any syntax errors, but I think I've tried everything to no avail. This does work without the line numbers.
EDIT:
I got the answer, but I decided to edit my question to include my solution which expands upon the answer I got - maybe someone will find this helpful.
I realised later that I want to retain the possible whitespace and comment characters in the beginning of the line. I accomplished it using this command:
find . -exec grep -ilI '.*Programmer.*Bob.*' {} \; xargs sed -i -r '1,10 s/([ \t#*]*)(.*Programmer.*Bob.*)/\1LINE REPLACED/I'
\1 keeps the pattern that matches [ \t#*]*. One could change this to ^[ \t#*]* that would anchor the pattern to the beginning of the line, but (I THINK) this current version would change
** Text I don't want to remove ** Programmer Bob
into
** Text I don't want to remove ** LINE REPLACED
Which could actually be better. (I also added the -I (capital i) flag to the find command, which skips binary files.)
You are mixing addresses and commands. Simple substitution should work:
find . -exec grep -il -E 'Programmer.*Bob' {} \; \
| xargs sed -i '1,10 s/.*Programmer.*Bob.*/LINE REPLACED/'
find . -type f -name "*.cpp"|xargs perl -pi -e 'if(/Programmer/ && /Bob/ && $.>=1 && $.<10){$_="line to replace"}'
sed command:
>sed '1,10 {s/programmer\|bob/LINE REPLACED/i;s/programmer\|bob//ig}' file

How can I process a list of files that includes spaces in its names in Unix?

I'm trying to list the files in a directory and do something to them in the Mac OS X prompt.
It should go like this: for f in $(ls -1); do echo $f; done
If I have files without spaces in their names (fileA.txt, fileB.txt), the echo works fine.
If the files include spaces in their names ("file A.txt", "file B.txt"), I get 4 strings (file, A.txt, file, B.txt).
I've tried quoting the listing command, but it only changed the problem.
If I do this: for f in $(ls -1); do echo $f; done
I get: file A.txt\nfile B.txt
(It displays correctly, but it is a single string and I need the 2 lines separated.
Step away from ls if at all possible. Use find from the findutils package.
find /target/path -type f -print0 | xargs -0 your_command_here
-print0 will cause find to output the names separated by NUL characters (ASCII zero). The -0 argument to xargs tells it to expect the arguments separated by NUL characters too, so everything will work just fine.
Replace /target/path with the path under which your files are located.
-type f will only locate files. Use -type d for directories, or omit altogether to get both.
Replace your_command_here with the command you'll use to process the file names. (Note: If you run this from a shell using echo for your_command_here you'll get everything on one line - don't get confused by that shell artifact, xargs will do the expected right thing anyway.)
Edit: Alternatively (or if you don't have xargs), you can use the much less efficient
find /target/path -type f -exec your_command_here \{\} \;
\{\} \; is the escape for {} ; which is the placeholder for the currently processed file. find will then invoke your_command_here with {} ; replaced by the file name, and since your_command_here will be launched by find and not by the shell the spaces won't matter.
The second version will be less efficient since find will launch a new process for each and every file found. xargs is smart enough to pipe the commands to a newly launched process if it can figure it's safe to do so. Prefer the xargs version if you have the choice.
for f in *; do echo "$f"; done
should do what you want. Why are you using ls instead of * ?
In general, dealing with spaces in shell is a PITA. Take a look at the $IFS variable, or better yet at Perl, Ruby, Python, etc.
Here's an answer using $IFS as discussed by derobert
http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
You can pipe the arguments into read. For example, to cat all files in the directory:
ls -1 | while read FILENAME; do cat "$FILENAME"; done
This means you can still use ls, as you have in your question, or any other command that produces $IFS delimited output.
The while loop makes it much easier to do several things to the argument, and makes complex processing more readable in my opinion. A contrived example:
ls -1 | while read FILE
do
echo 1: "$FILE"
echo 2: "$FILE"
done
look --quoting-style option.
for instance, --quoting-style=c would produce :
$ ls --quoting-style=c
"file1" "file2" "dir one"
Check out the manpage for xargs:
it works like this:
ls -1 /tmp/*.jpeg | xargs rm

Resources