ChatGPT解决这个技术问题 Extra ChatGPT

Replace one substring for another string in shell script

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"
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.
Who are Suzi, Marry and Sara btw?
@tripleee Why not? I found it useful
@Neil General "give me code please" questions with no research effort are problematic because, among other things, we can't guess the OP's baseline. Even in 2012 (and even way back in 1997) the web was full of "how to replace one string with another" questions, anyway. This site now ranks highly in many search engines so this question is now robbing some more informative page of visitors.

o
oguz ismail

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".


@ruakh how do I write this statement with a or condition. Just if I want to replace Suzi or Marry with new string.
@Priyatham51: There's no built-in feature for that. Just replace one, then the other.
Will this work for replacing "\n" (newline) with "<br />" (html break)? $STRING="${STRING/\n/<br />}"
@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'/<br />}". (Though you probably want STRING// -- replace-all -- instead of just STRING/.)
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}
K
Kevin

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"

It appears that they both answered in the exact same minute :O
What if first or second contain special characters, like /, $, {, }, <backslash>, ., +, (, ), *, etc.? (Problems with formatting of backslash in this comment.) Perhaps address that in the answer?
t
tripleee

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")

I got this: $ echo $(echo $firstString | sed 's/Suzi/$secondString/g') I love $secondString and Marry
@Qstnr_La use double quotes for variable substitution: result=$(echo $firstString | sed "s/Suzi/$secondString/g")
Plus 1 for showing how to output to a variable as well. Thanks!
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).
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.
P
Peter Mortensen

Try this:

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

You don't actually need Sed for this; Bash supports this sort of replacement natively.
I guess this is tagged "bash" but came here because needed something simple for another shell. This is a nice succinct alternative to what wiki.ubuntu.com/… made it look like I'd need.
This works great for ash/dash or any other POSIX sh.
I get error sed: -e expression #1, char 9: unknown option to `s
First answer that works with stdin, great for pipelining strings.
P
Peter Mortensen

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.


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?
@MarkHaferkamp See this 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 if you are more comfortable in another programming language and want to code a compiled prompt.
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.
@MarkHaferkamp Whoa, you're totally right, my bad. Will update the answer now.
@MarkHaferkamp Bash and its obscure pitfalls... :P
P
Peter Mortensen

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.


A
Alberto Salvia Novella
echo [string] | sed "s/[original]/[target]/g"

"s" means "substitute"

"g" means "global, all matching occurrences"


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).
N
Nate Revo

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}

Till I came to your example I had understood the order the other way round. This helped clarify what is happening
t
tripleee

Using AWK:

firstString="I love Suzi and Marry"
echo "$firstString" | awk '{gsub("Suzi","Sara"); print}'

That trick is the way to go if what you're trying to split isn't actually in a variable but in a text file. Thanks, @Payam!
a
agc

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.


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
C
CodeKiller

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 ${}.