8

I'm trying to replace a single line in a file with a multiline string stored in a variable.

I am able to get the correct result when printing to screen, but not if I want to do an in-place replacement.

The file has form:

*some code*
*some code*
string_to_replace
*some code*

I want the resulting file to be:

*some code*
*some code*
line number 1
line number 2
line number 3
*some code*

The code I tried was:

new_string="line number 1\nline number 2\nline number 3"

# Correct output on screen
sed -e s/"string_to_replace"/"${new_string}"/g $file

# Single-line output in file: "line number 1line number 2line number 3"
sed -i s/"string_to_replace"/"${new_string}"/g $file

When trying to combine -i and -e options, the result is the same as when only using -i.

I'm using GNU sed version 4.1.5 on CentOS (connected to it through ssh from Mac).

Bjorn
  • 303
  • 2
  • 4
  • 10
  • As seen in my [now deleted] answer, it was about converting file from Windows. For this, check `dos2unix`. – fedorqui Sep 26 '14 at 07:44
  • Stack Overflow is a site for programming and development questions. This question appears to be off-topic because it is not about programming or development. See [What topics can I ask about here](http://stackoverflow.com/help/on-topic) in the Help Center. Perhaps [Super User](http://superuser.com/) or [Unix & Linux Stack Exchange](http://unix.stackexchange.com/) would be a better place to ask. – jww Jan 15 '18 at 01:52
  • Did you try `"${new_string//\n/\\n}"`? Sed requires `\n` for the substitution; a literal newline will begin the next command. – Toby Speight Jan 15 '18 at 12:48

3 Answers3

4

Although you have specifically asked for sed, you can accomplish this using awk by storing your multiline variable in a file using the following

awk '/string_to_replace/{system("cat file_with_multiple_lines");next}1' file_to_replace_in > output_file
fireball.1
  • 1,413
  • 2
  • 17
  • 42
  • Using a subprocess with `cat` is convenient but rather inefficient. If you find yourself doing this a lot, maybe refactor it to use the common `NF==FNR` Awk idiom for reading a file into memory, then process subsequent files. – tripleee Jul 10 '21 at 09:29
2

In sed you can double quote the command string and let the shell do the expansion for you, like this:

new_string="line number 1\nline number 2\nline number 3"
sed -i "s/string_to_replace/$new_string/" file
  • 2
    This only works in `sed` dialetcts which expand the escape code `\n` to a literal newline character; this behavior is not standard or portable. – tripleee Jul 10 '21 at 09:00
1

Inlining a multi-line string into a sed script requires you to escape any literal newlines (and also any literal & characters, which otherwise interpolates the string you are replacing, as well as of course any literal backslashes, and whichever character you are using as the replacement delimiter). What exactly will work also depends slightly on the precise sed dialect. Ultimately, this may be one of those cases where using something else than sed is more robust and portable. But try e.g.

sed -e 's/[&%\\]/\\&/g' \
    -e '$!s/$/\\/' \
    -e '1s/^/s%string_to_replace%/' \
     -e '$s/$/%g/' <<<$replacement |
# pass to second sed instance
sed -f - "$file"

The <<<"here string" syntax is Bash-specific; you can replace it with printf '%s\n' "$replacement" | sed.

Not all sed versions allow you to pass in a script on standard input with -f -. Maybe try replacing the lone dash with /dev/stdin or /dev/fd/0; if that doesn't work, either, you'll have to save the generated script to a temporary file. (Bash lets you use a command substitution sed -f <(sed ...) "$file" which can be quite convenient, and saves you from having to remove the temporary file when you are done.)

Demo: https://ideone.com/uMqqcx

tripleee
  • 175,061
  • 34
  • 275
  • 318