770

What's the simplest way to do a find and replace for a given input string, say abc, and replace with another string, say XYZ in file /tmp/file.txt?

I am writting an app and using IronPython to execute commands through SSH — but I don't know Unix that well and don't know what to look for.

I have heard that Bash, apart from being a command line interface, can be a very powerful scripting language. So, if this is true, I assume you can perform actions like these.

Can I do it with bash, and what's the simplest (one line) script to achieve my goal?

tripleee
  • 175,061
  • 34
  • 275
  • 318
Ash
  • 24,276
  • 34
  • 107
  • 152

18 Answers18

1268

The easiest way is to use sed (or perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Which will invoke sed to do an in-place edit due to the -i option. This can be called from bash.

If you really really want to use just bash, then the following can work:

while IFS='' read -r a; do
    echo "${a//abc/XYZ}"
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

This loops over each line, doing a substitution, and writing to a temporary file (don't want to clobber the input). The move at the end just moves temporary to the original name. (For robustness and security, the temporary file name should not be static or predictable, but let's not go there.)

For Mac users:

sed -i '' 's/abc/XYZ/g' /tmp/file.txt

(See the comment below why)

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
johnny
  • 13,113
  • 2
  • 17
  • 8
  • 4
    Except that invoking mv is pretty much as 'non Bash' as using sed. I nearly said the same of echo, but it's a shell builtin. – slim Feb 08 '09 at 12:39
  • 5
    The -i argument for sed doesn't exist for Solaris (and I would think some other implementations) however, so keep that in mind. Just spent several minutes figuring that out... – Panky Nov 15 '11 at 23:45
  • 5
    Note to self: about regular expression of `sed`: `s/..../..../ - Substitute` and `/g - Global` – checksum Feb 20 '13 at 14:55
  • 2
    Ps: For me, this variant is 1/3 faster: `for a in \`cat hosts.txt\` ; do echo ${a//abc/XYZ} ; done > hosts.txt.t`) – Martin L. Feb 21 '13 at 21:26
  • 127
    Note for Mac users who get an `invalid command code C` error... For in-place replacements, BSD `sed` requires a file extension after the `-i` flag because it saves a backup file with the given extension. For example: `sed -i '.bak' 's/find/replace/' /file.txt` You can skip the backup by using an empty string like so: `sed -i '' 's/find/replace/' /file.txt` – Austin Feb 21 '13 at 21:36
  • 11
    Tip: If you want case insensitive repalce use `s/abc/XYZ/gi` – Boris D. Teoharov Aug 06 '14 at 00:03
  • Anyway to disable the `RegEx` functionality and use them as only strings (Replace only the whole word)? – Royi May 01 '20 at 13:38
  • Note to everyone s/ is for search /g is for global hence forth search globally – GoodJeans Aug 15 '21 at 15:41
  • @Royi I'm not sure if I understood your question, as regex can replace whole words only. Simply type a word between / / slash characters. You must escape some characters with backslash, such as $, *, ", ', \. – Human programmer Feb 23 '22 at 21:56
178

File manipulation isn't normally done by Bash, but by programs invoked by Bash, e.g.:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

The -i flag tells it to do an in-place replacement.

