【问题标题】:Using variables inside a bash heredoc在 bash heredoc 中使用变量
【发布时间】:2019-03-05 05:44:56
【问题描述】:

我正在尝试在 bash heredoc 中插入变量:

var=$1
sudo tee "/path/to/outfile" > /dev/null << "EOF"
Some text that contains my $var
EOF

这并没有像我预期的那样工作($var 是按字面意思处理的,而不是扩展的)。

我需要使用sudo tee,因为创建文件需要 sudo。做类似的事情:

sudo cat > /path/to/outfile <<EOT
my text...
EOT

不起作用,因为&gt;outfile 在当前 shell 中打开文件,而不是使用 sudo。

【问题讨论】:

  • 这是一个可以理解的混淆!如下所述,引用分隔符的任何部分会关闭heredoc 中的扩展(就像它在'' 中一样),但not 引用分隔符会打开扩展(就像它在@987654328 中一样@)。 但是, 您的直觉在 Perl 中是正确的,其中带有单引号标识符的heredoc 的行为就像它在单引号中一样,带有双引号标识符的一个就像在双引号中一样,一个带有返回-ticked 标识符,就像在反引号中一样!见:perlop: <<EOF

标签: bash variables sh heredoc


【解决方案1】:

在回答您的第一个问题时,没有参数替换,因为您已将分隔符放在引号中 - the bash manual says:

here-documents的格式为:

      <<[-]word
              here-document
      delimiter

没有参数扩展、命令替换、算术扩展,或 路径名扩展是在 word 上执行的。如果 word 中的任何字符是 引用,delimiter 是 word 上引号删除的结果,而 here-document 中的行不展开。如果 word 没有被引用,则所有 here-document 的行进行参数扩展、命令替换和算术扩展。 [...]

如果您将第一个示例更改为使用&lt;&lt;EOF 而不是&lt;&lt; "EOF",您会发现它有效。

在您的第二个示例中,shell 仅使用参数 cat 调用 sudo,并且重定向适用于作为原始用户的 sudo cat 的输出。如果你尝试它会起作用的:

sudo sh -c "cat > /path/to/outfile" <<EOT
my text...
EOT

【讨论】:

  • 如果您有兴趣,也可以这样做:(cat &gt; /path/to/outfile) &lt;&lt;EOF 代替 sudo sh -c ... &lt;&lt;EOF
  • 请告诉我埋在 Bash 中是一个很好的理由。
【解决方案2】:

不要在&lt;&lt;EOF 中使用引号:

var=$1
sudo tee "/path/to/outfile" > /dev/null <<EOF
Some text that contains my $var
EOF

变量扩展是 here-docs 中的默认行为。您可以通过引用标签(使用单引号或双引号)来禁用该行为。

【讨论】:

    【解决方案3】:

    作为此处较早答案的后期推论,您可能最终会遇到希望插入 some 而不是 all 变量的情况。您可以通过使用反斜杠来转义美元符号和反引号来解决这个问题;或者您可以将静态文本放入变量中。

    Name='Rich Ba$tard'
    dough='$$$dollars$$$'
    cat <<____HERE
    $Name, you can win a lot of $dough this week!
    Notice that \`backticks' need escaping if you want
    literal text, not `pwd`, just like in variables like
    \$HOME (current value: $HOME)
    ____HERE
    

    演示:https://ideone.com/rMF2XA

    请注意,任何引用机制——\____HERE"____HERE"'____HERE'——都会禁用所有变量插值,并将 here-document 变成一段文字。

    一个常见的任务是将局部变量与脚本结合起来,脚本应该由不同的 shell、编程语言或远程主机进行评估。

    local=$(uname)
    ssh -t remote <<:
        echo "$local is the value from the host which ran the ssh command"
        # Prevent here doc from expanding locally; remote won't see backslash
        remote=\$(uname)
        # Same here
        echo "\$remote is the value from the host we ssh:ed to"
    :
    

    【讨论】:

    • 不知道为什么这被否决了,但它添加了一个有效的注释,它没有包含在现在更高投票的答案中。