1340

I have "I love Suzi and Marry" and I want to change "Suzi" to "Sara".

firstString="I love Suzi and Marry"
secondString="Sara"

Desired result:

firstString="I love Sara and Marry"
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
Zincode
  • 13,441
  • 3
  • 13
  • 6
  • 26
    For the record, this may have been a fine question once upon a time, but since many years now, Stack Overflow does *not* encourage "give me code" type of questions. Please don't take this as a good example of how to ask questions here. – tripleee Jul 27 '20 at 04:47
  • 3
    @tripleee what exactly is the purpose of Stack Overflow if people aren't allowed to ask for code examples? – Ryan McCampbell Jun 26 '23 at 18:37
  • Most questions of this type are too broad, and these days also duplicates. Probably review the [help] and in particular [How to ask](/help/how-to-ask) as well as the guidance for providing a [mre]. Perhaps see also the [Stack Overflow homework FAQ](https://meta.stackoverflow.com/questions/334822/how-do-i-ask-and-answer-homework-questions) which expands on a related topic. – tripleee Jun 26 '23 at 20:22

17 Answers17

2205

To replace the first occurrence of a pattern with a given string, use ${parameter/pattern/string}:

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/"$secondString"}"    
# prints 'I love Sara and Marry'

To replace all occurrences, use ${parameter//pattern/string}:

message='The secret code is 12345'
echo "${message//[0-9]/X}"           
# prints 'The secret code is XXXXX'

(This is documented in the Bash Reference Manual, §3.5.3 "Shell Parameter Expansion".)

Note that this feature is not specified by POSIX — it's a Bash extension — so not all Unix shells implement it. For the relevant POSIX documentation, see The Open Group Technical Standard Base Specifications, Issue 7, the Shell & Utilities volume, §2.6.2 "Parameter Expansion".

oguz ismail
  • 1
  • 16
  • 47
  • 69
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • 4
    @ruakh how do I write this statement with a or condition. Just if I want to replace Suzi or Marry with new string. – Priyatham51 Jan 08 '14 at 22:41
  • 4
    @Priyatham51: There's no built-in feature for that. Just replace one, then the other. – ruakh Jan 09 '14 at 00:05
  • @ruakh : Thank you for your reply . I already did that yesterday, and just wanted to check with you if there is an option so that I don't have to write two lines of code. :) – Priyatham51 Jan 09 '14 at 15:08
  • 4
    Will this work for replacing "\n" (newline) with "
    " (html break)? `$STRING="${STRING/\n/
    }"`
    – Buttle Butkus Jul 23 '15 at 01:34
  • 10
    @Bu: No, because `\n` in that context would represent itself, not a newline. I don't have Bash handy right now to test, but you should be able to write something like, `$STRING="${STRING/$'\n'/
    }"`. (Though you probably want `STRING//` -- replace-all -- instead of just `STRING/`.)
    – ruakh Jul 23 '15 at 05:08
  • 1
    And if original string contains "/home/user/dir/" and I want to replace "/home/usr" by "/home" (getting the result "/home/dir/") ? how can I escape the meaning of "/" itself? – Denio Mariz Feb 15 '16 at 13:55
  • @DenioMariz: `foo=/home/user/dir`, followed by `echo "${foo/\/home\/user//home}"`, will print `/home/dir`. – ruakh Feb 15 '16 at 17:27
  • 74
    To be clear, since this confused me for a bit, the first part has to be a variable reference. You can't do `echo ${my string foo/foo/bar}`. You'd need `input="my string foo"; echo ${input/foo/bar}` – Henrik N Sep 15 '16 at 07:42
  • 1
    @ruakh Mary is usually spelled with a single r :D – JobJob Mar 10 '18 at 17:02
  • @JobJob: Who cares how it's "usually" spelled? The OP wrote `Marry`, end of story. – ruakh Mar 10 '18 at 21:20
  • @ruakh Oh shit - so they did! My apologies, should've commented on the OP. To be clear - was just having a bit of a laugh :D – JobJob Mar 12 '18 at 17:22
  • @DenioMariz I have `foo = "$(pwd)"` and `bar="/test/"` how can I use now `echo "${foo/bar/profi/}" ` – Walter Schrabmair Jul 13 '18 at 14:39
  • NB: If the replacement string contains slashes, just escape them. `path=/home/me/docs; echo "${path/me\/docs/you\/docs}"` – BallpointBen Aug 09 '18 at 20:15
  • How do I replace X with Y and A with B also. Would it be like `${${I like Suzi and Marry/Suzi/Sara}/like/love}`, `${I like Suzi and Marry/Suzi/Sara/like/love}`, or something else? – mekb Aug 23 '19 at 00:24
  • @facepalm42: That was asked and answered in the first two comments to this answer. – ruakh Aug 23 '19 at 00:35
  • Where are parameters like the global selector `//` explained? And which characters do I need to escape in match and replace ? – mgutt Nov 28 '19 at 15:18
  • 2
    @mgutt: This answer links to the relevant documentation. The documentation is not perfect -- for example, instead of mentioning `//` directly, it mentions what happens "If *pattern* begins with ‘`/`’" (which isn't even accurate, since the rest of that sentence assumes that the extra `/` is not actually part of the pattern) -- but I don't know of any better source. – ruakh Nov 28 '19 at 20:09
  • 1
    Why am I getting error about Bad substitution? I copied the entire code. – Čamo Jun 04 '20 at 20:07
  • @Čamo: I'm not a psychic, so unless you post details about what you're doing, I won't be able to tell you what you're doing wrong. – ruakh Jun 04 '20 at 21:35
  • The reason was that I was not in bash. I called it via sh .... and it should be bash .... – Čamo Jun 04 '20 at 21:37
  • @ruakh I've done multiple tests on Bash 4.4 and you can't do two replacements in a row. It doesn't work seemingly because it executes the second statement before the first one has stored the result in the variable needed in the second replacement. If there is a way to force the first statement to store its result in the variable before the second one executes please tell me. – PHP Guru Jul 11 '20 at 19:52
  • @PHPGuru: You must be doing something interestingly different from what I'm picturing; but I can't guess what. Maybe post a question with the details? (If you do post a question, feel free to comment here linking to it. I'm curious. :-) ) – ruakh Jul 11 '20 at 21:28
  • my_array="${my_array[@]//*\/tmp\/*/}" my_array="${my_array[@]//*\/cache\/*/}" – PHP Guru Jul 11 '20 at 21:39
  • @PHPGuru: OK, I see what you're doing wrong. If you post a question and comment here with a link to it, I'll post an answer (unless someone beats me to it). – ruakh Jul 11 '20 at 21:57
  • Sure https://stackoverflow.com/questions/62855432/two-shell-parameter-expansions-one-after-the-next-doesnt-work-bash – PHP Guru Jul 11 '20 at 22:55
  • Can I make the pattern case-insensitive? – Константин Ван Aug 09 '20 at 02:24
  • 1
    this solution is giving me bad substitution. echo "${firstString/Suzi/$secondString}" – solveit Aug 09 '21 at 10:58
  • I also get a bad substitution, my command has /, spaces, symbols like %,+ – Freedo Dec 08 '21 at 08:08
330

This can be done entirely with Bash string manipulation:

first="I love Suzy and Mary"
second="Sara"
first=${first/Suzy/$second}

That will replace only the first occurrence; to replace them all, double the first slash:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Kevin
  • 53,822
  • 15
  • 101
  • 132
  • 55
    It appears that they both answered in the exact same minute :O – Jamie Hutber Nov 03 '19 at 12:06
  • 14
    What if `first` or `second` contain special characters, like `/`, `$`, `{`, `}`, , `.`, `+`, `(`, `)`, `*`, etc.? (Problems with formatting of backslash in this comment.) Perhaps address that in the answer? – Peter Mortensen Jan 27 '22 at 14:57
  • As to wildcards this worked for me `r_getfilter="${r_getfilter/../.*}"` to replace `..` with `.*` (a grep "match anything" string). Using `'.*'` did **not** go well, the single quotes ended up in the substitution. – JL Peyret Oct 16 '22 at 02:05
220

For Dash all previous posts aren't working

The POSIX sh compatible solution is:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")

This will replace the first occurrence on each line of input. Add a /g flag to replace all occurrences:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/g")
tripleee
  • 175,061
  • 34
  • 275
  • 318
Roman Kazanovskyi
  • 3,370
  • 1
  • 21
  • 22
  • 2
    I got this: _$ echo $(echo $firstString | sed 's/Suzi/$secondString/g')_ **I love $secondString and Marry** –  May 09 '17 at 18:43
  • 5
    @Qstnr_La use double quotes for variable substitution: `result=$(echo $firstString | sed "s/Suzi/$secondString/g")` – emc Jun 08 '17 at 06:31
  • 4
    Plus 1 for showing how to output to a variable as well. Thanks! – Chef Pharaoh Nov 01 '17 at 14:28
  • 3
    I fixed the single quotes and also added the missing quotes around the `echo` argument. It deceptively works without quoting with simple strings, but easily breaks on any nontrivial input string (irregular spacing, shell metacharacters, etc). – tripleee Jul 17 '18 at 16:54
  • In sh (AWS Codebuild / Ubuntu sh) I found that I need a single slash at the end, not a double. I'm going to edit the comment as the comments above also show a single slash. – Tim Apr 11 '19 at 01:01
  • One should keep in mind that search string (in this case `Suzi`) is a regular expression, so some characters have special meaning (try matching a dot `.`) – Kuchara Jun 18 '21 at 10:30
86

Try this:

sed "s/Suzi/$secondString/g" <<< "$firstString"

The three greater-than signs create a here string.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Kent
  • 189,393
  • 32
  • 233
  • 301
  • 23
    You don't actually need Sed for this; Bash supports this sort of replacement natively. – ruakh Nov 03 '12 at 16:05
  • 14
    I guess this is tagged "bash" but came here because needed something simple for another shell. This is a nice succinct alternative to what https://wiki.ubuntu.com/DashAsBinSh#A.24.7Bparm.2BAC8.3F.2BAC8-pat.5B.2BAC8-str.5D.7D made it look like I'd need. – natevw Sep 30 '14 at 22:02
  • 4
    This works great for ash/dash or any other POSIX sh. – Yoshua Wuyts Mar 21 '15 at 10:18
  • 3
    I get error **sed: -e expression #1, char 9: unknown option to \`s** – Nam G VU Aug 08 '17 at 08:58
  • @ruakh Actually, I found this useful for cases where the haystack string is big. With the other option I was getting `zsh: command too long`. – Cristian Vrabie Nov 15 '17 at 15:28
  • 2
    First answer that works with stdin, great for pipelining strings. – Dinei Jan 24 '18 at 21:31
  • 2
    @ruakh this answer works with **pipes**. Like `my_command | sed ... | ...` – VasiliNovikov Feb 20 '18 at 18:33
  • 4
    @NamGVU, or anyone else getting that error, it usually means that one of the strings in your search or replace section has the same character as your delimiters. For example, it's common to use / as a delimiter, but if your strings contain *nix directories, then you'll see that error since they also have the / character. – John Pancoast Mar 21 '19 at 00:04
  • @YoshuaWuyts The `<<<` "here string" syntax is not POSIX. – tripleee Jul 27 '20 at 04:54
69

It's better to use Bash than sed if strings have regular expression characters.

echo ${first_string/Suzi/$second_string}

It's portable to Windows and works with at least as old as Bash 3.1.

To show you don't need to worry much about escaping, let's turn this:

/home/name/foo/bar

Into this:

~/foo/bar

But only if /home/name is in the beginning. We don't need sed!

Given that Bash gives us magic variables $PWD and $HOME, we can:

echo "${PWD/#$HOME/\~}"

Thanks for Mark Haferkamp in the comments for the note on quoting/escaping ~.*

Note how the variable $HOME contains slashes, but this didn't break anything.

Further reading: Advanced Bash-Scripting Guide.
If using sed is a must, be sure to escape every character.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
  • This answer stopped me from using `sed` with the `pwd` command to avoid defining a new variable each time my custom `$PS1` runs. Does Bash provide a more general way than magic variables to use the output of a command for string replacement? As for your code, I had to escape the `~` to keep Bash from expanding it into $HOME. Also, what does the `#` in your command do? – Mark Haferkamp May 26 '15 at 00:05
  • 2
    @MarkHaferkamp [See this](http://tldp.org/LDP/abs/html/string-manipulation.html#SUBSTRREPL02) from the "further reading recommended" link. About "escaping the `~`": notice how I quoted stuff. Remember to always quote stuff! And this doesn't just work for magic variables: any variable is capable of substitutions, getting string length, and more, within bash. Congrats on trying to your `$PS1` fast: you may also be interested in [`$PROMPT_COMMAND`](http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x264.html) if you are more comfortable in another programming language and want to code a compiled prompt. – Camilo Martin May 27 '15 at 19:33
  • The "further reading" link explains the "#". On Bash 4.3.30, `echo "${PWD/#$HOME/~}"` doesn't replace my `$HOME` with `~`. Replacing `~` with `\~` or `'~'` works. Any of these work on Bash 4.2.53 on another distro. Can you please update your post to quote or escape the `~` for better compatibility? What I meant by my "magic variables" question was: Can I use Bash's variable substitution on, e.g., the output of `uname` without saving it as a variable first? As for my personal `$PROMPT_COMMAND`, [it's complicated](https://github.com/refola/scripts/blob/master/sourced/prompt). – Mark Haferkamp May 30 '15 at 07:46
  • @MarkHaferkamp Whoa, you're totally right, my bad. Will update the answer now. – Camilo Martin May 31 '15 at 05:45
  • It seems that I was wrong.... Actually rebooting between Chakra Linux and openSUSE (as opposed to chrooting) shows that `${PWD/#$HOME/~}` works in Bash 4.2.53 on SUSE and `${PWD/#$HOME/\~}` works in Bash 4.3.30 on Chakra, but not vice versa. A workaround that works on both distros is to replace `~` with `$(echo '~')`, as in the verbose `${PWD/#$HOME/$(echo '~')}`. **Edit**: I just realized that `$'~'` might be better. I'm testing it now. – Mark Haferkamp Jun 10 '15 at 00:45
  • Please ignore the edit in my previous comment. `$'~'` works great in an echo command, but caused me nothing but frustration when I tried to use it in $PS1 to get a literal `~`. `$(echo '~')` seems to be the best workaround. – Mark Haferkamp Jun 10 '15 at 01:12
  • 3
    @MarkHaferkamp Bash and its obscure pitfalls... :P – Camilo Martin Jun 10 '15 at 13:28
  • this will break if, for example, your name was "abc" and you were in /home/abcd. should search for `$HOME/` but you'll have to escape the slash – 12Me21 Feb 01 '21 at 14:26
38
echo [string] | sed "s|[original]|[target]|g"
  • "s" means "substitute"
  • "g" means "global, all matching occurrences"
  • 10
    The `g` flag is hugely misunderstood. Without it, `sed` will replace the first occurrence *on each line* but if you don't expect multiple occurrences per line, you don't need `g`. (Frequently you see it in expressions where there could only ever be a single match per line, like `s/.*everything.*/all of it/g` where obviously you are matching the entire line in the first place, so there is no way you could match the regex several times). – tripleee Jul 27 '20 at 04:57
  • 1
    this works perfectly when replacing paths with "/" – damdafayton Feb 22 '23 at 19:10
32

If tomorrow you decide you don't love Marry either she can be replaced as well:

today=$(</tmp/lovers.txt)
tomorrow="${today//Suzi/Sara}"
echo "${tomorrow//Marry/Jesica}" > /tmp/lovers.txt

There must be 50 ways to leave your lover.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
vhs
  • 9,316
  • 3
  • 66
  • 70
20

Since I can't add a comment. @ruaka To make the example more readable write it like this

full_string="I love Suzy and Mary"
search_string="Suzy"
replace_string="Sara"
my_string=${full_string/$search_string/$replace_string}
or
my_string=${full_string/Suzy/Sarah}
Nate Revo
  • 301
  • 2
  • 3
  • 2
    Till I came to your example I had understood the order the other way round. This helped clarify what is happening – S Raghav Oct 14 '19 at 10:22
13

Using AWK:

firstString="I love Suzi and Marry"
echo "$firstString" | awk '{gsub("Suzi","Sara"); print}'
tripleee
  • 175,061
  • 34
  • 275
  • 318
Payam
  • 1,068
  • 12
  • 15
12

Pure POSIX shell method, which unlike Roman Kazanovskyi's sed-based answer needs no external tools, just the shell's own native parameter expansions. Note that long file names are minimized so the code fits better on one line:

f="I love Suzi and Marry"
s=Sara
t=Suzi
[ "${f%$t*}" != "$f" ] && f="${f%$t*}$s${f#*$t}"
echo "$f"

Output:

I love Sara and Marry

How it works:

  • Remove Smallest Suffix Pattern. "${f%$t*}" returns "I love" if the suffix $t "Suzi*" is in $f "I love Suzi and Marry".

  • But if t=Zelda, then "${f%$t*}" deletes nothing, and returns the whole string "I love Suzi and Marry".

  • This is used to test if $t is in $f with [ "${f%$t*}" != "$f" ] which will evaluate to true if the $f string contains "Suzi*" and false if not.

  • If the test returns true, construct the desired string using Remove Smallest Suffix Pattern ${f%$t*} "I love" and Remove Smallest Prefix Pattern ${f#*$t} "and Marry", with the 2nd string $s "Sara" in between.

agc
  • 7,973
  • 2
  • 29
  • 50
  • it's worth noting that this depends on t occurring only once in f but it can be modified with a loop and one longest match to replace multiple occurrences – dicktyr Feb 15 '21 at 23:42
  • The only thing I'd add after all this time is to better quote the variables to protect their expanded values. `[ "${f%"$t"*}" != "$f" ] && f="${f%"$t"*}$s${f#*"$t"}"]` – StephenFeather Mar 20 '23 at 17:58
8

Pattern to substitute the first occurrence with special charters:

${parameter/pattern/string}

Pattern to substitute all occurrence with special charters:

${parameter//pattern/string}

firstString="I love //Suzi// and Marry"
secondString="Sara"
firstString="${firstString/\/\/Suzi\/\//"$secondString"}"
echo $firstString

It will print: I love Sara and Marry

Towhidul Islam Tuhin
  • 1,051
  • 13
  • 10
8

I think this is the cleanest form for your use case:

firstString="${firstString//Suzi/$secondString}"
Tomer Gal
  • 933
  • 12
  • 21
0

Try this:

ls *.ext | awk '{print "mv "$1" "$1".newext"}' | sed "s/.ext.newext/.newext/" | parallel {}
0

based on proposed above awk solution, I would extend it to use awk-variables. This will allow passing a text containing special chars..

aString="I love _p1_ very much!"
aVar="complicated \" text \' with \. special ) chars"
awk -v p1="$aVar" '{gsub("_p1_",p1); print}' <<< $aString

produces:

I love complicated " text ' with . special ) chars very much

it would be uneasy to implement this case with sed -e or bash substitutions.

Fedor
  • 31
  • 5
0

As python now builtin available in linux, I would suggest this py string replace str.replace()

firstString="I love Suzi and Marry"
secondString="Sara"

secondString=`python3 -c "s='$firstString'.replace('Suzi', 'Sara'); print(s)" `
echo $secondString
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
-1

The only way I found is store the string in a file, use sed then store the file content in a var :

echo "I love Suzy" > tmp.txt
sed -i "s/Suzy/Sarah/" tmp.txt
set res=`cat tmp.txt`
echo $res
rm tmp.txt

I don't know which kind of shell I am using (only thing I found is sh-4.2 if I type 'sh') but all classic syntax fails, like the simple test=${test2}. It fails 2 times : at the assignment (must use set) and at the ${}.

CodeKiller
  • 67
  • 1
  • 7
  • Sounds like you are using `csh`, but even that supported pipes. `sh-4.2` looks like Bash running in POSIX compatibility mode, but that would not require (or understand) the `set` syntax. There was a time when Csh was quite popular in spite of its obvious flaws, but that was 30+ years ago; probably join us in abandoning it. – tripleee Dec 30 '22 at 09:55
-1

Using sed we can do it easily

sed -i "s+$value_to_be_replaced+$with_variable1 "some character" $with_variable2+g" $file_name
tripleee
  • 175,061
  • 34
  • 275
  • 318
rubal islam
  • 339
  • 3
  • 5
  • 1
    Not my downvote, but the broken nested quoting and the lack of explanation (as well as the inadequate formatting, but I fixed that) are all problematic. – tripleee Dec 30 '22 at 09:50