如何避免heredoc扩展变量?

144
我正在尝试使用环境变量中的替换字符串来创建脚本文件,但我同时也希望防止某些字符被转义。
export PLACEHOLDER1="myPlaceholder1Value"
sudo /bin/su -c "cat << EOF > /etc/init.d/my-script
#!/bin/bash

myvariable_1=toto$PLACEHOLDER1
myvariable_final=\"dynamicvar=\${myvariable_1},\${myvariable_2}\"
EOF
"

这会导致问题,因为myvariable_final没有被转义并且替换为init脚本依赖项($remote_fs, $syslog, $network, $time)中的变量。

#!/bin/bash

myvariable_1=totomyPlaceholder1Value
myvariable_2=titimyPlaceholder2Value
myvariable_final="dynamicvar=,"

如果我试图在美元符号$后面放一个反斜杠\,我可以避免替换,但是我会得到一个不想要的反斜杠\

export PLACEHOLDER1="myPlaceholder1Value"
export PLACEHOLDER2="myPlaceholder2Value"
sudo /bin/su -c "cat << EOF > /etc/init.d/my-script
#!/bin/bash

myvariable_1=toto$PLACEHOLDER1
myvariable_2=titi$PLACEHOLDER2
myvariable_final=\"dynamicvar=\$\{myvariable_1},\$\{myvariable_2}\"
EOF
"

导致结果为:

#!/bin/bash

myvariable_1=totomyPlaceholder1Value
myvariable_2=titimyPlaceholder2Value
myvariable_final="dynamicvar=$\{myvariable_1},$\{myvariable_2}"

期望/实际结果应该是:


#!/bin/bash

myvariable_1=totomyPlaceholder1Value
myvariable_2=titimyPlaceholder2Value
myvariable_final="dynamicvar=${myvariable_1},${myvariable_2}"

通过将 EOF 引用在引号中,并在需要时使用反斜杠来控制转义,解决了问题。
export PLACEHOLDER1="myPlaceholder1Value"
export PLACEHOLDER2="myPlaceholder2Value"
sudo /bin/su -c "cat << 'EOF' > /etc/init.d/my-script
#!/bin/bash

myvariable_1=toto$PLACEHOLDER1
myvariable_2=titi$PLACEHOLDER2
myvariable_final=\"dynamicvar=\${myvariable_1},\${myvariable_2}\"
EOF
"

7
请注意,只有第一个“EOF”需要加引号! - masterxilo
1
如果您在引号中使用“EOF”,它将不会被替换。但是,例如在我的情况下,我需要一些变量被替换,而一些环境变量不需要转义。我们该如何做到这一点? - indianwebdevil
我投票重新开放这个问题,因为该问题并未涵盖其他用户案例:无论如何,请在此处查看:https://github.com/roboll/helmfile/issues/1640#issuecomment-1454786173 - Abdennour TOUMI
2个回答

287

只需使用'EOF'来防止变量扩展:

sudo /bin/su -c "cat << 'EOF' > /etc/init.d/my-script
#                       ^   ^
... contents go here
EOF

来自man bash

Here Documents

This type of redirection instructs the shell to read input from the current source until a line containing only delimiter (with no trailing blanks) is seen. All of the lines read up to that point are then used as the standard input for a command.

The format of here-documents is:

      <<[-]word
              here-document
      delimiter

No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. In the latter case, the character sequence \<newline> is ignored, and \ must be used to quote the characters \, $, and `.


9
加上对“EOF”的引号是解决问题的关键,通过转义,我能够控制什么被替换和什么不被替换。 - TheCodeKiller
在一个 shell 脚本中,当我这样做(创建辅助脚本)时,奇怪的是它无法识别闭合的 'EOF'。它只包括父脚本的剩余部分。 - Sridhar Sarnobat
5
最后我终于弄清楚我做错了什么。'EOF' 应该只用于开标签,而不是闭标签。只需使用没有引号的 EOF 即可。 - Sridhar Sarnobat
1
@SridharSarnobat 哦,有趣。请随意编辑答案以包含此内容。 - fedorqui
如果在使用sudo时,在sh -c参数的末尾应该有一个EOF"来关闭参数。问题在于这样做会悄悄地删除文档中的任何双引号。 - undefined

6

在使用su命令时,请将命令本身放在单引号中,并用反斜杠转义$符号。占位符变量必须在命令bash上下文中设置(在su之后)。因此,您需要执行类似以下的操作:

su -c 'ph="ph"; cat << EOF > script 
varinscript=$ph
var=\${var}
EOF'

你的答案很好,但我想要避免再次将所有的env/export VAR放回命令中。 - TheCodeKiller
那么在这种情况下不需要引用EOF吗? - Walid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接