I want to write a small tool or Bash snippet that can be used like:

tool foo bar file

and it should replace all occurrences of foo in file with bar.

The key problem for me: both foo and bar can be arbitrary strings, possibly containing symbols or characters that have special meaning in regex or something of sed, awk, etc.
So tools like sed fail for me

Is there a simple way to do this in Bash? The simpler the better.
Thanks in advance!

I tried:

sed -i "s/${key}/${value}/g" file

and

body="$(cat file)"
body="${body//${key}/${value}}"

and

body="$( awk -v "key=${key}" -v "value=${value}" 'BEGIN { gsub(/\$/, "\\$", key) }{ gsub(key, value); print }' file )"

But none is good enough.

7 Replies 7

The question would be more objective if you posted your attempted solution. Otherwise, the question looks like "do my work for me".

I use chmln/sd: sd -F foo bar file (flag -F will "treat FIND and REPLACE_WITH args as literal strings")

@pmf, Thanks, and I think this sd works in the way I want, but it doesn't come with system by default, and actually I wonder why there isn't such thing in Linux for this basic requirement...

If your bash has mapfile:

tool()(
    mapfile -d '' data <"$3"
    builtin printf '%s' "${data//"$1"/"$2"}" >"$3"
)

Add appropriate sanity-checks.

Note the quotes inside the substitution. Not using them is why your attempt failed.

An idea: Why not using an interpreter language like perl or php that supports simple string replace?

A bash/php script:

#!/usr/bin/env bash
PHP='
    while ( $line = fgets(STDIN,10000) )
        echo str_replace($argv[1],$argv[2],$line);
'

if [[ $3 = "" ]]
then
    php -r "$PHP" -- "$1" "$2"
else
    php -r "$PHP" -- "$1" "$2" <"$3"
fi

If no input file is given, then STDIN is read.

With GNU awk do not use sub or gsub because they perform regular expression matching, while what you want is exact string equality. Use the index function, instead, and then substr for the replacement. And use the -i inplace option to edit in place (equivalent of -i with GNU sed).

But your specification is incomplete, as in most similar questions here:

  • What do you want to do with overlapping strings? Example: if you want to replace all occurrences of abab with x in input ababab, should the output be abx or xab? And with input abababab, should it be xx or abxab?

  • Do you want a recursive replacement or not? Example: if you want to replace all occurrences of abab with ab in input abababab, should the output be ababab, abab or ab?

  • ...

Last remark: "none is good enough" is not exactly an accurate description of the problems you encountered...

See Is it possible to escape regex metacharacters reliably with sed.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.