0

I am trying to replace some texts of a file with another text. I tried following

#!/bin/bash

file="path/to/file.txt"
file_contents=$(cat "$file")
escaped_replacement="Some other text"
sed -i "s/$file_contents/$escaped_replacement/g" "$file"

It gives me error sed: -e expression #1, char 11: unterminated s' command`.

The file has the content

This
is
an
example

But when I change the text to This is an example it works perfectly okay.

It looks like when there is a newline in the text that I want to replace, it gives this error. How can I solve this?

I need to replace some texts with another text from some files.

StaticName
  • 239
  • 1
  • 2
  • 10
  • what's the content of `$file_contents`? it likely contains `/` – phuclv Jun 15 '23 at 08:51
  • 4
    sed processes the input line by line. You cannot match a multiline string like this even if the syntax was correct. – choroba Jun 15 '23 at 09:29
  • See [is-it-possible-to-escape-regex-metacharacters-reliably-with-sed](https://stackoverflow.com/questions/29613304/is-it-possible-to-escape-regex-metacharacters-reliably-with-sed) for why using sed for that (or any other approach using regexps instead of literal strings) will be a struggle. Consider using awk instead as it's also a mandatory POSIX tool, just like sed, but it can work with literal strings. – Ed Morton Jun 15 '23 at 12:39
  • 1
    Are you actually trying to replace the entire contents of the file with `$escaped_replacement`? If so, don't use `sed`, just overwrite the file (deleting the old contents). – Gordon Davisson Jun 15 '23 at 18:35

1 Answers1

2

sed processes the input line by line, so you can't match a multiline string.

You can use Perl which can read the whole file into memory (that's what the -0777 does).

perl -0777 -pi -e "s/$file_contents/$escaped_replacement/g" "$file"

This can still break if the file contains a slash. You can prevent this by using an ENV variable instead of expanding the shell variable in the command before Perl sees it:

fc=$file_contents perl -0777 -pi -e "s/\$ENV{fc}/$escaped_replacement/g" "$file"

(and probably do the same with the other variable, then you can use single quotes for the expression, see below).

If the file contains special regex constructs (e.g. {1,3}) and you want to replace them literally, you might need to precede the pattern with a \Q. This will call quotemeta to escape all the special characters for you.

fc=$file_contents er=$escaped_replacement perl -0777 -pi -e \
    's/\Q$ENV{fc}/$ENV{er}/g' "$file"

If you insist on using sed, here are some steps to try:

It seems you can prefix newlines with backslashes to prevent sed from complainig about syntax:

file_contents=${file_contents//$'\n'/$'\\\n'}

But you can't match a multiline string this way, even if the syntax was correct - unless you use GNU sed. It can load the whole file for you with -z, similarly to Perl above, so if you use the substitution mentioned above, you can run

sed -i -z "s=$file_contents=$escaped_replacement=" "$file"

You might also need to do more substitutions if the file contains slashes, backslashes, etc. For example,

file_contents=${file_contents//'\'/'\\'}  # Escape backslashes.
file_contents=${file_contents//'/'/'\/'}  # Escape slashes.
choroba
  • 231,213
  • 25
  • 204
  • 289