See man perlrun for more details, including how to take a backup of the original file.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • 43
    The purist in me says you can't be sure Perl will be available on the system. But that's very seldom the case nowadays. Perhaps I'm showing my age. – slim Feb 08 '09 at 12:44
  • 3
    Can you show a more complex example. Something like replacing "chdir /blah" with "chdir /blah2". I tried `perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text`, but I keep getting an error with Having no space between pattern and following word is deprecated at -e line 1. Unmatched ( in regex; marked by <-- HERE in m/(chdir)( )( <-- HERE ?:\\/ at -e line 1. – CMCDragonkai Nov 05 '13 at 18:31
  • @CMCDragonkai Check this answer: https://stackoverflow.com/a/12061491/2730528 – Alfonso Santiago Dec 04 '19 at 13:12
  • Why use Perl in this case when it's only function is to wrap a language around sed? – Edwin Buck May 24 '23 at 19:54
  • @EdwinBuck some sed implementations don't support in-place editing. – Alnitak May 26 '23 at 12:12
80

This is an old post but for anyone wanting to use variables as @centurian said the single quotes mean nothing will be expanded.

A simple way to get variables in is to do string concatenation since this is done by juxtaposition in bash the following should work:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
zcourts
  • 4,863
  • 6
  • 49
  • 74
  • 1
    I use this in a file:`sed "s/\"$li\"/- [x]\"\${li:5}\"/" $dat` ang get sed unterminated `s' command – Timo Nov 18 '20 at 17:58
  • Problem solved, well, ... $li comes from a file line, so there is e.g. \n and the error is there. So either awk or another language like python comes. – Timo Nov 18 '20 at 18:38
77

I was surprised when I stumbled over this...

There is a replace command which ships with the "mysql-server" package, so if you have installed it try it out:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

See man replace for more on this.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
rayro
  • 803
  • 6
  • 2
  • 21
    Two things are possible here: a) `replace` is a useful independent tool and the MySQL folks should release it separately and depend on it b) `replace` requires some bit of MySQL o_O Either way, installing mysql-server to get replace would be the wrong thing to do :) – Philip Whitehouse Aug 28 '14 at 15:31
  • only works for mac? in my ubuntu I centos that command does not exist – paul Aug 18 '16 at 10:26
  • 1
    That's because you don't have `mysql-server` package installed. As pointed by @rayro, `replace` is part of it. – Phius Jan 11 '17 at 15:53
  • 2
    "Warning: replace is deprecated and will be removed in a future version." – Steven Vachon Jan 04 '18 at 16:52
  • As of 2018, it is included in the MariaDB package for Ubuntu. – JLB Mar 15 '18 at 18:01
  • Not as powerful, but I have a simple "script" here that does the same in case downloading a MySQL or MariaDB is overkill: https://gist.github.com/neuro-sys/3bf00b6cf28a93e07e44 – neuro_sys Apr 11 '18 at 21:29
  • 2
    Be careful not to run the REPLACE command on Windows! On Windows the REPLACE command is for a fast replication of files. Not relevant to this discussion. – Maor Jun 03 '18 at 10:00
44

Bash, like other shells, is just a tool for coordinating other commands. Typically you would try to use standard UNIX commands, but you can of course use Bash to invoke anything, including your own compiled programs, other shell scripts, Python and Perl scripts etc.

In this case, there are a couple of ways to do it.

If you want to read a file, and write it to another file, doing search/replace as you go, use sed:

    sed 's/abc/XYZ/g' <infile >outfile

If you want to edit the file in place (as if opening the file in an editor, editing it, then saving it) supply instructions to the line editor 'ex'

    echo "%s/abc/XYZ/g
    w
    q
    " | ex file

Example is like vi without the fullscreen mode. You can give it the same commands you would at vi's : prompt.

P i
  • 29,020
  • 36
  • 159
  • 267
slim
  • 40,215
  • 13
  • 94
  • 127
  • @awattar Just do them one at a time in a `for` loop. – slim Jul 30 '20 at 09:05
  • Is there an option to use it together with globbing as a one liner? – awattar Jul 30 '20 at 09:07
  • Not as far as I know. `for f in report*.txt; do echo "%s/abc/XYZ/g \n w \n q \n" | ex file; done` is clean and simple. Why put functionality into `ex` that the shell already has? – slim Jul 30 '20 at 09:15
  • (Or, if your problem outgrows the shell, use Python/Perl/whatever) – slim Jul 30 '20 at 09:16
36

I found this thread among others and I agree it contains the most complete answers so I'm adding mine too:

  1. sed and ed are so useful...by hand. Look at this code from @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
    
  2. When my restriction is to use it in a shell script, no variable can be used inside in place of "abc" or "XYZ". The BashFAQ seems to agree with what I understand at least. So, I can't use:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt
    

    but, what can we do? As, @Johnny said use a while read... but, unfortunately that's not the end of the story. The following worked well with me:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
    
  3. As one can see if nullglob is set then, it behaves strangely when there is a string containing a * as in:

    <VirtualHost *:80>
     ServerName www.example.com
    

    which becomes

    <VirtualHost ServerName www.example.com
    

    there is no ending angle bracket and Apache2 can't even load.

  4. This kind of parsing should be slower than one-hit search and replace but, as you already saw, there are four variables for four different search patterns working out of one parse cycle.

The most suitable solution I can think of with the given assumptions of the problem.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
centurian
  • 1,168
  • 13
  • 25
  • 14
    In your (2) -- you can do `sed -e "s/$x/$y/"`, and it will work. Not the double quotes. It can get seriously confusing if the strings in the variables themselves contain characters with special meaning. For example if x="/" or x="\". When you hit these issues, it probably means you should stop trying to use the shell for this job. – slim Mar 06 '13 at 10:51
32

You can use sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

You can use find and sed if you don't know your filename:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Find and replace in all Python files:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;
Madvin
  • 770
  • 9
  • 15
  • 2
    `-i` isn't "ignore case", it's `-i[SUFFIX], --in-place[=SUFFIX]` (edit files in place (makes backup if SUFFIX supplied)) – gvee Jul 08 '20 at 11:00
15

Be careful if you replace URLs with "/" character.

An example of how to do it:

sed -i "s%http://domain.com%http://www.domain.com/folder/%g" "test.txt"

Extracted from: http://www.sysadmit.com/2015/07/linux-reemplazar-texto-en-archivos-con-sed.html

Linux4you
  • 151
  • 1
  • 2
14

If the file you are working on is not so big, and temporarily storing it in a variable is no problem, then you can use Bash string substitution on the whole file at once - there's no need to go over it line by line:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

The whole file contents will be treated as one long string, including linebreaks.

XYZ can be a variable eg $replacement, and one advantage of not using sed here is that you need not be concerned that the search or replace string might contain the sed pattern delimiter character (usually, but not necessarily, /). A disadvantage is not being able to use regular expressions or any of sed's more sophisticated operations.

johnraff
  • 327
  • 2
  • 5
  • Any tips for using this with tab characters? For some reason my script doesn't find anything with tabs after changing from sed with lots of escaping to this method. – Brian Hannay Sep 25 '17 at 17:37
  • 2
    If you want to put a tab in the string you're replacing, you can do so with Bash's "dollared single quotes" syntax, so a tab is represented by $'\t', and you can do $ echo 'tab'$'\t''separated' > testfile; $ file_contents=$( – johnraff Oct 27 '17 at 07:07
12

You may also use the ed command to do in-file search and replace:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

See more in "Editing files via scripts with ed".

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • 3
    This solution is independent from GNU/FreeBSD (Mac OSX) incompatibilities (unlike `sed -i `). Very nice! – Peterino Sep 12 '14 at 09:59
6

To edit text in the file non-interactively, you need in-place text editor such as vim.

Here is simple example how to use it from the command line:

vim -esnc '%s/foo/bar/g|:wq' file.txt

This is equivalent to @slim answer of ex editor which is basically the same thing.

Here are few ex practical examples.

Replacing text foo with bar in the file:

ex -s +%s/foo/bar/ge -cwq file.txt

Removing trailing whitespaces for multiple files:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Troubleshooting (when terminal is stuck):

  • Add -V1 param to show verbose messages.
  • Force quit by: -cwq!.

See also:

kenorb
  • 155,785
  • 88
  • 678
  • 743
  • Wanted to do the replacements interactively. Hence tried "vim -esnc '%s/foo/bar/gc|:wq' file.txt". But the terminal is stuck now. How shall we make the replacements interactively without the bash shell behaving weirdly. – vineeshvs Apr 20 '19 at 13:46
  • To debug, add `-V1`, to force quit, use `wq!`. – kenorb Apr 26 '19 at 22:39
5

Try the following shell command:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
kenorb
  • 155,785
  • 88
  • 678
  • 743
J Ajay
  • 319
  • 3
  • 3
  • 5
    This is an excellent answer to "how do I accidentally all the files in all subdirectories, too" but that does not seem to be what is asked here. – tripleee Oct 11 '16 at 14:08
  • This syntax won't work for BSD version of `sed`, use `sed -i''` instead. – kenorb Sep 26 '19 at 15:20
4

You can use python within the bash script too. I didn't have much success with some of the top answers here, and found this to work without the need for loops:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()
micalith
  • 49
  • 2
2

Simplest way to replace multiple text in a file using sed command

Command -

sed -i 's#a/b/c#D/E#g;s#/x/y/z#D:/X#g;' filename

In the above command s#a/b/c#D/E#g where I am replacing a/b/c with D/E and then after the ; we again doing the same thing

Shivam Tyagi
  • 306
  • 3
  • 3
1

You can use rpl command. For example you want to change domain name in whole php project.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

This is not clear bash of cause, but it's a very quick and usefull. :)

zalex
  • 729
  • 6
  • 14
1

For MAC users in case you don't read the comments :)

As mentioned by @Austin, if you get the Invalid command code error

For the in-place replacements BSD sed requires a file extension after the -i flag to save to a backup file with given extension.

sed -i '.bak' 's/find/replace' /file.txt

You can use '' empty string if you want to skip backup.

sed -i '' 's/find/replace' /file.txt

All merit to @Austin

Szekelygobe
  • 2,309
  • 1
  • 19
  • 25
0

Open file using vim editor. In command mode

:%s/abc/xyz/g 

This is the simplest

flash
  • 89
  • 10
-1

In case of doing changes in multiple files together we can do in a single line as:-

user_name='whoami'
for file in file1.txt file2.txt file3.txt; do sed -i -e 's/default_user/${user_name}/g' $file; done

Added if in case could be useful.