1

Bear with me at first, thank you. Suppose I have

$ echo $'foo\nbar'
foo
bar

Now when I assign the string to a Bash variable, Bash does not give the same vertical output anymore:

$ str='foo\nbar'
$
$ echo $str
foo\nbar
$
$ echo $'str'
str

Try printf:

$ printf "$str\n"
foo
bar

Those examples are for illustration purposes because I am looking for a way to expand the newline(s) inside the $str variable such that I can substitute the $str variable on sed replacement (insertion) side.

# this does not work:
sed -i.bak $'/<!-- insert here -->/i\\\n'$'str'$'\\\n' index.html

# this works as expected though:
sed -i.bak $'/<!-- insert here -->/i\\\n'foo$'\\\n'bar$'\\\n' index.html

I did several ways to hack this but none worked; here is one example:

# this does not work:
sed -i.bak $'/<!-- insert here -->/i\\\n'`printf 'foo\\x0Abar'`$'\\\n' index.html

Further tests, I realized that as long as the variable does not contain newlines, things work as expected:

# This works as long as str2 does not contain any newline.
str2='foo_bar'
sed -i.bak $'/<!-- insert here -->/i\\\n'$str2$'\\\n' index.html

The expected result is that sed will insert 2 liners in place before <!-- insert here --> of the index.html file.

foo
bar
<!-- insert here -->

I try to achieve this as one liner. I know I can break sed into the vertical, multi-line form, which will be easier for me; however, I want to explore if there is a one liner style.

Is this doable or not?

My system is macOS High Sierra 10.13.6
Bash version: 3.2.57(1)-release
BSD sed was last updated on May 10, 2005

Bart Simpson
  • 749
  • 3
  • 7
  • 17
  • sed doesn't allow arbitrary content as replacement string... see [sed substitution with bash variables](https://stackoverflow.com/questions/7680504/sed-substitution-with-bash-variables) – Sundeep Jan 08 '19 at 11:43
  • consider using `r` command as alternative.. that is the most robust way to insert multiline content (assuming you are not replacing part of line) without worrying what characters are there or worry about metacharacters (for ex: `&`) and so on – Sundeep Jan 08 '19 at 11:45
  • 1
    Bart Simpson: Can you add a proper input sample and an expected output? Not sure how your output should look like – Inian Jan 08 '19 at 12:20
  • In the `echo $'foo\nbar'` case, you are forcing the shell to interpret escape sequences before invoking the echo command, i.e. the string passed to echo literally contains a newline character and not `\n`. In the second case `str` contains `foo\nbar` (ie. these 8 characters and no newline) and echo will simply print them (`echo -e` would convert `\n` to a newline, such as `printf`). In order to replace complex values or values specified by a user, I recommend you use awk, e.g.: `echo "blah blah" | awk -v REP=$'foo\nbar' -- '{gsub(/blah/, REP); print}'`. – Robin479 Jan 08 '19 at 16:40

3 Answers3

1

Your examples have a few subtle error, so here are a few examples regarding quoting and newlines in strings in bash and sed.

How quoting works in general:

# bash converts escape-sequence '\n' to real newline (0x0a) before passing it to echo
$ echo $'foo\nbar'
foo
bar

# bash passes literal 8 characters 'foo\nbar' to echo and echo simply prints them
$ echo 'foo\nbar'
foo\nbar

# bash passes literal 8 characters 'foo\nbar' to echo and echo converts escape-sequence
$ echo -e 'foo\nbar'
foo
bar

# bash passes literal string 'foo\nbar' to echo (twice)
# then echo recombines both arguments using a single space
$ str='foo\nbar'
$ echo $str        "$str"
foo\nbar foo\nbar

# bash interprets escape-sequences and stores result 'foo<0x0a>bar' in str,
# then passes two arguments 'foo' and 'bar' to echo, due to "word splitting"
# then echo recombines both arguments using a single space
$ str=$'foo\nbar'
$ echo $str
foo bar

