如何在sed替换(插入)端正确扩展包含换行符的Bash变量

时间:2019-01-08 11:24:30

标签: bash sed

首先和我在一起,谢谢。假设我有

$ echo $'foo\nbar'
foo
bar

现在,当我将字符串分配给Bash变量时,Bash不再提供相同的垂直输出:

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

尝试printf:

$ printf "$str\n"
foo
bar

这些示例出于说明目的,因为我正在寻找一种在$ str变量内扩展换行符的方法,以便可以在sed替换(插入)端替换$ str变量。

# 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

我做了几种方法来破解它,但是都没有用;这是一个示例:

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

进一步的测试中,我意识到只要变量不包含换行符,事情就会按预期进行:

# 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

预期结果是sed将在index.html文件的<!-在此插入->之前插入2个衬里。

foo
bar
<!-- insert here -->

我尝试通过一种班轮来实现这一目标。我知道我可以将sed分解为垂直的多行形式,这对我来说会更容易;但是,我想探讨一下是否有一种内衬样式。

这可行吗?

我的系统是macOS High Sierra 10.13.6
Bash版本:3.2.57(1)-发行版
BSD sed的最新更新时间为2005年5月10日

3 个答案:

答案 0 :(得分:0)

echo -e $str

-e是

  • 启用反斜杠转义的解释

答案 1 :(得分:0)

使用sed命令r插入任意文本

str="abc\ndef"

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

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

rm -r "$tmp"

sed将换行符解释为命令分隔符。 ;并不是sed的命令分隔符,只有换行符。不要在;命令中添加/后缀}w或空格-它会被解释为文件名的一部分(是的,也包含空格)。 sed之类的wr命令由换行符转义。

如果您想获得更大的灵活性,请转到awk

答案 2 :(得分:0)

您的示例有一些细微的错误,因此这里有一些有关bash和sed中字符串中的引号和换行符的示例。

报价一般如何工作:

# 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

在处理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'

所有sed示例将打印相同:

blahfoo
barblah

因此,sed替换字符串中的换行符必须为 (1)换行符的转义序列(即'\n'),因此sed可以用文字换行符替换它,或者 (2)文字换行符,其后有换行符(即$'\\\n''\'$'\n',与'\\\n''\\n'或{{1}不同})。

这意味着您需要用换行符的转义序列$'\\n'替换每个文字换行符<0x0a>或在其前面插入 line-continuation-mark 双引号扩展之前,替换字符串中的每个文字换行符将其替换为sed的替换替换字符串。

由于有关sed中转义的更多注意事项,我建议您使用\n的{​​{1}}函数,而不是通过awk将替换字符串作为变量传递,例如

gsub

PS:我不知道这个答案是否对您完全正确,因为您的操作系统使用了过时的bash版本。