Bash: any command to replace strings in text files? - bash

I have a hierarchy of directories containing many text files. I would like to search for a particular text string every time it comes up in one of the files, and replace it with another string. For example, I may want to replace every occurrence of the string "Coke" with "Pepsi". Does anyone know how to do this? I am wondering if there is some sort of Bash command that can do this without having to load all these files in an editor, or come up with a more complex script to do it.
I found this page explaining a trick using sed, but it doesn't seem to work in files in subdirectories.

Use sed in combination with find. For instance:
find . -name "*.txt" | xargs sed -i s/Coke/Pepsi/g
or
find . -name "*.txt" -exec sed -i s/Coke/Pepsi/g {} \;
(See the man page on find for more information)

IMO, the tool with the easiest usage for this task is rpl:
rpl -R Coke Pepsi .
(-R is for recursive replacement in all subdirectories)

Combine sed with find like this:
find . -name "file.*" -exec sed -i 's/Coke/Pepsi/g' {} \;

find . -type f -exec sed -i 's/old-word/new-word/g' {} \;

I usually do it in perl. However watch out - it uses regexps which are much more powerful then normal string substitution:
% perl -pi -e 's/Coke/Pepsi/g;' $filename
EDIT I forgot about subdirectories
% find ./ -exec perl -pi -e 's/Coke/Pepsi/g;' {} \;

you want a combination of find and sed

You may also:
Search & replace with find & ed
http://codesnippets.joyent.com/posts/show/2299
(which also features a test mode via -t flag)

Related

Get all occurrences of a string within a directory(including subdirectories) in .gz file using bash?

I want to find all the occurrences of "getId" inside a directory which has subdirectories as follows:
*/*/*/*/*/*/myfile.gz
i tried thisfind -name *myfile.gz -print0 | xargs -0 zgrep -i "getId" but it didn't work. Can anyone tell me the best and simplest approach to get this?
find ./ -name '*gz' -exec zgrep -aiH 'getSorById' {} \;
find allows you to execute a command on the file using "-exe" and it replaces "{}" with the file name, you terminate the command with "\;"
I added "-H" to zgrep so it also prints out the file path when it has a match, as its helpful. "-a" treats binary files as text (since you might get tar-ed gzipped files)
Lastly, its best to quote your strings in case bash starts globbing them.
https://linux.die.net/man/1/grep
https://linux.die.net/man/1/find
Use the following find approach:
find . -name *myfile.gz -exec zgrep -ai 'getSORByID' {} \;
This will print all possible lines containing getSORByID substring

Why the command used to remove BOM doesn't work?

I found a command to find documents in my all-level folders and directories that are utf-8 with BOM and then remove the BOM. But it doesn't seem to work on my computer(osx)...Should I install moodle on my machine first in order to run it in my command line?
Below is the command:
find . -type f -exec sed 's/^\xEF\xBB\xBF//' -i.bak {} \; -exec rm {}.bak \;
The result I got is sed: -i.bak: No such file or directory and all the content in the files, which seems very weird.
Thank you for your help!
The command you found was for GNU's sed, which supports optional arguments anywhere. Worst of all, OS X's sed doesn't seem to support non-ASCII byte sequences.
Instead, for OS X use the following answer, which uses Perl: https://stackoverflow.com/a/9101056/1554386
Tying it into find is as follows:
find . -type f -exec perl -e 's/\xef\xbb\xbf//;' -pi.bak {} \;
You can add the -exec rm {}.bak \; from your command if you wish, but you can just as easily do that separately

How to use the name of the file with sed in a find expression