# bash interprets escape-sequences and stores result 'foo<0x0a>bar' in str,
# then passes it as a single argument to echo, without "word splitting"
$ str=$'foo\nbar'
$ echo "$str"
foo
bar

How to apply shell quoting, when dealing with newlines in sed

# replace a character with newline, using newline's escape-sequence
# sed will convert '\n' to a literal newline (0x0a)
$ sed 's/-/foo\nbar/' <<< 'blah-blah'

# replace a character with newline, using newline's escape-sequence in a variable
# sed will convert '\n' to a literal newline (0x0a)
$ str='foo\nbar' # str contains the escape-sequence '\n' and not a literal newline
$ sed 's/-/'"$str"'/' <<< 'blah-blah'

# replace a character with newline, using a literal newline.
# note the line-continuation-mark \ after 'foo' before the literal newline,
# which is part of the sed script, since everything in-between '' is literal
$ sed 's/-/foo\
bar/' <<< 'blah-blah' # end-of-command

# replace a character with newline, using a newline in shell-escape-mode
# note the same line-continuation-mark \ before $'\n', which is part of the sed script
# note: the sed script is a single string composed of three parts '…\', $'\n' and '…',
$ sed 's/-/foo\'$'\n''bar/' <<< 'blah-blah'

# the same as above, but with a single shell-escape-mode string instead of 3 parts.
# note the required quoting of the line-continuation-mark with an additional \ escape
# i.e. after shell-escaping the sed script contains a single \ and a literal newline
$ sed $'s/-/foo\\\nbar/' <<< 'blah-blah'

# replace a character with newline, using a shell-escaped string in a variable
$ str=$'\n' # str contains a literal newline (0x0a) due to shell escaping
$ sed 's/-/foo\'"$str"'bar/' <<< 'blah-blah'

# same as above with the required (quoted) line-continuation inside the variable
# note, how the single \ from '…foo\' (previous example) became \\ inside $'\\…'
$ str=$'\\\n' # str contains \ and a literal newline (0x0a) due to shell escaping
$ sed 's/-/foo'"$str"'bar/' <<< 'blah-blah'

All the sed examples will print the same:

blahfoo
barblah

So, a newline in sed's replacement string must either be (1) newline's escape-sequence (i.e. '\n'), so sed can replace it with a literal newline, or (2) a literal newline preceded by a line-continuation-mark (i.e. $'\\\n' or '\'$'\n', which is NOT the same as '\\\n' or '\\n' or $'\\n').

This means you need to replace each literal newline <0x0a> with newline's escape-sequence \n or insert a line-continuation-mark before each literal newline inside your replacement string before double-quote-expanding it into sed's substitute replacement string.

Since there are many more caveats regarding escaping in sed, I recommend you use awk's gsub function instead passing your replacement string as a variable via -v, e.g.

$ str=$'foo\nbar'
$ awk -v REP="$str" -- '{gsub(/-/, REP); print}' <<< 'blah-blah'
blahfoo
barblah

PS: I don't know, if this answer is entirely true in your case, because your operating system uses an outdated version of bash.

Robin479
  • 1,606
  • 15
  • 16
0
echo -e $str

where -e is

  • enable interpretation of backslash escapes
Derviş Kayımbaşıoğlu
  • 28,492
  • 4
  • 50
  • 72
0

Use sed command r to insert arbitrary text

str="abc\ndef"

tmp=$(mktemp)
(
   echo
   printf -- "$str"
   echo
) > "$tmp"

sed -i.bak '/<!-- insert here -->/r '"$tmp" index.html

rm -r "$tmp"

sed interprets newline as command delimiter. The ; doesn't really is a seds command delimeter, only newline is. Don't append/suffix ; or } or spaces in the w command - it will be interpreted as part of the filename (yes, spaces also). sed commands like w or r are escaped by a newline.

If you want more flexibility, rather move to awk.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111