How can I replace a string in a file(s)?Using 'sed' to find and replaceHow do I delete a line only if it is...
Fermat's polygonal number theorem
Why did Crew Dragon switch to burst disks instead of multiple check valves?
Are there 99 percentiles, or 100 percentiles? And are they groups of numbers, or dividers or pointers to individual numbers?
How to see time in ~/.bash_history file
In the comics, have any of the Robins called their costume "target attraction" for villains?
How to plot two axis bar chart side by side
Is there a historical explanation as to why the USA people are so litigious compared to France?
Idiom for a situation or event that makes one poor or even poorer?
Can you upgrade armour from breastplate to half plate?
Disordered Cryptic Orders
What is the design rationale for having armor and magic penetration mechanics?
Satellite for internet... Safe enough?
Incorrect mmap behavior when assembly files included in the project
What does すきすき mean here?
How does Firefox know my ISP login page?
Advisor asked for my entire slide presentation so she could give the presentation at an international conference
Is there a push, in the United States, to use gender-neutral language and gender pronouns (when they are given)?
How to respond to "Why didn't you do a postdoc after your PhD?"
How to prove that invoices are really UNPAID?
Function of 令 in this sentence (cantonese)
Why does Sonny say they call Michael "Giogali"?
Slaad Chaos Phage: Weak Combat Ability?
How many records can an Apex Batch process
Extra battery in the bay of an HDD
How can I replace a string in a file(s)?
Using 'sed' to find and replaceHow do I delete a line only if it is at the specified line number and it matches the pattern?how to replace a specific pattern from all filesHow can i replace a specific string within a line inside a text fileHow to replace the last octet of a valid network address with the number 2How to edit a file from a script using a loop instead of find and replace with sed?How to change certain word in certain line in a fileHow to change a single line in multiple filesGrep global find replaceModify part of line if keyword is found using sed?How to search and replace strings matching a replacement list for multiple filesReplace multiple strings in a single passSearch and replace strings that are not substrings of other stringsReplace multiline string in filesReplace string in multiple files using find and sedBash-search and replace-Merge columns in CSV fileReplace the string as $testString to replace in shell upto certain context
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{
margin-bottom:0;
}
Replacing strings in files based on certain search criteria is a very common task. How can I
- replace string
foo
withbar
in all files in the current directory? - do the same recursively for sub directories?
- replace only if the file name matches another string?
- replace only if the string is found in a certain context?
- replace if the string is on a certain line number?
- replace multiple strings with the same replacement
- replace multiple strings with different replacements
text-processing awk sed perl
add a comment
|
Replacing strings in files based on certain search criteria is a very common task. How can I
- replace string
foo
withbar
in all files in the current directory? - do the same recursively for sub directories?
- replace only if the file name matches another string?
- replace only if the string is found in a certain context?
- replace if the string is on a certain line number?
- replace multiple strings with the same replacement
- replace multiple strings with different replacements
text-processing awk sed perl
2
This is intended to be a canonical Q&A on this subject (see this meta discussion), please feel free to edit my answer below or add your own.
– terdon♦
Feb 1 '14 at 17:08
add a comment
|
Replacing strings in files based on certain search criteria is a very common task. How can I
- replace string
foo
withbar
in all files in the current directory? - do the same recursively for sub directories?
- replace only if the file name matches another string?
- replace only if the string is found in a certain context?
- replace if the string is on a certain line number?
- replace multiple strings with the same replacement
- replace multiple strings with different replacements
text-processing awk sed perl
Replacing strings in files based on certain search criteria is a very common task. How can I
- replace string
foo
withbar
in all files in the current directory? - do the same recursively for sub directories?
- replace only if the file name matches another string?
- replace only if the string is found in a certain context?
- replace if the string is on a certain line number?
- replace multiple strings with the same replacement
- replace multiple strings with different replacements
text-processing awk sed perl
text-processing awk sed perl
edited Jan 21 '17 at 0:25
Braiam
24.9k20 gold badges83 silver badges149 bronze badges
24.9k20 gold badges83 silver badges149 bronze badges
asked Feb 1 '14 at 17:05
terdon♦terdon
143k35 gold badges296 silver badges473 bronze badges
143k35 gold badges296 silver badges473 bronze badges
2
This is intended to be a canonical Q&A on this subject (see this meta discussion), please feel free to edit my answer below or add your own.
– terdon♦
Feb 1 '14 at 17:08
add a comment
|
2
This is intended to be a canonical Q&A on this subject (see this meta discussion), please feel free to edit my answer below or add your own.
– terdon♦
Feb 1 '14 at 17:08
2
2
This is intended to be a canonical Q&A on this subject (see this meta discussion), please feel free to edit my answer below or add your own.
– terdon♦
Feb 1 '14 at 17:08
This is intended to be a canonical Q&A on this subject (see this meta discussion), please feel free to edit my answer below or add your own.
– terdon♦
Feb 1 '14 at 17:08
add a comment
|
8 Answers
8
active
oldest
votes
1. Replacing all occurrences of one string with another in all files in the current directory:
These are for cases where you know that the directory contains only regular files and that you want to process all non-hidden files. If that is not the case, use the approaches in 2.
All sed
solutions in this answer assume GNU sed
. If using FreeBSD or OS/X, replace -i
with -i ''
. Also note that the use of the -i
switch with any version of sed
has certain filesystem security implications and is inadvisable in any script which you plan to distribute in any way.
Non recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
(the
perl
one will fail for file names ending in|
or space)).
Recursive, regular files (including hidden ones) in this and all subdirectories
find . -type f -exec sed -i 's/foo/bar/g' {} +
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(may fail if the list is too big, see
zargs
to work around).
Bash can't check directly for regular files, a loop is needed (braces avoid setting the options globally):
( shopt -s globstar dotglob;
for file in **; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/foo/bar/g' "$file"
fi
done
)
The files are selected when they are actual files (-f) and they are writable (-w).
2. Replace only if the file name matches another string / has a specific extension / is of a certain type etc:
Non-recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
Recursive, regular files in this and all subdirectories
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
If you are using bash (braces avoid setting the options globally):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
The
--
serves to tellsed
that no more flags will be given in the command line. This is useful to protect against file names starting with-
.
If a file is of a certain type, for example, executable (see
man find
for more options):
find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Replace only if the string is found in a certain context
Replace
foo
withbar
only if there is abaz
later on the same line:
sed -i 's/foo(.*baz)/bar1/' file
In
sed
, using( )
saves whatever is in the parentheses and you can then access it with1
. There are many variations of this theme, to learn more about such regular expressions, see here.
Replace
foo
withbar
only iffoo
is found on the 3d column (field) of the input file (assuming whitespace-separated fields):
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(needs
gawk
4.1.0 or newer).
For a different field just use
$N
whereN
is the number of the field of interest. For a different field separator (:
in this example) use:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Another solution using
perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTE: both the
awk
andperl
solutions will affect spacing in the file (remove the leading and trailing blanks, and convert sequences of blanks to one space character in those lines that match). For a different field, use$F[N-1]
whereN
is the field number you want and for a different field separator use (the$"=":"
sets the output field separator to:
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
Replace
foo
withbar
only on the 4th line:
sed -i '4s/foo/bar/g' file
gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
perl -i -pe 's/foo/bar/g if $.==4' file
4. Multiple replace operations: replace with different strings
You can combine
sed
commands:
sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Be aware that order matters (
sed 's/foo/bar/g; s/bar/baz/g'
will substitutefoo
withbaz
).
or Perl commands
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
If you have a large number of patterns, it is easier to save your patterns and their replacements in a
sed
script file:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
Or, if you have too many pattern pairs for the above to be feasible, you can read pattern pairs from a file (two space separated patterns, $pattern and $replacement, per line):
while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
That will be quite slow for long lists of patterns and large data files so you might want to read the patterns and create a
sed
script from them instead. The following assumes a <space> delimiter separates a list of MATCH<space>REPLACE pairs occurring one-per-line in the filepatterns.txt
:
sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt |
sed -f- ./editfile >outfile
The above format is largely arbitrary and, for example, doesn't allow for a <space> in either of MATCH or REPLACE. The method is very general though: basically, if you can create an output stream which looks like a
sed
script, then you can source that stream as ased
script by specifyingsed
's script file as-
stdin.
You can combine and concatenate multiple scripts in similar fashion:
SOME_PIPELINE |
sed -e'#some expression script'
-f./script_file -f-
-e'#more inline expressions'
./actual_edit_file >./outfile
A POSIX
sed
will concatenate all scripts into one in the order they appear on the command-line. None of these need end in an
ewline.
grep
can work the same way:
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
When working with fixed-strings as patterns, it is good practice to escape regular expression metacharacters. You can do this rather easily:
sed 's/[]$&^*./[]/\&/g
s| *([^ ]*) *([^ ]*).*|s/1/2/g|
' <patterns.txt |
sed -f- ./editfile >outfile
5. Multiple replace operations: replace multiple patterns with the same string
Replace any of
foo
,bar
orbaz
withfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
or
perl -i -pe 's/foo|bar|baz/foobar/g' file
2
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone useszsh
. By all means addzsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create ased
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.
– terdon♦
Jan 16 '15 at 15:10
2
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.
– Stéphane Chazelas
Jan 16 '15 at 15:16
7
@terdon What does--
aftersed -i
and before the substitute command indicate?
– Geek
Sep 28 '15 at 11:29
5
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with-
. Using it ensures that the commands will work on files with names like-foo
. Without it, the-f
would be parsed as an option.
– terdon♦
Sep 28 '15 at 11:42
1
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.
– Pistos
Apr 19 '16 at 14:44
|
show 14 more comments
A good replacement Linux tool is rpl, that was originally written for the Debian project, so it is available with apt-get install rpl
in any Debian derived distro, and may be for others, but otherwise you can download the tar.gz
file in SourgeForge.
Simplest example of use:
$ rpl old_string new_string test.txt
Note that if the string contain spaces it should be enclosed in quotation marks. By default rpl
take care of capital letters but not of complete words, but you can change these defaults with options -i
(ignore case) and -w
(whole words). You can also specify multiple files:
$ rpl -i -w "old string" "new string" test.txt test2.txt
Or even specify the extensions (-x
) to search or even search recursively (-R
) in the directory:
$ rpl -x .html -x .txt -R old_string new_string test*
You can also search/replace in interactive mode with -p
(prompt) option:
The output show the numbers of files/string replaced and the type of search (case in/sensitive, whole/partial words), but it can be silent with the -q
(quiet mode) option, or even more verbose, listing line numbers that contain matches of each file and directory with -v
(verbose mode) option.
Other options that are worth remembering are -e
(honor escapes) that allow regular expressions
, so you can search also tabs (t
), new lines (n
),etc. Even you can use -f
to force permissions (of course, only when the user have write permissions) and -d
to preserve the modification times`).
Finally, if you are unsure of which will make exactly, use the -s
(simulate mode).
2
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
1
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
add a comment
|
How to do a search and replace over multiple files suggests:
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
- -e means execute the following line of code.
- -i means edit in-place
- -w write warnings
- -p loop over the input file, printing each line after the script is applied to it.
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' )
add a comment
|
You can use Vim in Ex mode:
replace string ALF with BRA in all files in the current directory?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
do the same recursively for sub directories?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
replace only if the file name matches another string?
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
replace only if the string is found in a certain context?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
replace if the string is on a certain line number?
ex -sc '2s/ALF/BRA/g' -cx file
replace multiple strings with the same replacement
ex -sc '%s/vALF|ECH/BRA/g' -cx file
replace multiple strings with different replacements
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
add a comment
|
I used this:
grep -r "old_string" -l | tr 'n' ' ' | xargs sed -i 's/old_string/new_string/g'
List all files that contain
old_string
.Replace newline in result with spaces (so that the list of files can be fed to
sed
.Run
sed
on those files to replace old string with new.
Update: The above result will fail on filenames that contain whitespaces. Instead, use:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
Note that this will fail if any of your file names contain spaces, tabs or newlines. Usegrep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.
– terdon♦
Oct 26 '15 at 17:07
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
add a comment
|
From a user's perspective, a nice & simple Unix tool that does the job perfectly is qsubst
. For example,
% qsubst foo bar *.c *.h
will replace foo
with bar
in all my C files. A nice feature is that qsubst
will do a query-replace, i.e., it will show me each occurrence of foo
and ask whether I want to replace it or not. [You can replace unconditionally (no asking) with -go
option, and there are other options, e.g., -w
if you only want to replace foo
when it is a whole word.]
How to get it: qsubst
was invented by der Mouse (from McGill) and posted to comp.unix.sources 11(7) in Aug. 1987. Updated versions exist. For example, the NetBSD version qsubst.c,v 1.8 2004/11/01
compiles and runs perfectly on my mac.
add a comment
|
I needed something that would provide a dry-run option and would work recursively with a glob, and after trying to do it with awk
and sed
I gave up and instead did it in python.
The script searches recursively all files matching a glob pattern (e.g. --glob="*.html"
) for a regex and replaces with the replacement regex:
find_replace.py [--dir=my_folder]
--search-regex=<search_regex>
--replace-regex=<replace_regex>
--glob=[glob_pattern]
--dry-run
Every long option such as --search-regex
has a corresponding short option, i.e. -s
. Run with -h
to see all options.
For example, this will flip all dates from 2017-12-31
to 31-12-2017
:
python replace.py --glob=myfile.txt
--search-regex="(d{4})-(d{2})-(d{2})"
--replace-regex="3-2-1"
--dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re
import argparse
def find_replace(cfg):
search_pattern = re.compile(cfg.search_regex)
if cfg.dry_run:
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
for filename in fnmatch.filter(files, cfg.glob):
if cfg.print_parent_folder:
pardir = os.path.normpath(os.path.join(path, '..'))
pardir = os.path.split(pardir)[-1]
print('[%s]' % pardir)
filepath = os.path.join(path, filename)
# backup original file
if cfg.create_backup:
backup_path = filepath + '.bak'
while os.path.exists(backup_path):
backup_path += '.bak'
print('DBG: creating backup', backup_path)
shutil.copyfile(filepath, backup_path)
with open(filepath) as f:
old_text = f.read()
all_matches = search_pattern.findall(old_text)
if all_matches:
print('Found {} matches in file {}'.format(len(all_matches), filename))
new_text = search_pattern.sub(cfg.replace_regex, old_text)
if not cfg.dry_run:
with open(filepath, "w") as f:
print('DBG: replacing in file', filepath)
f.write(new_text)
else:
for idx, matches in enumerate(all_matches):
print("Match #{}: {}".format(idx, matches))
print("NEW TEXT:n{}".format(new_text))
elif cfg.verbose:
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''DESCRIPTION:
Find and replace recursively from the given folder using regular expressions''',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''USAGE:
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
'''.format(os.path.basename(sys.argv[0])))
parser.add_argument('--dir', '-d',
help='folder to search in; by default current folder',
default='.')
parser.add_argument('--search-regex', '-s',
help='search regex',
required=True)
parser.add_argument('--replace-regex', '-r',
help='replacement regex',
required=True)
parser.add_argument('--glob', '-g',
help='glob pattern, i.e. *.html',
default="*.*")
parser.add_argument('--dry-run', '-dr',
action='store_true',
help="don't replace anything just show what is going to be done",
default=False)
parser.add_argument('--create-backup', '-b',
action='store_true',
help='Create backup files',
default=False)
parser.add_argument('--verbose', '-v',
action='store_true',
help="Show files which don't match the search regex",
default=False)
parser.add_argument('--print-parent-folder', '-p',
action='store_true',
help="Show the parent info for debug",
default=False)
config = parser.parse_args(sys.argv[1:])
find_replace(config)
Here
is an updated version of the script which highlights the search terms and replacements with different colors.
1
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)globstar
option and**
globs orfind
. For a dry run, just usesed
. Unless you use the-i
option, it won't make any changes. For a backup usesed -i.bak
(orperl -i .bak
); for files that don't match, usegrep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Whyscript.py --glob=foo*
instead of justscript.py foo*
?
– terdon♦
Nov 23 '17 at 9:34
1
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowingsed
andawk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).
– ccpizza
Nov 23 '17 at 12:59
add a comment
|
ripgrep (command name rg
) is a grep
tool, but supports search and replace as well.
$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky
$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky
rg
doesn't support in-place option, so you'll have to do it yourself
$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky
See Rust regex documentation for regular expression syntax and features. The -P
switch will enable PCRE2 flavor. rg
supports Unicode by default.
$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat
$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg 'p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)
$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.
$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|w+' -r '[$0]'
[car] bat [cod] map
Like grep
, the -F
option will allow fixed strings to be matched, a handy option which I feel sed
should implement too.
$ printf '2.3/[4]*6nfoon5.3-[4]*9n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29
Another handy option is -U
which enables multiline matching
$ # (?s) flag will allow . to match newline characters as well
$ printf '42nHi therenHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi Day
rg
can handle dos-style files too
$ # same as: sed -E 's/w+(r?)$/1231/'
$ printf 'hi thererngood dayrn' | rg --passthru --crlf 'w+$' -r '123'
hi 123
good 123
Another advantage of rg
is that it is likely to be faster than sed
$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real 0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real 0m0.007s
$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/bcatb/dog/g' big.txt > f1
real 0m0.060s
$ time rg --passthru 'bcatb' -r 'dog' big.txt > f2
real 0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical
$ time LC_ALL=C sed -E 's/b(w+)(s+1)+b/1/g' big.txt > f1
real 0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(w+)(s+1)+' -r '$1' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
add a comment
|
protected by Community♦ Jul 27 '15 at 21:05
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
8 Answers
8
active
oldest
votes
8 Answers
8
active
oldest
votes
active
oldest
votes
active
oldest
votes
1. Replacing all occurrences of one string with another in all files in the current directory:
These are for cases where you know that the directory contains only regular files and that you want to process all non-hidden files. If that is not the case, use the approaches in 2.
All sed
solutions in this answer assume GNU sed
. If using FreeBSD or OS/X, replace -i
with -i ''
. Also note that the use of the -i
switch with any version of sed
has certain filesystem security implications and is inadvisable in any script which you plan to distribute in any way.
Non recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
(the
perl
one will fail for file names ending in|
or space)).
Recursive, regular files (including hidden ones) in this and all subdirectories
find . -type f -exec sed -i 's/foo/bar/g' {} +
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(may fail if the list is too big, see
zargs
to work around).
Bash can't check directly for regular files, a loop is needed (braces avoid setting the options globally):
( shopt -s globstar dotglob;
for file in **; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/foo/bar/g' "$file"
fi
done
)
The files are selected when they are actual files (-f) and they are writable (-w).
2. Replace only if the file name matches another string / has a specific extension / is of a certain type etc:
Non-recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
Recursive, regular files in this and all subdirectories
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
If you are using bash (braces avoid setting the options globally):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
The
--
serves to tellsed
that no more flags will be given in the command line. This is useful to protect against file names starting with-
.
If a file is of a certain type, for example, executable (see
man find
for more options):
find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Replace only if the string is found in a certain context
Replace
foo
withbar
only if there is abaz
later on the same line:
sed -i 's/foo(.*baz)/bar1/' file
In
sed
, using( )
saves whatever is in the parentheses and you can then access it with1
. There are many variations of this theme, to learn more about such regular expressions, see here.
Replace
foo
withbar
only iffoo
is found on the 3d column (field) of the input file (assuming whitespace-separated fields):
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(needs
gawk
4.1.0 or newer).
For a different field just use
$N
whereN
is the number of the field of interest. For a different field separator (:
in this example) use:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Another solution using
perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTE: both the
awk
andperl
solutions will affect spacing in the file (remove the leading and trailing blanks, and convert sequences of blanks to one space character in those lines that match). For a different field, use$F[N-1]
whereN
is the field number you want and for a different field separator use (the$"=":"
sets the output field separator to:
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
Replace
foo
withbar
only on the 4th line:
sed -i '4s/foo/bar/g' file
gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
perl -i -pe 's/foo/bar/g if $.==4' file
4. Multiple replace operations: replace with different strings
You can combine
sed
commands:
sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Be aware that order matters (
sed 's/foo/bar/g; s/bar/baz/g'
will substitutefoo
withbaz
).
or Perl commands
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
If you have a large number of patterns, it is easier to save your patterns and their replacements in a
sed
script file:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
Or, if you have too many pattern pairs for the above to be feasible, you can read pattern pairs from a file (two space separated patterns, $pattern and $replacement, per line):
while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
That will be quite slow for long lists of patterns and large data files so you might want to read the patterns and create a
sed
script from them instead. The following assumes a <space> delimiter separates a list of MATCH<space>REPLACE pairs occurring one-per-line in the filepatterns.txt
:
sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt |
sed -f- ./editfile >outfile
The above format is largely arbitrary and, for example, doesn't allow for a <space> in either of MATCH or REPLACE. The method is very general though: basically, if you can create an output stream which looks like a
sed
script, then you can source that stream as ased
script by specifyingsed
's script file as-
stdin.
You can combine and concatenate multiple scripts in similar fashion:
SOME_PIPELINE |
sed -e'#some expression script'
-f./script_file -f-
-e'#more inline expressions'
./actual_edit_file >./outfile
A POSIX
sed
will concatenate all scripts into one in the order they appear on the command-line. None of these need end in an
ewline.
grep
can work the same way:
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
When working with fixed-strings as patterns, it is good practice to escape regular expression metacharacters. You can do this rather easily:
sed 's/[]$&^*./[]/\&/g
s| *([^ ]*) *([^ ]*).*|s/1/2/g|
' <patterns.txt |
sed -f- ./editfile >outfile
5. Multiple replace operations: replace multiple patterns with the same string
Replace any of
foo
,bar
orbaz
withfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
or
perl -i -pe 's/foo|bar|baz/foobar/g' file
2
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone useszsh
. By all means addzsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create ased
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.
– terdon♦
Jan 16 '15 at 15:10
2
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.
– Stéphane Chazelas
Jan 16 '15 at 15:16
7
@terdon What does--
aftersed -i
and before the substitute command indicate?
– Geek
Sep 28 '15 at 11:29
5
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with-
. Using it ensures that the commands will work on files with names like-foo
. Without it, the-f
would be parsed as an option.
– terdon♦
Sep 28 '15 at 11:42
1
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.
– Pistos
Apr 19 '16 at 14:44
|
show 14 more comments
1. Replacing all occurrences of one string with another in all files in the current directory:
These are for cases where you know that the directory contains only regular files and that you want to process all non-hidden files. If that is not the case, use the approaches in 2.
All sed
solutions in this answer assume GNU sed
. If using FreeBSD or OS/X, replace -i
with -i ''
. Also note that the use of the -i
switch with any version of sed
has certain filesystem security implications and is inadvisable in any script which you plan to distribute in any way.
Non recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
(the
perl
one will fail for file names ending in|
or space)).
Recursive, regular files (including hidden ones) in this and all subdirectories
find . -type f -exec sed -i 's/foo/bar/g' {} +
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(may fail if the list is too big, see
zargs
to work around).
Bash can't check directly for regular files, a loop is needed (braces avoid setting the options globally):
( shopt -s globstar dotglob;
for file in **; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/foo/bar/g' "$file"
fi
done
)
The files are selected when they are actual files (-f) and they are writable (-w).
2. Replace only if the file name matches another string / has a specific extension / is of a certain type etc:
Non-recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
Recursive, regular files in this and all subdirectories
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
If you are using bash (braces avoid setting the options globally):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
The
--
serves to tellsed
that no more flags will be given in the command line. This is useful to protect against file names starting with-
.
If a file is of a certain type, for example, executable (see
man find
for more options):
find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Replace only if the string is found in a certain context
Replace
foo
withbar
only if there is abaz
later on the same line:
sed -i 's/foo(.*baz)/bar1/' file
In
sed
, using( )
saves whatever is in the parentheses and you can then access it with1
. There are many variations of this theme, to learn more about such regular expressions, see here.
Replace
foo
withbar
only iffoo
is found on the 3d column (field) of the input file (assuming whitespace-separated fields):
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(needs
gawk
4.1.0 or newer).
For a different field just use
$N
whereN
is the number of the field of interest. For a different field separator (:
in this example) use:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Another solution using
perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTE: both the
awk
andperl
solutions will affect spacing in the file (remove the leading and trailing blanks, and convert sequences of blanks to one space character in those lines that match). For a different field, use$F[N-1]
whereN
is the field number you want and for a different field separator use (the$"=":"
sets the output field separator to:
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
Replace
foo
withbar
only on the 4th line:
sed -i '4s/foo/bar/g' file
gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
perl -i -pe 's/foo/bar/g if $.==4' file
4. Multiple replace operations: replace with different strings
You can combine
sed
commands:
sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Be aware that order matters (
sed 's/foo/bar/g; s/bar/baz/g'
will substitutefoo
withbaz
).
or Perl commands
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
If you have a large number of patterns, it is easier to save your patterns and their replacements in a
sed
script file:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
Or, if you have too many pattern pairs for the above to be feasible, you can read pattern pairs from a file (two space separated patterns, $pattern and $replacement, per line):
while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
That will be quite slow for long lists of patterns and large data files so you might want to read the patterns and create a
sed
script from them instead. The following assumes a <space> delimiter separates a list of MATCH<space>REPLACE pairs occurring one-per-line in the filepatterns.txt
:
sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt |
sed -f- ./editfile >outfile
The above format is largely arbitrary and, for example, doesn't allow for a <space> in either of MATCH or REPLACE. The method is very general though: basically, if you can create an output stream which looks like a
sed
script, then you can source that stream as ased
script by specifyingsed
's script file as-
stdin.
You can combine and concatenate multiple scripts in similar fashion:
SOME_PIPELINE |
sed -e'#some expression script'
-f./script_file -f-
-e'#more inline expressions'
./actual_edit_file >./outfile
A POSIX
sed
will concatenate all scripts into one in the order they appear on the command-line. None of these need end in an
ewline.
grep
can work the same way:
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
When working with fixed-strings as patterns, it is good practice to escape regular expression metacharacters. You can do this rather easily:
sed 's/[]$&^*./[]/\&/g
s| *([^ ]*) *([^ ]*).*|s/1/2/g|
' <patterns.txt |
sed -f- ./editfile >outfile
5. Multiple replace operations: replace multiple patterns with the same string
Replace any of
foo
,bar
orbaz
withfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
or
perl -i -pe 's/foo|bar|baz/foobar/g' file
2
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone useszsh
. By all means addzsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create ased
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.
– terdon♦
Jan 16 '15 at 15:10
2
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.
– Stéphane Chazelas
Jan 16 '15 at 15:16
7
@terdon What does--
aftersed -i
and before the substitute command indicate?
– Geek
Sep 28 '15 at 11:29
5
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with-
. Using it ensures that the commands will work on files with names like-foo
. Without it, the-f
would be parsed as an option.
– terdon♦
Sep 28 '15 at 11:42
1
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.
– Pistos
Apr 19 '16 at 14:44
|
show 14 more comments
1. Replacing all occurrences of one string with another in all files in the current directory:
These are for cases where you know that the directory contains only regular files and that you want to process all non-hidden files. If that is not the case, use the approaches in 2.
All sed
solutions in this answer assume GNU sed
. If using FreeBSD or OS/X, replace -i
with -i ''
. Also note that the use of the -i
switch with any version of sed
has certain filesystem security implications and is inadvisable in any script which you plan to distribute in any way.
Non recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
(the
perl
one will fail for file names ending in|
or space)).
Recursive, regular files (including hidden ones) in this and all subdirectories
find . -type f -exec sed -i 's/foo/bar/g' {} +
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(may fail if the list is too big, see
zargs
to work around).
Bash can't check directly for regular files, a loop is needed (braces avoid setting the options globally):
( shopt -s globstar dotglob;
for file in **; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/foo/bar/g' "$file"
fi
done
)
The files are selected when they are actual files (-f) and they are writable (-w).
2. Replace only if the file name matches another string / has a specific extension / is of a certain type etc:
Non-recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
Recursive, regular files in this and all subdirectories
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
If you are using bash (braces avoid setting the options globally):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
The
--
serves to tellsed
that no more flags will be given in the command line. This is useful to protect against file names starting with-
.
If a file is of a certain type, for example, executable (see
man find
for more options):
find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Replace only if the string is found in a certain context
Replace
foo
withbar
only if there is abaz
later on the same line:
sed -i 's/foo(.*baz)/bar1/' file
In
sed
, using( )
saves whatever is in the parentheses and you can then access it with1
. There are many variations of this theme, to learn more about such regular expressions, see here.
Replace
foo
withbar
only iffoo
is found on the 3d column (field) of the input file (assuming whitespace-separated fields):
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(needs
gawk
4.1.0 or newer).
For a different field just use
$N
whereN
is the number of the field of interest. For a different field separator (:
in this example) use:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Another solution using
perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTE: both the
awk
andperl
solutions will affect spacing in the file (remove the leading and trailing blanks, and convert sequences of blanks to one space character in those lines that match). For a different field, use$F[N-1]
whereN
is the field number you want and for a different field separator use (the$"=":"
sets the output field separator to:
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
Replace
foo
withbar
only on the 4th line:
sed -i '4s/foo/bar/g' file
gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
perl -i -pe 's/foo/bar/g if $.==4' file
4. Multiple replace operations: replace with different strings
You can combine
sed
commands:
sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Be aware that order matters (
sed 's/foo/bar/g; s/bar/baz/g'
will substitutefoo
withbaz
).
or Perl commands
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
If you have a large number of patterns, it is easier to save your patterns and their replacements in a
sed
script file:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
Or, if you have too many pattern pairs for the above to be feasible, you can read pattern pairs from a file (two space separated patterns, $pattern and $replacement, per line):
while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
That will be quite slow for long lists of patterns and large data files so you might want to read the patterns and create a
sed
script from them instead. The following assumes a <space> delimiter separates a list of MATCH<space>REPLACE pairs occurring one-per-line in the filepatterns.txt
:
sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt |
sed -f- ./editfile >outfile
The above format is largely arbitrary and, for example, doesn't allow for a <space> in either of MATCH or REPLACE. The method is very general though: basically, if you can create an output stream which looks like a
sed
script, then you can source that stream as ased
script by specifyingsed
's script file as-
stdin.
You can combine and concatenate multiple scripts in similar fashion:
SOME_PIPELINE |
sed -e'#some expression script'
-f./script_file -f-
-e'#more inline expressions'
./actual_edit_file >./outfile
A POSIX
sed
will concatenate all scripts into one in the order they appear on the command-line. None of these need end in an
ewline.
grep
can work the same way:
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
When working with fixed-strings as patterns, it is good practice to escape regular expression metacharacters. You can do this rather easily:
sed 's/[]$&^*./[]/\&/g
s| *([^ ]*) *([^ ]*).*|s/1/2/g|
' <patterns.txt |
sed -f- ./editfile >outfile
5. Multiple replace operations: replace multiple patterns with the same string
Replace any of
foo
,bar
orbaz
withfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
or
perl -i -pe 's/foo|bar|baz/foobar/g' file
1. Replacing all occurrences of one string with another in all files in the current directory:
These are for cases where you know that the directory contains only regular files and that you want to process all non-hidden files. If that is not the case, use the approaches in 2.
All sed
solutions in this answer assume GNU sed
. If using FreeBSD or OS/X, replace -i
with -i ''
. Also note that the use of the -i
switch with any version of sed
has certain filesystem security implications and is inadvisable in any script which you plan to distribute in any way.
Non recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *
perl -i -pe 's/foo/bar/g' ./*
(the
perl
one will fail for file names ending in|
or space)).
Recursive, regular files (including hidden ones) in this and all subdirectories
find . -type f -exec sed -i 's/foo/bar/g' {} +
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(may fail if the list is too big, see
zargs
to work around).
Bash can't check directly for regular files, a loop is needed (braces avoid setting the options globally):
( shopt -s globstar dotglob;
for file in **; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/foo/bar/g' "$file"
fi
done
)
The files are selected when they are actual files (-f) and they are writable (-w).
2. Replace only if the file name matches another string / has a specific extension / is of a certain type etc:
Non-recursive, files in this directory only:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz
sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
Recursive, regular files in this and all subdirectories
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
If you are using bash (braces avoid setting the options globally):
( shopt -s globstar dotglob
sed -i -- 's/foo/bar/g' **baz*
sed -i -- 's/foo/bar/g' **.baz
)
If you are using zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.)
sed -i -- 's/foo/bar/g' **/*.baz(D.)
The
--
serves to tellsed
that no more flags will be given in the command line. This is useful to protect against file names starting with-
.
If a file is of a certain type, for example, executable (see
man find
for more options):
find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Replace only if the string is found in a certain context
Replace
foo
withbar
only if there is abaz
later on the same line:
sed -i 's/foo(.*baz)/bar1/' file
In
sed
, using( )
saves whatever is in the parentheses and you can then access it with1
. There are many variations of this theme, to learn more about such regular expressions, see here.
Replace
foo
withbar
only iffoo
is found on the 3d column (field) of the input file (assuming whitespace-separated fields):
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(needs
gawk
4.1.0 or newer).
For a different field just use
$N
whereN
is the number of the field of interest. For a different field separator (:
in this example) use:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Another solution using
perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTE: both the
awk
andperl
solutions will affect spacing in the file (remove the leading and trailing blanks, and convert sequences of blanks to one space character in those lines that match). For a different field, use$F[N-1]
whereN
is the field number you want and for a different field separator use (the$"=":"
sets the output field separator to:
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
Replace
foo
withbar
only on the 4th line:
sed -i '4s/foo/bar/g' file
gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
perl -i -pe 's/foo/bar/g if $.==4' file
4. Multiple replace operations: replace with different strings
You can combine
sed
commands:
sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Be aware that order matters (
sed 's/foo/bar/g; s/bar/baz/g'
will substitutefoo
withbaz
).
or Perl commands
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
If you have a large number of patterns, it is easier to save your patterns and their replacements in a
sed
script file:
#! /usr/bin/sed -f
s/foo/bar/g
s/baz/zab/g
Or, if you have too many pattern pairs for the above to be feasible, you can read pattern pairs from a file (two space separated patterns, $pattern and $replacement, per line):
while read -r pattern replacement; do
sed -i "s/$pattern/$replacement/" file
done < patterns.txt
That will be quite slow for long lists of patterns and large data files so you might want to read the patterns and create a
sed
script from them instead. The following assumes a <space> delimiter separates a list of MATCH<space>REPLACE pairs occurring one-per-line in the filepatterns.txt
:
sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt |
sed -f- ./editfile >outfile
The above format is largely arbitrary and, for example, doesn't allow for a <space> in either of MATCH or REPLACE. The method is very general though: basically, if you can create an output stream which looks like a
sed
script, then you can source that stream as ased
script by specifyingsed
's script file as-
stdin.
You can combine and concatenate multiple scripts in similar fashion:
SOME_PIPELINE |
sed -e'#some expression script'
-f./script_file -f-
-e'#more inline expressions'
./actual_edit_file >./outfile
A POSIX
sed
will concatenate all scripts into one in the order they appear on the command-line. None of these need end in an
ewline.
grep
can work the same way:
sed -e'#generate a pattern list' <in |
grep -f- ./grepped_file
When working with fixed-strings as patterns, it is good practice to escape regular expression metacharacters. You can do this rather easily:
sed 's/[]$&^*./[]/\&/g
s| *([^ ]*) *([^ ]*).*|s/1/2/g|
' <patterns.txt |
sed -f- ./editfile >outfile
5. Multiple replace operations: replace multiple patterns with the same string
Replace any of
foo
,bar
orbaz
withfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
or
perl -i -pe 's/foo|bar|baz/foobar/g' file
edited Apr 13 '17 at 12:37
community wiki
31 revs, 9 users 58%
terdon
2
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone useszsh
. By all means addzsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create ased
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.
– terdon♦
Jan 16 '15 at 15:10
2
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.
– Stéphane Chazelas
Jan 16 '15 at 15:16
7
@terdon What does--
aftersed -i
and before the substitute command indicate?
– Geek
Sep 28 '15 at 11:29
5
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with-
. Using it ensures that the commands will work on files with names like-foo
. Without it, the-f
would be parsed as an option.
– terdon♦
Sep 28 '15 at 11:42
1
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.
– Pistos
Apr 19 '16 at 14:44
|
show 14 more comments
2
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone useszsh
. By all means addzsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create ased
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.
– terdon♦
Jan 16 '15 at 15:10
2
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.
– Stéphane Chazelas
Jan 16 '15 at 15:16
7
@terdon What does--
aftersed -i
and before the substitute command indicate?
– Geek
Sep 28 '15 at 11:29
5
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with-
. Using it ensures that the commands will work on files with names like-foo
. Without it, the-f
would be parsed as an option.
– terdon♦
Sep 28 '15 at 11:42
1
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.
– Pistos
Apr 19 '16 at 14:44
2
2
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone uses
zsh
. By all means add zsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create a sed
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.– terdon♦
Jan 16 '15 at 15:10
@StéphaneChazelas thanks for the edit, it did indeed fix several things. However, please don't remove information that is relevant to bash. Not everyone uses
zsh
. By all means add zsh
info but there is no reason to remove the bash stuff. Also, I know that using the shell for text processing is not ideal but there are cases where it is needed. I edited in a better version of my original script that will create a sed
script instead of actually using the shell loop to parse. This can be useful if you have several hundred pairs of patterns for example.– terdon♦
Jan 16 '15 at 15:10
2
2
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the
(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.– Stéphane Chazelas
Jan 16 '15 at 15:16
@terdon, your bash one is incorrect. bash before 4.3 will follow symlinks when descending. Also bash has no equivalent for the
(.)
globbing qualifier so can't be used here. (you're missing some -- as well). The for loop is incorrect (missing -r) and means making several passes in the files and adds no benefit over a sed script.– Stéphane Chazelas
Jan 16 '15 at 15:16
7
7
@terdon What does
--
after sed -i
and before the substitute command indicate?– Geek
Sep 28 '15 at 11:29
@terdon What does
--
after sed -i
and before the substitute command indicate?– Geek
Sep 28 '15 at 11:29
5
5
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with
-
. Using it ensures that the commands will work on files with names like -foo
. Without it, the -f
would be parsed as an option.– terdon♦
Sep 28 '15 at 11:42
@Geek that's a POSIX thing. It signifies the end of options and lets you pass arguments starting with
-
. Using it ensures that the commands will work on files with names like -foo
. Without it, the -f
would be parsed as an option.– terdon♦
Sep 28 '15 at 11:42
1
1
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a
.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.– Pistos
Apr 19 '16 at 14:44
Be very careful executing some of the recursive commands in git repositories. For example, the solutions provided in section 1 of this answer will actually modify internal git files in a
.git
directory, and actually mess up your checkout. Better to operate within/on specific directories by name.– Pistos
Apr 19 '16 at 14:44
|
show 14 more comments
A good replacement Linux tool is rpl, that was originally written for the Debian project, so it is available with apt-get install rpl
in any Debian derived distro, and may be for others, but otherwise you can download the tar.gz
file in SourgeForge.
Simplest example of use:
$ rpl old_string new_string test.txt
Note that if the string contain spaces it should be enclosed in quotation marks. By default rpl
take care of capital letters but not of complete words, but you can change these defaults with options -i
(ignore case) and -w
(whole words). You can also specify multiple files:
$ rpl -i -w "old string" "new string" test.txt test2.txt
Or even specify the extensions (-x
) to search or even search recursively (-R
) in the directory:
$ rpl -x .html -x .txt -R old_string new_string test*
You can also search/replace in interactive mode with -p
(prompt) option:
The output show the numbers of files/string replaced and the type of search (case in/sensitive, whole/partial words), but it can be silent with the -q
(quiet mode) option, or even more verbose, listing line numbers that contain matches of each file and directory with -v
(verbose mode) option.
Other options that are worth remembering are -e
(honor escapes) that allow regular expressions
, so you can search also tabs (t
), new lines (n
),etc. Even you can use -f
to force permissions (of course, only when the user have write permissions) and -d
to preserve the modification times`).
Finally, if you are unsure of which will make exactly, use the -s
(simulate mode).
2
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
1
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
add a comment
|
A good replacement Linux tool is rpl, that was originally written for the Debian project, so it is available with apt-get install rpl
in any Debian derived distro, and may be for others, but otherwise you can download the tar.gz
file in SourgeForge.
Simplest example of use:
$ rpl old_string new_string test.txt
Note that if the string contain spaces it should be enclosed in quotation marks. By default rpl
take care of capital letters but not of complete words, but you can change these defaults with options -i
(ignore case) and -w
(whole words). You can also specify multiple files:
$ rpl -i -w "old string" "new string" test.txt test2.txt
Or even specify the extensions (-x
) to search or even search recursively (-R
) in the directory:
$ rpl -x .html -x .txt -R old_string new_string test*
You can also search/replace in interactive mode with -p
(prompt) option:
The output show the numbers of files/string replaced and the type of search (case in/sensitive, whole/partial words), but it can be silent with the -q
(quiet mode) option, or even more verbose, listing line numbers that contain matches of each file and directory with -v
(verbose mode) option.
Other options that are worth remembering are -e
(honor escapes) that allow regular expressions
, so you can search also tabs (t
), new lines (n
),etc. Even you can use -f
to force permissions (of course, only when the user have write permissions) and -d
to preserve the modification times`).
Finally, if you are unsure of which will make exactly, use the -s
(simulate mode).
2
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
1
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
add a comment
|
A good replacement Linux tool is rpl, that was originally written for the Debian project, so it is available with apt-get install rpl
in any Debian derived distro, and may be for others, but otherwise you can download the tar.gz
file in SourgeForge.
Simplest example of use:
$ rpl old_string new_string test.txt
Note that if the string contain spaces it should be enclosed in quotation marks. By default rpl
take care of capital letters but not of complete words, but you can change these defaults with options -i
(ignore case) and -w
(whole words). You can also specify multiple files:
$ rpl -i -w "old string" "new string" test.txt test2.txt
Or even specify the extensions (-x
) to search or even search recursively (-R
) in the directory:
$ rpl -x .html -x .txt -R old_string new_string test*
You can also search/replace in interactive mode with -p
(prompt) option:
The output show the numbers of files/string replaced and the type of search (case in/sensitive, whole/partial words), but it can be silent with the -q
(quiet mode) option, or even more verbose, listing line numbers that contain matches of each file and directory with -v
(verbose mode) option.
Other options that are worth remembering are -e
(honor escapes) that allow regular expressions
, so you can search also tabs (t
), new lines (n
),etc. Even you can use -f
to force permissions (of course, only when the user have write permissions) and -d
to preserve the modification times`).
Finally, if you are unsure of which will make exactly, use the -s
(simulate mode).
A good replacement Linux tool is rpl, that was originally written for the Debian project, so it is available with apt-get install rpl
in any Debian derived distro, and may be for others, but otherwise you can download the tar.gz
file in SourgeForge.
Simplest example of use:
$ rpl old_string new_string test.txt
Note that if the string contain spaces it should be enclosed in quotation marks. By default rpl
take care of capital letters but not of complete words, but you can change these defaults with options -i
(ignore case) and -w
(whole words). You can also specify multiple files:
$ rpl -i -w "old string" "new string" test.txt test2.txt
Or even specify the extensions (-x
) to search or even search recursively (-R
) in the directory:
$ rpl -x .html -x .txt -R old_string new_string test*
You can also search/replace in interactive mode with -p
(prompt) option:
The output show the numbers of files/string replaced and the type of search (case in/sensitive, whole/partial words), but it can be silent with the -q
(quiet mode) option, or even more verbose, listing line numbers that contain matches of each file and directory with -v
(verbose mode) option.
Other options that are worth remembering are -e
(honor escapes) that allow regular expressions
, so you can search also tabs (t
), new lines (n
),etc. Even you can use -f
to force permissions (of course, only when the user have write permissions) and -d
to preserve the modification times`).
Finally, if you are unsure of which will make exactly, use the -s
(simulate mode).
edited Jan 5 '16 at 20:01
answered Dec 27 '15 at 8:06
FranFran
1,21111 silver badges8 bronze badges
1,21111 silver badges8 bronze badges
2
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
1
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
add a comment
|
2
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
1
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
2
2
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
So much better at the feedback and simplicity than sed. I just wish it allowed acting on file names, and then it'd be perfect as-is.
– Kzqai
Dec 23 '16 at 17:12
1
1
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
i like the -s (simulate mode) :-)
– erm3nda
Jun 10 '18 at 11:08
add a comment
|
How to do a search and replace over multiple files suggests:
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
- -e means execute the following line of code.
- -i means edit in-place
- -w write warnings
- -p loop over the input file, printing each line after the script is applied to it.
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' )
add a comment
|
How to do a search and replace over multiple files suggests:
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
- -e means execute the following line of code.
- -i means edit in-place
- -w write warnings
- -p loop over the input file, printing each line after the script is applied to it.
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' )
add a comment
|
How to do a search and replace over multiple files suggests:
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
- -e means execute the following line of code.
- -i means edit in-place
- -w write warnings
- -p loop over the input file, printing each line after the script is applied to it.
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' )
How to do a search and replace over multiple files suggests:
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
- -e means execute the following line of code.
- -i means edit in-place
- -w write warnings
- -p loop over the input file, printing each line after the script is applied to it.
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' )
edited Oct 19 '15 at 21:38
Community♦
1
1
answered Jan 16 '15 at 14:02
Alejandro Salamanca MazueloAlejandro Salamanca Mazuelo
3784 silver badges8 bronze badges
3784 silver badges8 bronze badges
add a comment
|
add a comment
|
You can use Vim in Ex mode:
replace string ALF with BRA in all files in the current directory?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
do the same recursively for sub directories?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
replace only if the file name matches another string?
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
replace only if the string is found in a certain context?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
replace if the string is on a certain line number?
ex -sc '2s/ALF/BRA/g' -cx file
replace multiple strings with the same replacement
ex -sc '%s/vALF|ECH/BRA/g' -cx file
replace multiple strings with different replacements
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
add a comment
|
You can use Vim in Ex mode:
replace string ALF with BRA in all files in the current directory?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
do the same recursively for sub directories?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
replace only if the file name matches another string?
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
replace only if the string is found in a certain context?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
replace if the string is on a certain line number?
ex -sc '2s/ALF/BRA/g' -cx file
replace multiple strings with the same replacement
ex -sc '%s/vALF|ECH/BRA/g' -cx file
replace multiple strings with different replacements
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
add a comment
|
You can use Vim in Ex mode:
replace string ALF with BRA in all files in the current directory?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
do the same recursively for sub directories?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
replace only if the file name matches another string?
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
replace only if the string is found in a certain context?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
replace if the string is on a certain line number?
ex -sc '2s/ALF/BRA/g' -cx file
replace multiple strings with the same replacement
ex -sc '%s/vALF|ECH/BRA/g' -cx file
replace multiple strings with different replacements
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
You can use Vim in Ex mode:
replace string ALF with BRA in all files in the current directory?
for CHA in *
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
do the same recursively for sub directories?
find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'
replace only if the file name matches another string?
for CHA in *.txt
do
ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done
replace only if the string is found in a certain context?
ex -sc 'g/DEL/s/ALF/BRA/g' -cx file
replace if the string is on a certain line number?
ex -sc '2s/ALF/BRA/g' -cx file
replace multiple strings with the same replacement
ex -sc '%s/vALF|ECH/BRA/g' -cx file
replace multiple strings with different replacements
ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
edited Apr 17 '16 at 5:31
answered Apr 17 '16 at 1:47
Steven PennySteven Penny
1
1
add a comment
|
add a comment
|
I used this:
grep -r "old_string" -l | tr 'n' ' ' | xargs sed -i 's/old_string/new_string/g'
List all files that contain
old_string
.Replace newline in result with spaces (so that the list of files can be fed to
sed
.Run
sed
on those files to replace old string with new.
Update: The above result will fail on filenames that contain whitespaces. Instead, use:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
Note that this will fail if any of your file names contain spaces, tabs or newlines. Usegrep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.
– terdon♦
Oct 26 '15 at 17:07
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
add a comment
|
I used this:
grep -r "old_string" -l | tr 'n' ' ' | xargs sed -i 's/old_string/new_string/g'
List all files that contain
old_string
.Replace newline in result with spaces (so that the list of files can be fed to
sed
.Run
sed
on those files to replace old string with new.
Update: The above result will fail on filenames that contain whitespaces. Instead, use:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
Note that this will fail if any of your file names contain spaces, tabs or newlines. Usegrep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.
– terdon♦
Oct 26 '15 at 17:07
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
add a comment
|
I used this:
grep -r "old_string" -l | tr 'n' ' ' | xargs sed -i 's/old_string/new_string/g'
List all files that contain
old_string
.Replace newline in result with spaces (so that the list of files can be fed to
sed
.Run
sed
on those files to replace old string with new.
Update: The above result will fail on filenames that contain whitespaces. Instead, use:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
I used this:
grep -r "old_string" -l | tr 'n' ' ' | xargs sed -i 's/old_string/new_string/g'
List all files that contain
old_string
.Replace newline in result with spaces (so that the list of files can be fed to
sed
.Run
sed
on those files to replace old string with new.
Update: The above result will fail on filenames that contain whitespaces. Instead, use:
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
edited May 9 '16 at 7:34
shas
7383 gold badges10 silver badges27 bronze badges
7383 gold badges10 silver badges27 bronze badges
answered Oct 26 '15 at 16:58
o_o_o--o_o_o--
4201 gold badge4 silver badges9 bronze badges
4201 gold badge4 silver badges9 bronze badges
Note that this will fail if any of your file names contain spaces, tabs or newlines. Usegrep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.
– terdon♦
Oct 26 '15 at 17:07
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
add a comment
|
Note that this will fail if any of your file names contain spaces, tabs or newlines. Usegrep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.
– terdon♦
Oct 26 '15 at 17:07
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
Note that this will fail if any of your file names contain spaces, tabs or newlines. Use
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.– terdon♦
Oct 26 '15 at 17:07
Note that this will fail if any of your file names contain spaces, tabs or newlines. Use
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
will make it deal with arbitrary file names.– terdon♦
Oct 26 '15 at 17:07
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
thanks guys. added update and left the old code cause it's an interesting caveat that could be useful to someone unaware of this behavior.
– o_o_o--
Oct 26 '15 at 20:59
add a comment
|
From a user's perspective, a nice & simple Unix tool that does the job perfectly is qsubst
. For example,
% qsubst foo bar *.c *.h
will replace foo
with bar
in all my C files. A nice feature is that qsubst
will do a query-replace, i.e., it will show me each occurrence of foo
and ask whether I want to replace it or not. [You can replace unconditionally (no asking) with -go
option, and there are other options, e.g., -w
if you only want to replace foo
when it is a whole word.]
How to get it: qsubst
was invented by der Mouse (from McGill) and posted to comp.unix.sources 11(7) in Aug. 1987. Updated versions exist. For example, the NetBSD version qsubst.c,v 1.8 2004/11/01
compiles and runs perfectly on my mac.
add a comment
|
From a user's perspective, a nice & simple Unix tool that does the job perfectly is qsubst
. For example,
% qsubst foo bar *.c *.h
will replace foo
with bar
in all my C files. A nice feature is that qsubst
will do a query-replace, i.e., it will show me each occurrence of foo
and ask whether I want to replace it or not. [You can replace unconditionally (no asking) with -go
option, and there are other options, e.g., -w
if you only want to replace foo
when it is a whole word.]
How to get it: qsubst
was invented by der Mouse (from McGill) and posted to comp.unix.sources 11(7) in Aug. 1987. Updated versions exist. For example, the NetBSD version qsubst.c,v 1.8 2004/11/01
compiles and runs perfectly on my mac.
add a comment
|
From a user's perspective, a nice & simple Unix tool that does the job perfectly is qsubst
. For example,
% qsubst foo bar *.c *.h
will replace foo
with bar
in all my C files. A nice feature is that qsubst
will do a query-replace, i.e., it will show me each occurrence of foo
and ask whether I want to replace it or not. [You can replace unconditionally (no asking) with -go
option, and there are other options, e.g., -w
if you only want to replace foo
when it is a whole word.]
How to get it: qsubst
was invented by der Mouse (from McGill) and posted to comp.unix.sources 11(7) in Aug. 1987. Updated versions exist. For example, the NetBSD version qsubst.c,v 1.8 2004/11/01
compiles and runs perfectly on my mac.
From a user's perspective, a nice & simple Unix tool that does the job perfectly is qsubst
. For example,
% qsubst foo bar *.c *.h
will replace foo
with bar
in all my C files. A nice feature is that qsubst
will do a query-replace, i.e., it will show me each occurrence of foo
and ask whether I want to replace it or not. [You can replace unconditionally (no asking) with -go
option, and there are other options, e.g., -w
if you only want to replace foo
when it is a whole word.]
How to get it: qsubst
was invented by der Mouse (from McGill) and posted to comp.unix.sources 11(7) in Aug. 1987. Updated versions exist. For example, the NetBSD version qsubst.c,v 1.8 2004/11/01
compiles and runs perfectly on my mac.
edited Jul 30 '15 at 11:27
terdon♦
143k35 gold badges296 silver badges473 bronze badges
143k35 gold badges296 silver badges473 bronze badges
answered Jul 30 '15 at 11:25
phsphs
2832 silver badges9 bronze badges
2832 silver badges9 bronze badges
add a comment
|
add a comment
|
I needed something that would provide a dry-run option and would work recursively with a glob, and after trying to do it with awk
and sed
I gave up and instead did it in python.
The script searches recursively all files matching a glob pattern (e.g. --glob="*.html"
) for a regex and replaces with the replacement regex:
find_replace.py [--dir=my_folder]
--search-regex=<search_regex>
--replace-regex=<replace_regex>
--glob=[glob_pattern]
--dry-run
Every long option such as --search-regex
has a corresponding short option, i.e. -s
. Run with -h
to see all options.
For example, this will flip all dates from 2017-12-31
to 31-12-2017
:
python replace.py --glob=myfile.txt
--search-regex="(d{4})-(d{2})-(d{2})"
--replace-regex="3-2-1"
--dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re
import argparse
def find_replace(cfg):
search_pattern = re.compile(cfg.search_regex)
if cfg.dry_run:
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
for filename in fnmatch.filter(files, cfg.glob):
if cfg.print_parent_folder:
pardir = os.path.normpath(os.path.join(path, '..'))
pardir = os.path.split(pardir)[-1]
print('[%s]' % pardir)
filepath = os.path.join(path, filename)
# backup original file
if cfg.create_backup:
backup_path = filepath + '.bak'
while os.path.exists(backup_path):
backup_path += '.bak'
print('DBG: creating backup', backup_path)
shutil.copyfile(filepath, backup_path)
with open(filepath) as f:
old_text = f.read()
all_matches = search_pattern.findall(old_text)
if all_matches:
print('Found {} matches in file {}'.format(len(all_matches), filename))
new_text = search_pattern.sub(cfg.replace_regex, old_text)
if not cfg.dry_run:
with open(filepath, "w") as f:
print('DBG: replacing in file', filepath)
f.write(new_text)
else:
for idx, matches in enumerate(all_matches):
print("Match #{}: {}".format(idx, matches))
print("NEW TEXT:n{}".format(new_text))
elif cfg.verbose:
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''DESCRIPTION:
Find and replace recursively from the given folder using regular expressions''',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''USAGE:
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
'''.format(os.path.basename(sys.argv[0])))
parser.add_argument('--dir', '-d',
help='folder to search in; by default current folder',
default='.')
parser.add_argument('--search-regex', '-s',
help='search regex',
required=True)
parser.add_argument('--replace-regex', '-r',
help='replacement regex',
required=True)
parser.add_argument('--glob', '-g',
help='glob pattern, i.e. *.html',
default="*.*")
parser.add_argument('--dry-run', '-dr',
action='store_true',
help="don't replace anything just show what is going to be done",
default=False)
parser.add_argument('--create-backup', '-b',
action='store_true',
help='Create backup files',
default=False)
parser.add_argument('--verbose', '-v',
action='store_true',
help="Show files which don't match the search regex",
default=False)
parser.add_argument('--print-parent-folder', '-p',
action='store_true',
help="Show the parent info for debug",
default=False)
config = parser.parse_args(sys.argv[1:])
find_replace(config)
Here
is an updated version of the script which highlights the search terms and replacements with different colors.
1
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)globstar
option and**
globs orfind
. For a dry run, just usesed
. Unless you use the-i
option, it won't make any changes. For a backup usesed -i.bak
(orperl -i .bak
); for files that don't match, usegrep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Whyscript.py --glob=foo*
instead of justscript.py foo*
?
– terdon♦
Nov 23 '17 at 9:34
1
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowingsed
andawk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).
– ccpizza
Nov 23 '17 at 12:59
add a comment
|
I needed something that would provide a dry-run option and would work recursively with a glob, and after trying to do it with awk
and sed
I gave up and instead did it in python.
The script searches recursively all files matching a glob pattern (e.g. --glob="*.html"
) for a regex and replaces with the replacement regex:
find_replace.py [--dir=my_folder]
--search-regex=<search_regex>
--replace-regex=<replace_regex>
--glob=[glob_pattern]
--dry-run
Every long option such as --search-regex
has a corresponding short option, i.e. -s
. Run with -h
to see all options.
For example, this will flip all dates from 2017-12-31
to 31-12-2017
:
python replace.py --glob=myfile.txt
--search-regex="(d{4})-(d{2})-(d{2})"
--replace-regex="3-2-1"
--dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re
import argparse
def find_replace(cfg):
search_pattern = re.compile(cfg.search_regex)
if cfg.dry_run:
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
for filename in fnmatch.filter(files, cfg.glob):
if cfg.print_parent_folder:
pardir = os.path.normpath(os.path.join(path, '..'))
pardir = os.path.split(pardir)[-1]
print('[%s]' % pardir)
filepath = os.path.join(path, filename)
# backup original file
if cfg.create_backup:
backup_path = filepath + '.bak'
while os.path.exists(backup_path):
backup_path += '.bak'
print('DBG: creating backup', backup_path)
shutil.copyfile(filepath, backup_path)
with open(filepath) as f:
old_text = f.read()
all_matches = search_pattern.findall(old_text)
if all_matches:
print('Found {} matches in file {}'.format(len(all_matches), filename))
new_text = search_pattern.sub(cfg.replace_regex, old_text)
if not cfg.dry_run:
with open(filepath, "w") as f:
print('DBG: replacing in file', filepath)
f.write(new_text)
else:
for idx, matches in enumerate(all_matches):
print("Match #{}: {}".format(idx, matches))
print("NEW TEXT:n{}".format(new_text))
elif cfg.verbose:
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''DESCRIPTION:
Find and replace recursively from the given folder using regular expressions''',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''USAGE:
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
'''.format(os.path.basename(sys.argv[0])))
parser.add_argument('--dir', '-d',
help='folder to search in; by default current folder',
default='.')
parser.add_argument('--search-regex', '-s',
help='search regex',
required=True)
parser.add_argument('--replace-regex', '-r',
help='replacement regex',
required=True)
parser.add_argument('--glob', '-g',
help='glob pattern, i.e. *.html',
default="*.*")
parser.add_argument('--dry-run', '-dr',
action='store_true',
help="don't replace anything just show what is going to be done",
default=False)
parser.add_argument('--create-backup', '-b',
action='store_true',
help='Create backup files',
default=False)
parser.add_argument('--verbose', '-v',
action='store_true',
help="Show files which don't match the search regex",
default=False)
parser.add_argument('--print-parent-folder', '-p',
action='store_true',
help="Show the parent info for debug",
default=False)
config = parser.parse_args(sys.argv[1:])
find_replace(config)
Here
is an updated version of the script which highlights the search terms and replacements with different colors.
1
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)globstar
option and**
globs orfind
. For a dry run, just usesed
. Unless you use the-i
option, it won't make any changes. For a backup usesed -i.bak
(orperl -i .bak
); for files that don't match, usegrep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Whyscript.py --glob=foo*
instead of justscript.py foo*
?
– terdon♦
Nov 23 '17 at 9:34
1
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowingsed
andawk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).
– ccpizza
Nov 23 '17 at 12:59
add a comment
|
I needed something that would provide a dry-run option and would work recursively with a glob, and after trying to do it with awk
and sed
I gave up and instead did it in python.
The script searches recursively all files matching a glob pattern (e.g. --glob="*.html"
) for a regex and replaces with the replacement regex:
find_replace.py [--dir=my_folder]
--search-regex=<search_regex>
--replace-regex=<replace_regex>
--glob=[glob_pattern]
--dry-run
Every long option such as --search-regex
has a corresponding short option, i.e. -s
. Run with -h
to see all options.
For example, this will flip all dates from 2017-12-31
to 31-12-2017
:
python replace.py --glob=myfile.txt
--search-regex="(d{4})-(d{2})-(d{2})"
--replace-regex="3-2-1"
--dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re
import argparse
def find_replace(cfg):
search_pattern = re.compile(cfg.search_regex)
if cfg.dry_run:
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
for filename in fnmatch.filter(files, cfg.glob):
if cfg.print_parent_folder:
pardir = os.path.normpath(os.path.join(path, '..'))
pardir = os.path.split(pardir)[-1]
print('[%s]' % pardir)
filepath = os.path.join(path, filename)
# backup original file
if cfg.create_backup:
backup_path = filepath + '.bak'
while os.path.exists(backup_path):
backup_path += '.bak'
print('DBG: creating backup', backup_path)
shutil.copyfile(filepath, backup_path)
with open(filepath) as f:
old_text = f.read()
all_matches = search_pattern.findall(old_text)
if all_matches:
print('Found {} matches in file {}'.format(len(all_matches), filename))
new_text = search_pattern.sub(cfg.replace_regex, old_text)
if not cfg.dry_run:
with open(filepath, "w") as f:
print('DBG: replacing in file', filepath)
f.write(new_text)
else:
for idx, matches in enumerate(all_matches):
print("Match #{}: {}".format(idx, matches))
print("NEW TEXT:n{}".format(new_text))
elif cfg.verbose:
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''DESCRIPTION:
Find and replace recursively from the given folder using regular expressions''',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''USAGE:
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
'''.format(os.path.basename(sys.argv[0])))
parser.add_argument('--dir', '-d',
help='folder to search in; by default current folder',
default='.')
parser.add_argument('--search-regex', '-s',
help='search regex',
required=True)
parser.add_argument('--replace-regex', '-r',
help='replacement regex',
required=True)
parser.add_argument('--glob', '-g',
help='glob pattern, i.e. *.html',
default="*.*")
parser.add_argument('--dry-run', '-dr',
action='store_true',
help="don't replace anything just show what is going to be done",
default=False)
parser.add_argument('--create-backup', '-b',
action='store_true',
help='Create backup files',
default=False)
parser.add_argument('--verbose', '-v',
action='store_true',
help="Show files which don't match the search regex",
default=False)
parser.add_argument('--print-parent-folder', '-p',
action='store_true',
help="Show the parent info for debug",
default=False)
config = parser.parse_args(sys.argv[1:])
find_replace(config)
Here
is an updated version of the script which highlights the search terms and replacements with different colors.
I needed something that would provide a dry-run option and would work recursively with a glob, and after trying to do it with awk
and sed
I gave up and instead did it in python.
The script searches recursively all files matching a glob pattern (e.g. --glob="*.html"
) for a regex and replaces with the replacement regex:
find_replace.py [--dir=my_folder]
--search-regex=<search_regex>
--replace-regex=<replace_regex>
--glob=[glob_pattern]
--dry-run
Every long option such as --search-regex
has a corresponding short option, i.e. -s
. Run with -h
to see all options.
For example, this will flip all dates from 2017-12-31
to 31-12-2017
:
python replace.py --glob=myfile.txt
--search-regex="(d{4})-(d{2})-(d{2})"
--replace-regex="3-2-1"
--dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re
import argparse
def find_replace(cfg):
search_pattern = re.compile(cfg.search_regex)
if cfg.dry_run:
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
for filename in fnmatch.filter(files, cfg.glob):
if cfg.print_parent_folder:
pardir = os.path.normpath(os.path.join(path, '..'))
pardir = os.path.split(pardir)[-1]
print('[%s]' % pardir)
filepath = os.path.join(path, filename)
# backup original file
if cfg.create_backup:
backup_path = filepath + '.bak'
while os.path.exists(backup_path):
backup_path += '.bak'
print('DBG: creating backup', backup_path)
shutil.copyfile(filepath, backup_path)
with open(filepath) as f:
old_text = f.read()
all_matches = search_pattern.findall(old_text)
if all_matches:
print('Found {} matches in file {}'.format(len(all_matches), filename))
new_text = search_pattern.sub(cfg.replace_regex, old_text)
if not cfg.dry_run:
with open(filepath, "w") as f:
print('DBG: replacing in file', filepath)
f.write(new_text)
else:
for idx, matches in enumerate(all_matches):
print("Match #{}: {}".format(idx, matches))
print("NEW TEXT:n{}".format(new_text))
elif cfg.verbose:
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='''DESCRIPTION:
Find and replace recursively from the given folder using regular expressions''',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''USAGE:
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
'''.format(os.path.basename(sys.argv[0])))
parser.add_argument('--dir', '-d',
help='folder to search in; by default current folder',
default='.')
parser.add_argument('--search-regex', '-s',
help='search regex',
required=True)
parser.add_argument('--replace-regex', '-r',
help='replacement regex',
required=True)
parser.add_argument('--glob', '-g',
help='glob pattern, i.e. *.html',
default="*.*")
parser.add_argument('--dry-run', '-dr',
action='store_true',
help="don't replace anything just show what is going to be done",
default=False)
parser.add_argument('--create-backup', '-b',
action='store_true',
help='Create backup files',
default=False)
parser.add_argument('--verbose', '-v',
action='store_true',
help="Show files which don't match the search regex",
default=False)
parser.add_argument('--print-parent-folder', '-p',
action='store_true',
help="Show the parent info for debug",
default=False)
config = parser.parse_args(sys.argv[1:])
find_replace(config)
Here
is an updated version of the script which highlights the search terms and replacements with different colors.
edited Jan 7 '18 at 19:31
answered Nov 15 '17 at 21:59
ccpizzaccpizza
7711 gold badge10 silver badges11 bronze badges
7711 gold badge10 silver badges11 bronze badges
1
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)globstar
option and**
globs orfind
. For a dry run, just usesed
. Unless you use the-i
option, it won't make any changes. For a backup usesed -i.bak
(orperl -i .bak
); for files that don't match, usegrep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Whyscript.py --glob=foo*
instead of justscript.py foo*
?
– terdon♦
Nov 23 '17 at 9:34
1
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowingsed
andawk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).
– ccpizza
Nov 23 '17 at 12:59
add a comment
|
1
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)globstar
option and**
globs orfind
. For a dry run, just usesed
. Unless you use the-i
option, it won't make any changes. For a backup usesed -i.bak
(orperl -i .bak
); for files that don't match, usegrep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Whyscript.py --glob=foo*
instead of justscript.py foo*
?
– terdon♦
Nov 23 '17 at 9:34
1
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowingsed
andawk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).
– ccpizza
Nov 23 '17 at 12:59
1
1
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)
globstar
option and **
globs or find
. For a dry run, just use sed
. Unless you use the -i
option, it won't make any changes. For a backup use sed -i.bak
(or perl -i .bak
); for files that don't match, use grep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Why script.py --glob=foo*
instead of just script.py foo*
?– terdon♦
Nov 23 '17 at 9:34
I don't understand why you would make something this complex. For recursion, use either bash's (or your shell's equivalent)
globstar
option and **
globs or find
. For a dry run, just use sed
. Unless you use the -i
option, it won't make any changes. For a backup use sed -i.bak
(or perl -i .bak
); for files that don't match, use grep PATTERN file || echo file
. And why in the world would you have python expand the glob instead of letting the shell do it? Why script.py --glob=foo*
instead of just script.py foo*
?– terdon♦
Nov 23 '17 at 9:34
1
1
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowing
sed
and awk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).– ccpizza
Nov 23 '17 at 12:59
My why's are very simple: (1) above all, ease of debugging; (2) using only a single well documented tool with a supportive community (3) not knowing
sed
and awk
well and being unwilling to invest extra time on mastering them, (4) readability, (5) this solution will also work on non-posix systems (not that I need that but somebody else might).– ccpizza
Nov 23 '17 at 12:59
add a comment
|
ripgrep (command name rg
) is a grep
tool, but supports search and replace as well.
$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky
$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky
rg
doesn't support in-place option, so you'll have to do it yourself
$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky
See Rust regex documentation for regular expression syntax and features. The -P
switch will enable PCRE2 flavor. rg
supports Unicode by default.
$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat
$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg 'p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)
$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.
$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|w+' -r '[$0]'
[car] bat [cod] map
Like grep
, the -F
option will allow fixed strings to be matched, a handy option which I feel sed
should implement too.
$ printf '2.3/[4]*6nfoon5.3-[4]*9n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29
Another handy option is -U
which enables multiline matching
$ # (?s) flag will allow . to match newline characters as well
$ printf '42nHi therenHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi Day
rg
can handle dos-style files too
$ # same as: sed -E 's/w+(r?)$/1231/'
$ printf 'hi thererngood dayrn' | rg --passthru --crlf 'w+$' -r '123'
hi 123
good 123
Another advantage of rg
is that it is likely to be faster than sed
$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real 0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real 0m0.007s
$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/bcatb/dog/g' big.txt > f1
real 0m0.060s
$ time rg --passthru 'bcatb' -r 'dog' big.txt > f2
real 0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical
$ time LC_ALL=C sed -E 's/b(w+)(s+1)+b/1/g' big.txt > f1
real 0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(w+)(s+1)+' -r '$1' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
add a comment
|
ripgrep (command name rg
) is a grep
tool, but supports search and replace as well.
$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky
$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky
rg
doesn't support in-place option, so you'll have to do it yourself
$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky
See Rust regex documentation for regular expression syntax and features. The -P
switch will enable PCRE2 flavor. rg
supports Unicode by default.
$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat
$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg 'p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)
$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.
$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|w+' -r '[$0]'
[car] bat [cod] map
Like grep
, the -F
option will allow fixed strings to be matched, a handy option which I feel sed
should implement too.
$ printf '2.3/[4]*6nfoon5.3-[4]*9n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29
Another handy option is -U
which enables multiline matching
$ # (?s) flag will allow . to match newline characters as well
$ printf '42nHi therenHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi Day
rg
can handle dos-style files too
$ # same as: sed -E 's/w+(r?)$/1231/'
$ printf 'hi thererngood dayrn' | rg --passthru --crlf 'w+$' -r '123'
hi 123
good 123
Another advantage of rg
is that it is likely to be faster than sed
$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real 0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real 0m0.007s
$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/bcatb/dog/g' big.txt > f1
real 0m0.060s
$ time rg --passthru 'bcatb' -r 'dog' big.txt > f2
real 0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical
$ time LC_ALL=C sed -E 's/b(w+)(s+1)+b/1/g' big.txt > f1
real 0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(w+)(s+1)+' -r '$1' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
add a comment
|
ripgrep (command name rg
) is a grep
tool, but supports search and replace as well.
$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky
$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky
rg
doesn't support in-place option, so you'll have to do it yourself
$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky
See Rust regex documentation for regular expression syntax and features. The -P
switch will enable PCRE2 flavor. rg
supports Unicode by default.
$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat
$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg 'p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)
$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.
$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|w+' -r '[$0]'
[car] bat [cod] map
Like grep
, the -F
option will allow fixed strings to be matched, a handy option which I feel sed
should implement too.
$ printf '2.3/[4]*6nfoon5.3-[4]*9n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29
Another handy option is -U
which enables multiline matching
$ # (?s) flag will allow . to match newline characters as well
$ printf '42nHi therenHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi Day
rg
can handle dos-style files too
$ # same as: sed -E 's/w+(r?)$/1231/'
$ printf 'hi thererngood dayrn' | rg --passthru --crlf 'w+$' -r '123'
hi 123
good 123
Another advantage of rg
is that it is likely to be faster than sed
$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real 0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real 0m0.007s
$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/bcatb/dog/g' big.txt > f1
real 0m0.060s
$ time rg --passthru 'bcatb' -r 'dog' big.txt > f2
real 0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical
$ time LC_ALL=C sed -E 's/b(w+)(s+1)+b/1/g' big.txt > f1
real 0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(w+)(s+1)+' -r '$1' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
ripgrep (command name rg
) is a grep
tool, but supports search and replace as well.
$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky
$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky
rg
doesn't support in-place option, so you'll have to do it yourself
$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky
See Rust regex documentation for regular expression syntax and features. The -P
switch will enable PCRE2 flavor. rg
supports Unicode by default.
$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat
$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg 'p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)
$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.
$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|w+' -r '[$0]'
[car] bat [cod] map
Like grep
, the -F
option will allow fixed strings to be matched, a handy option which I feel sed
should implement too.
$ printf '2.3/[4]*6nfoon5.3-[4]*9n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29
Another handy option is -U
which enables multiline matching
$ # (?s) flag will allow . to match newline characters as well
$ printf '42nHi therenHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi Day
rg
can handle dos-style files too
$ # same as: sed -E 's/w+(r?)$/1231/'
$ printf 'hi thererngood dayrn' | rg --passthru --crlf 'w+$' -r '123'
hi 123
good 123
Another advantage of rg
is that it is likely to be faster than sed
$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real 0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real 0m0.007s
$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/bcatb/dog/g' big.txt > f1
real 0m0.060s
$ time rg --passthru 'bcatb' -r 'dog' big.txt > f2
real 0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical
$ time LC_ALL=C sed -E 's/b(w+)(s+1)+b/1/g' big.txt > f1
real 0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(w+)(s+1)+' -r '$1' big.txt > f2
real 0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
answered 53 mins ago
SundeepSundeep
7,9561 gold badge13 silver badges30 bronze badges
7,9561 gold badge13 silver badges30 bronze badges
add a comment
|
add a comment
|
protected by Community♦ Jul 27 '15 at 21:05
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
2
This is intended to be a canonical Q&A on this subject (see this meta discussion), please feel free to edit my answer below or add your own.
– terdon♦
Feb 1 '14 at 17:08