Trying to answer Using Bash/Perl to modify files based on each file's name I ended in a point in which I don't know how to use find and sed all together.
Let's say there is a certain structure of files in which we want to change a line, appending the name of the file.
If it was a normal for loop we would do:
for file in dir/*
do
sed -i "s/text/text plus $file/g" $file
done
But let's say we want to use find to change files from all subdirectories. In this case, I would use...
find . -type f -exec sed -i "s/text/text plus {}/g" {} \;
^
it does not like this part
but these {} within sed are not accepted and I get the error
sed: -e expression #1, char 20: unknown option to `s'
I found some similar questions (1) but could not generalize it enough to make it understandable for me in this case.
I am sure you guys will come with a great solution for this. Thanks!
I really think the issue is that your files name contains a / that is why sed believes it start the options strings.
Replace / by # in you sed command would do the job.
I try that on Linux BASH and it work perfectly
find . -type f -exec sed -i -e "s#text#test plus {}#g" {} \;
find would return pathnames (relative or absolute) depending upon the path you specify.
This would conflict with the delimiter you've specified, i.e. /. Change the delimiter for sed and you should be good:
find . -type f -exec sed -i "s|text|text plus {}|g" {} \;
EDIT: For removing the leading ./ from the paths, you can try:
find . -type f -exec sh -c '$f={}; f=${f/.\//}; sed -i "s|text|text plus ${f}|g" {}' \;
I'm certain that better solutions might exist ...

Find, grep, and execute - all in one?

This is the command I've been using for finding matches (queryString) in php files, in the current directory, with grep, case insensitive, and showing matching results in line:
find . -iname "*php" -exec grep -iH queryString {} \;
Is there a way to also pipe just the file name of the matches to another script?
I could probably run the -exec command twice, but that seems inefficient.
What I'd love to do on Mac OS X is then actually to "reveal" that file in the finder. I think I can handle that part. If I had to give up the inline matches and just let grep show the files names, and then pipe that to a third script, that would be fine, too - I would settle.
But I'm actually not even sure how to pipe the output (the matched file names) to somewhere else...
Help! :)
Clarification
I'd like to reveal each of the files in a finder window - so I'm probably not going to using the -q flag and stop at the first one.
I'm going to run this in the console, ideally I'd like to see the inline matches printed out there, as well as being able to pipe them to another script, like oascript (applescript, to reveal them in the finder). That's why I have been using -H - because I like to see both the file name and the match.
If I had to settle for just using -l so that the file name could more easily be piped to another script, that would be OK, too. But I think after looking at the reply below from #Charlie Martin, that xargs could be helpful here in doing both at the same time with a single find, and single grep command.
I did say bash but I don't really mind if this needs to be ran as /bin/sh instead - I don't know too much about the differences yet, but I do know there are some important ones.
Thank you all for the responses, I'm going to try some of them at the command line and see if I can get any of them to work and then I think I can choose the best answer. Leave a comment if you want me to clarify anything more.
Thanks again!
You bet. The usual thing is something like
$ find /path -name pattern -print | xargs command
So you might for example do
$ find . -name '*.[ch]' -print | xargs grep -H 'main'
(Quiz: why -H?)
You can carry on with this farther; for example. you might use
$ find . -name '*.[ch]' -print | xargs grep -H 'main' | cut -d ':' -f 1
to get the vector of file names for files that contain 'main', or
$ find . -name '*.[ch]' -print | xargs grep -H 'main' | cut -d ':' -f 1 |
xargs growlnotify -
to have each name become a Growl notification.
You could also do
$ grep pattern `find /path -name pattern`
or
$ grep pattern $(find /path -name pattern)
(in bash(1) at least these are equivalent) but you can run into limits on the length of a command line that way.
Update
To answer your questions:
(1) You can do anything in bash you can do in sh. The one thing I've mentioned that would be any different is the use of $(command) in place of using backticks around command, and that works in the version of sh on Macs. The csh, zsh, ash, and fish are different.
(2) I think merely doing $ open $(dirname arg) will opena finder window on the containing directory.
It sounds like you want to open all *.php files that contain querystring from within a Terminal.app session.
You could do it this way:
find . -name '*.php' -exec grep -li 'querystring' {} \; | xargs open
With my setup, this opens MacVim with each file on a separate tab. YMMV.
Replace -H with -l and you will get a list of those filenames that matched the pattern.
if you have bash4, simply do
grep pattern /path/**/*.php
the ** operator is like
grep pattern `find -name \*.php -print`
find /home/aaronmcdaid/Code/ -name '*.cpp' -exec grep -q -iH boost {} \; -exec echo {} \;
The first change I made is to add -q to your grep command. This is "Exit immediately with zero status if any match is found".
The good news is that this speeds up grep when a file has many matching lines. You don't care how many matches there are. But that means we need another exec on the end to actually print the filenames when grep has been successful
The grep result will be sent to stdout, so another -exec predicate is probably the best solution here.
Pipe to another script:
find . -iname "*.php" | myScript
File names will come into the stdin of myScript 1 line at a time.
You can also use xargs to form/execute commands to act on each file:
find . -iname "*.php" | xargs ls -l
act on files you find that match:
find . -iname "*.php" | xargs grep -l pattern | myScript
act that don't match pattern
find . -iname "*.php" | xargs grep -L pattern | myScript
In general using multiple -exec's and grep -q will be FAR faster than piping, since find has implied short circuits -a's separating each juxtaposed pair of expressions that's not separated with an explicit operator. The main problem here, is that you want something to happen if grep matches something AND for matches to be printed. If the files are reasonably sized then this should be faster (because grep -q exits after finding a single match)
find . -iname "*php" -exec grep -iq queryString {} \; -exec grep -iH queryString {} \; -exec otherprogram {} \;
If the files are particularly big, encapsulating it in a shell script may be faster then running multiple grep commands
find . -iname "*php" -exec bash -c \
'out=$(grep -iH queryString "$1"); [[ -n $out ]] && echo "$out" && exit 0 || exit 1' \
bash {} \; -print
Also note, if the matches are not particularly needed, then
find . -iname "*php" -exec grep -iq queryString {} \; -exec otherprogram {} \;
Will virtually always be faster than then a piped solution like
find . -iname "*php" -print0 | xargs -0 grep -iH | ...
Additionally, you should really have -type f in all cases, unless you want to catch *php directories
Regarding the question of which is faster, and you actually care about the minuscule time difference, which maybe you might if you are trying to see which will save your processor some time... perhaps testing using the command as a suffix to the "time" command, and see which one performs better.

Shell script traversing the all subdirectories and modifying the content of files

I need to modify a number of files inside a directory. I need to modify all those files which contain particular text and have to replace with some new text.
So I thought of writing a shell script which will traverse through all the subdirectories and modify the content but I'm having problem while traversing the all possible directories.
You can use find to traverse through subdirectories looking for files and then pass them on to sed to search and replace for text.
e.g.
find /some/directory -type f -name "*.txt" -print -exec sed -i 's/foo/bar/g' {} \;
will find all txt files and replace foo with bar in them.
The -i makes sed change the files in-place. You can also supply a backup-suffix to sed if you want the files backed up before being changed.
GNU find
find /some_path -type f -name "*.txt" -exec sed -i 's/foo/bar/g' "{}" +;
http://git.savannah.gnu.org/cgit/bash.git/tree/examples/functions/recurse
:)
You want find.
for n in $(find | grep txt$)
do
echo $n
modify_content.sh $n
done

Resources