Reputation: 8757
I need to replace a string in a lot of files in a folder, with only ssh
access to the server. How can I do this?
Upvotes: 856
Views: 1082510
Reputation: 9
Below command can be used to first search the files and replace the files:
find . | xargs grep 'search string' | sed 's/search string/new string/g'
For example
find . | xargs grep abc | sed 's/abc/xyz/g'
Upvotes: -1
Reputation: 720
On the command line it depends on the string you're trying to search-and-replace. What works in my case:
STR="a-?fairly@complicated,*String;but%3A=fortunately&without_quotes.css"
for f in $(grep -lir "$STR" *); do sed -i "s/$STR/newstyle.css/g" $f; done
66% speed boost relative to find
, grepping recursively over ~15k files generated by a web spider. The grep
-with-xargs -i@
answer didn't work for me, possibly due to the @
symbol.
Upvotes: 2
Reputation: 25267
I used ag
, the_silver_searcher:
ag -0 -l 'old' | xargs -0 sed -ri.bak -e 's/old/new/g';
Then git clean
to remove .bak files (rm
was being buggy when running inside a git rebase exec
)
git clean -f '**/*.bak';
Upvotes: 0
Reputation: 53
I found this one from another post (can't remember which) and while not the most elegant, it's simple and as a novice Linux user has given me no trouble
for i in *old_str* ; do mv -v "$i" "${i/\old_str/new_str}" ; done
if you have spaces or other special characters use a \
for i in *old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done
for strings in sub-directories use **
for i in *\*old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done
Upvotes: 1
Reputation: 921
There is an easier way by using a simple script file:
# sudo touch /bin/replace_string_files_present_dir
# sudo chmod +x /bin/replace_string_files_present_dir
open the file in gedit or an editor of your choice, I use gedit here.
# sudo gedit /bin/replace_string_files_present_dir
Then in the editor paste the following in the file
#!/bin/bash
replace "oldstring" "newstring" -- *
replace "oldstring1" "newstring2" -- *
#add as many lines of replace as there are your strings to be replaced for
#example here i have two sets of strings to replace which are oldstring and
#oldstring1 so I use two replace lines.
Save the file, close gedit, then exit your terminal or just close it and then start it to be able load the new script you added.
Navigate to the directory where you have multiple files you want to edit. Then run:
#replace_string_files_present_dir
Press enter and this will automatically replace the oldstring and oldstring1 in all the files that contain them with the correct newstring and newstring1 respectively.
It will skip all the directories and files that don't contain the old strings.
This might help in eliminating the tedious work of typing in case you have multiple directories with files that need string replacement. All you have to do is navigate to each of those directories, then run:
#replace_string_files_present_dir
All you have to do is to ensure you've included or added all the replacement strings as I showed you above:
replace "oldstring" "newstring" -- *
at the end of the file /bin/replace_string_files_present_dir.
To add a new replace string just open the script we created by typing the following in the terminal:
sudo gedit /bin/replace_string_files_present_dir
Don't worry about the number of replace strings you add, they will have no effect if the oldstring is not found.
Upvotes: 1
Reputation: 11
If the file contains backslashes (paths usually) you can try something like this:
sed -i -- 's,<path1>,<path2>,g' *
ex:
sed -i -- 's,/foo/bar,/new/foo/bar,g' *.sh (in all shell scripts available)
Upvotes: 1
Reputation: 6400
The stream editor does modify multiple files “inplace” when invoked with the -i
switch, which takes a backup file ending as argument. So
sed -i.bak 's/foo/bar/g' *
replaces foo
with bar
in all files in this folder, but does not descend into subfolders. This will however generate a new .bak
file for every file in your directory.
To do this recursively for all files in this directory and all its subdirectories, you need a helper, like find
, to traverse the directory tree.
find ./ -print0 | xargs -0 sed -i.bak 's/foo/bar/g' *
find
allows you further restrictions on what files to modify, by specifying further arguments like find ./ -name '*.php' -or -name '*.html' -print0
, if necessary.
Note: GNU sed
does not require a file ending, sed -i 's/foo/bar/g' *
will work, as well; FreeBSD sed
demands an extension, but allows a space in between, so sed -i .bak s/foo/bar/g *
works.
Upvotes: 2
Reputation: 24866
To maintain my personal English node, I wrote an utility script that help to replace multiple pair of old/new string, for all files under a directory recursively.
The multiple pair of old / new string are managed in a hash map.
The dir can be set via command line or environment variable, the map is hard coded in the script, but you can modify the code to load from a file, if necessary.
It requires bash 4.2, due to some new feature.
en_standardize.sh:
#! /bin/bash
# (need bash 4.2+,)
#
# Standardize phonetic symbol of English.
#
# format:
# en_standardize.sh [<dir>]
#
# params:
# * dir
# target dir, optional,
# if not specified then use environment variable "$node_dir_en",
# if both not provided, then will not execute,
# *
#
paramCount=$#
# figure target dir,
if [ $paramCount -ge 1 ]; then # dir specified
echo -e "dir specified (in command):\n\t$1\n"
targetDir=$1
elif [[ -v node_dir_en ]]; then # environable set,
echo -e "dir specified (in environment vairable):\n\t$node_dir_en\n"
targetDir=$node_dir_en
else # environable not set,
echo "dir not specified, won't execute"
exit
fi
# check whether dir exists,
if [ -d $targetDir ]; then
cd $targetDir
else
echo -e "invalid dir location:\n\t$targetDir\n"
exit
fi
# initial map,
declare -A itemMap
itemMap=( ["ɪ"]="i" ["ː"]=":" ["ɜ"]="ə" ["ɒ"]="ɔ" ["ʊ"]="u" ["ɛ"]="e")
# print item maps,
echo 'maps:'
for key in "${!itemMap[@]}"; do
echo -e "\t$key\t->\t${itemMap[$key]}"
done
echo -e '\n'
# do replace,
for key in "${!itemMap[@]}"; do
grep -rli "$key" * | xargs -i@ sed -i "s/$key/${itemMap[$key]}/g" @
done
echo -e "\nDone."
exit
Upvotes: 2
Reputation: 638
script for multiedit command
multiedit [-n PATTERN] OLDSTRING NEWSTRING
From Kaspar's answer I made a bash script to accept command line arguments and optionally limit the filenames matching a pattern. Save in your $PATH and make executable, then just use the command above.
Here's the script:
#!/bin/bash
_help="\n
Replace OLDSTRING with NEWSTRING recursively starting from current directory\n
multiedit [-n PATTERN] OLDSTRING NEWSTRING\n
[-n PATTERN] option limits to filenames matching PATTERN\n
Note: backslash escape special characters\n
Note: enclose STRINGS with spaces in double quotes\n
Example to limit the edit to python files:\n
multiedit -n \*.py \"OLD STRING\" NEWSTRING\n"
# ensure correct number of arguments, otherwise display help...
if [ $# -lt 2 ] || [ $# -gt 4 ]; then echo -e $_help ; exit ; fi
if [ $1 == "-n" ]; then # if -n option is given:
# replace OLDSTRING with NEWSTRING recursively in files matching PATTERN
find ./ -type f -name "$2" -exec sed -i "s/$3/$4/g" {} \;
else
# replace OLDSTRING with NEWSTRING recursively in all files
find ./ -type f -exec sed -i "s/$1/$2/" {} \;
fi
Upvotes: 3
Reputation: 8091
grep --include={*.php,*.html} -rnl './' -e "old" | xargs -i@ sed -i 's/old/new/g' @
Upvotes: 4
Reputation: 1951
On a MacBook Pro, I used the following (inspired by https://stackoverflow.com/a/19457213/6169225):
sed -i '' -e 's/<STR_TO_REPLACE>/<REPLACEMENT_STR>/g' *
-i ''
will ensure you are taking no backups.
-e
for modern regex.
Upvotes: 6
Reputation: 121
The first line occurrences of "foo" will be replaced with "bar". And you can using the second line to check.
grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
grep 'foo' -r * | awk -F: {'print $1'} | sort -n | uniq -c
Upvotes: 12
Reputation: 1351
"You could also use find and sed, but I find that this little line of perl works nicely.
perl -pi -w -e 's/search/replace/g;' *.php
" (Extracted from http://www.liamdelahunty.com/tips/linux_search_and_replace_multiple_files.php)
My best results come from using perl and grep (to ensure that file have the search expression )
perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )
Upvotes: 12
Reputation: 20601
If you have list of files you can use
replace "old_string" "new_string" -- file_name1 file_name2 file_name3
If you have all files you can use
replace "old_string" "new_string" -- *
If you have list of files with extension, you can use
replace "old_string" "new_string" -- *.extension
Upvotes: 12
Reputation: 53540
To replace a path within files (avoiding escape characters) you may use the following command:
sed -i 's@old_path@new_path@g'
The @ sign means that all of the special characters should be ignored in a following string.
Upvotes: 32
Reputation: 2671
Given you want to search for the string search
and replace it with replace
across multiple files, this is my battle-tested, one-line formula:
grep -RiIl 'search' | xargs sed -i 's/search/replace/g'
Quick grep explanation:
-R
- recursive search-i
- case-insensitive-I
- skip binary files (you want text, right?)-l
- print a simple list as output. Needed for the other commandsThe grep output is then piped to sed (through xargs) which is used to actually replace text. The -i
flag will alter the file directly. Remove it for a kind of "dry run" mode.
Upvotes: 39
Reputation: 525
To replace a string in multiple files you can use:
grep -rl string1 somedir/ | xargs sed -i 's/string1/string2/g'
E.g.
grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'
Upvotes: 41
Reputation: 3078
There are a few standard answers to this already listed. Generally, you can use find to recursively list the files and then do the operations with sed or perl.
For most quick uses, you may find the command rpl is much easier to remember.
Replace foo
with bar
on all .txt
files:
rpl -v foo bar '*.txt'
Simulate replacing the regex foo.*
with bar
in all .txt
files recursively:
rpl --dry-run 'foo.*' bar '**/*.txt'
You'll probably need to install it (apt-get install rpl
or similar).
However, for tougher jobs that involve regular expressions and back substitution, or file renames as well as search-and-replace, the most general and powerful tool I'm aware of is repren, a small Python script I wrote a while back for some thornier renaming and refactoring tasks. The reasons you might prefer it are:
To use it, pip install repren
. Check the README for examples.
Upvotes: 72
Reputation: 8185
@kev's answer is good, but only affects files in the immediate directory. The example below uses grep
to recursively find files. It works for me every time.
grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @
Command breakdown
grep -r
: --recursive
, recursively read all files under each directory.
grep -l
: --print-with-matches
, prints the name of each file that has a match, instead of printing matching lines.
grep -i
: --ignore-case
.
xargs
: transform the STDIN to arguments, following this answer.
xargs -i@ ~command contains @~
: a placeholder for the argument to be used in a specific position in the ~command~
. The @
sign is a placeholder which could replaced by any string.
sed -i
: edit files in place, without backups.
sed s/regexp/replacement/
: substitute string matching regexp
with replacement
.
sed s/regexp/replacement/g
: global, make the substitution for each match instead of only the first match.
Upvotes: 371
Reputation: 10664
Similar to Kaspar's answer but with the g flag to replace all the occurrences on a line.
find ./ -type f -exec sed -i 's/old_string/new_string/g' {} \;
For global case insensitive:
find ./ -type f -exec sed -i 's/old_string/new_string/gI' {} \;
Ignoring .git
directory:
find . -not -path '*/\.git/*' -name '*.rb' -exec sed -i 's/old_string/new_string/g' '{}' \;
Upvotes: 400
Reputation: 161604
cd /path/to/your/folder
sed -i 's/foo/bar/g' *
Occurrences of "foo" will be replaced with "bar".
On BSD systems like macOS, you need to provide a backup extension like -i '.bak'
or else "risk corruption or partial content" per the manpage.
cd /path/to/your/folder
sed -i '.bak' 's/foo/bar/g' *
Upvotes: 1026
Reputation: 175
After research I found this solution (\b is for word limit if you only want to change words):
$ STRING_OLD="\bPeter\b"; STRING_NEW="John"; find bin/ -type f -name '*.sh' -not -name "*.bak" -print0 | xargs -0 grep -Zl "$STRING_OLD" | xargs -t -0 sed -i.bak 's/'"$STRING_OLD"'/'"$STRING_NEW"'/g'
It works with filenames/directories with spaces,tabs,new lines and non ASCII chars.
Variable STRING_OLD is a regexp for sed s/regexp/replacement/ command. You must be aware of it.
You also see changed files. It only changes (and backs up) in files where STRING_OLD is found in regexp. The rest of the files remain untouched (that's what I wanted).
Upvotes: 0
Reputation: 1502
This worked for me:
find ./ -type f -exec sed -i 's/string1/string2/' {} \;
However, this did not: sed -i 's/string1/string2/g' *
. Maybe "foo" was not meant to be string1 and "bar" not string2.
Upvotes: 64
Reputation: 51
My problem with many of the answers was that I needed to replace a filepath inside of many files. Though one answer provided mentioned this, it did not work for me. My solution:
First, generate a list of the filenames that you want to be changed.
filelist=($(find /path/to/your/folder | xargs grep '/path/to/fix' | cut -d : -f 1 | tr '\n' ' '))
What the commands above do is that the find
piped to grep
generates the names of the files with the /path/to/fix
inside. However, grep
also prints out the line that the string was found on, so the cut
command gets rid of this and just keeps the filename. tr
replaces newline characters with spaces, which allows for filelist
to be stored as an array.
for file in "${filelist[@]}"; do sed -i.bak 's+/path/to/fix+/new/path/for/my/file+g' $file; done
This sed
command draws on other answers to this question, and uses +
as delimiters rather than the normal /
, since the /
characters are being used in the filepath.
Upvotes: 0
Reputation: 125
I'd just like to add a note to do two things at once - find a file that contains a string and then do a replace, using the find 'chaining' method:
find . -type f -iname \*.php -exec fgrep -l "www." {} \; -exec sed -i "s|www||g" {} \;
In this real case, remove the anachronistic 'www' from urls found in PHP files.
The 'fgrep -l' only triggers if it finds at least one match in a file, it produces no other output. Don't forget the '\;' separators!
Upvotes: 4
Reputation: 23445
Using the ack command would be alot faster like this:
ack '25 Essex' -l | xargs sed -i 's/The\ fox \jump/abc 321/g'
Also if you have a white space in the search result. You need to escape it.
Upvotes: 1
Reputation: 494
In case your string has a forward slash(/) in it, you could change the delimiter to '+'.
find . -type f -exec sed -i 's+http://example.com+https://example.com+g' {} +
This command would run recursively in the current directory.
Upvotes: 26
Reputation: 737
Really lame, but I couldn't get any of the sed commands to work right on OSX, so I did this dumb thing instead:
:%s/foo/bar/g
:wn
^- copy these three lines into my clipboard (yes, include the ending newline), then:
vi *
and hold down command-v until it says there's no files left.
Dumb...hacky...effective...
Upvotes: 5
Reputation: 51
I am giving an example for fixing a common shebang error in python sources.
You can try the grep/sed approach. Here is one that works with GNU sed and won't break a git repo:
$ grep -rli --exclude '*.git*' '#!/usr/bin/python' . | xargs -I {} \
gsed -i '' -e 's/#!\/usr\/bin\/python/#!\/usr\/bin\/env python/' {}
Or you can use greptile :)
$ greptile -x .py -l -i -g '#!/usr/bin/env python' -r '#!/usr/bin/python' .
I just tested the first script, and the second should work as well. Be careful with escape characters, I think it should be easier to use greptile in most cases. Of course, you can do many interesting things with sed, and for that it may be preferable to master using it with xargs.
Upvotes: 1
Reputation: 2094
I did concoct my own solution before I found this question (and answers). I searched for different combinations of "replace" "several" and "xml," because that was my application, but did not find this particular one.
My problem: I had spring xml files with data for test cases, containing complex objects. A refactor on the java source code changed a lot of classes and did not apply to the xml data files. In order to save the test cases data, I needed to change all the class names in all the xml files, distributed across several directories. All while saving backup copies of the original xml files (although this was not a must, since version control would save me here).
I was looking for some combination of find
+ sed
, because it worked for me in other situations, but not with several replacements at once.
Then I found ask ubuntu response and it helped me build my command line:
find -name "*.xml" -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;
And it worked perfectly (well, my case had six different replacements). But please note that it will touch all *.xml files under current directory. Because of that, and if you are accountable to a version control system, you might want to filter first and only pass on to sed
those actually having the strings you want; like:
find -name "*.xml" -exec grep -e "firstWord" -e "secondWord" -e "thirdWord" {} \; -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;
Upvotes: 5