Bash - <<EOF 和 <<'EOF' 之间的区别
Bash - difference between <<EOF and <<'EOF'
GNU Bash - 3.6.6 Here Documents
[n]<<[-]word
here-document
delimiter
If any part of word is 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, the character sequence \newline is ignored, and ‘\’ must be used to quote the characters ‘\’, ‘$’, and ‘`’.
如果我对 EOF 进行单引号,它就可以工作。我认为是因为要调用的 bash /bin/bash 进程获取未扩展的字符串,然后调用的进程解释这些行。
$ /bin/bash<<'EOF'
#!/bin/bash
echo $BASH_VERSION
EOF
3.2.57(1)-release
但是,以下内容会导致错误。我以为 BASH_VERSION 会被扩展并将当前 bash 进程的版本传递给要调用的 /bin/bash 进程。但是不工作。
$ /bin/bash<<EOF
#!/bin/bash
echo $BASH_VERSION
EOF
/bin/bash: line 2: syntax error near unexpected token `('
/bin/bash: line 2: `echo 5.0.17(1)-release'
如果您不引用 EOF
,heredoc 中的变量将由原始 shell 展开,然后再将其作为输入传递给调用的 shell。所以相当于执行
echo 3.2.57(1)-release
在调用的 shell 中。那是无效的 bash
语法,所以你会得到一个错误。
引用该词可防止变量扩展,因此调用的 shell 按字面意思接收 $BASH_VERSION
,并自行扩展。
/bin/bash<<EOF
#!/bin/bash
echo $BASH_VERSION
EOF
您可以从错误消息中推断出,heredoc 正在扩展为:
/bin/bash<<EOF
#!/bin/bash
echo 5.0.17(1)-release
EOF
听起来这就是您所期望的:它正在扩展到外部 shell 的版本。问题不在于 heredoc 或扩展;这是不带引号的括号是语法错误。手动尝试 运行 只是 echo
命令,你会得到同样的错误:
$ echo 5.0.17(1)-release
bash: syntax error near unexpected token `('
要解决此问题,您可以添加额外的引号:
/bin/bash<<EOF
echo '$BASH_VERSION'
EOF
这将起作用并打印外部 shell 的版本。我使用单引号来证明这些引号 不会 抑制变量扩展。外部 shell 看不到这些引号。只有内部 shell 可以。
(我也摆脱了 #!/bin/bash
shebang 行。没有必要,因为你明确地调用了 bash。)
但是,引用并非 100% 可靠。 如果 $BASH_VERSION
碰巧包含单引号,您就会遇到问题。引号使括号 (
)
安全,但它们并非万无一失。作为一般技术,如果您希望它完全安全无论游戏中有什么特殊角色,您都必须跳过一些丑陋的箍。
使用printf '%q'
转义所有特殊字符。
/bin/bash <<EOF
echo $(printf '%q' "$BASH_VERSION")
EOF
这将扩展为 echo 5.0.17\(1\)-release
。
将其作为环境变量传入并使用<<'EOF'
禁用脚本内的插值。
OUTER_VERSION="$BASH_VERSION" /bin/bash <<'EOF'
echo "$OUTER_VERSION"
EOF
这是我的选择。我更喜欢尽可能使用 <<'EOF'
形式。让父 shell 插入传递给子 shell 的脚本可能会令人困惑且难以推理。此外,显式 $OUTER_VERSION
变量使它 crystal 清楚发生了什么。
使用 bash -c 'script'
而不是 heredoc,然后将版本作为命令行参数传入。
bash -c 'echo ""' bash "$BASH_VERSION"
我可能会选择单行脚本。
在第一种情况下,引号阻止了此处文档的任何更改,因此子 shell 看到 echo $BASH_VERSION
并扩展字符串并回显它。
在第二种情况下,没有引号意味着第一个 shell 扩展了信息并且它看到 echo 3.2.57(1)-release
,如果您在命令行中键入它,则会出现语法错误.
如果您在两者中都使用 echo "$BASH_VERSION"
,那么两者都可以工作,但不同的 shell 会扩展 $BASH_VERSION
。
GNU Bash - 3.6.6 Here Documents
[n]<<[-]word here-document delimiter
If any part of word is 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, the character sequence \newline is ignored, and ‘\’ must be used to quote the characters ‘\’, ‘$’, and ‘`’.
如果我对 EOF 进行单引号,它就可以工作。我认为是因为要调用的 bash /bin/bash 进程获取未扩展的字符串,然后调用的进程解释这些行。
$ /bin/bash<<'EOF'
#!/bin/bash
echo $BASH_VERSION
EOF
3.2.57(1)-release
但是,以下内容会导致错误。我以为 BASH_VERSION 会被扩展并将当前 bash 进程的版本传递给要调用的 /bin/bash 进程。但是不工作。
$ /bin/bash<<EOF
#!/bin/bash
echo $BASH_VERSION
EOF
/bin/bash: line 2: syntax error near unexpected token `('
/bin/bash: line 2: `echo 5.0.17(1)-release'
如果您不引用 EOF
,heredoc 中的变量将由原始 shell 展开,然后再将其作为输入传递给调用的 shell。所以相当于执行
echo 3.2.57(1)-release
在调用的 shell 中。那是无效的 bash
语法,所以你会得到一个错误。
引用该词可防止变量扩展,因此调用的 shell 按字面意思接收 $BASH_VERSION
,并自行扩展。
/bin/bash<<EOF #!/bin/bash echo $BASH_VERSION EOF
您可以从错误消息中推断出,heredoc 正在扩展为:
/bin/bash<<EOF
#!/bin/bash
echo 5.0.17(1)-release
EOF
听起来这就是您所期望的:它正在扩展到外部 shell 的版本。问题不在于 heredoc 或扩展;这是不带引号的括号是语法错误。手动尝试 运行 只是 echo
命令,你会得到同样的错误:
$ echo 5.0.17(1)-release
bash: syntax error near unexpected token `('
要解决此问题,您可以添加额外的引号:
/bin/bash<<EOF
echo '$BASH_VERSION'
EOF
这将起作用并打印外部 shell 的版本。我使用单引号来证明这些引号 不会 抑制变量扩展。外部 shell 看不到这些引号。只有内部 shell 可以。
(我也摆脱了 #!/bin/bash
shebang 行。没有必要,因为你明确地调用了 bash。)
但是,引用并非 100% 可靠。 如果 $BASH_VERSION
碰巧包含单引号,您就会遇到问题。引号使括号 (
)
安全,但它们并非万无一失。作为一般技术,如果您希望它完全安全无论游戏中有什么特殊角色,您都必须跳过一些丑陋的箍。
使用
printf '%q'
转义所有特殊字符。/bin/bash <<EOF echo $(printf '%q' "$BASH_VERSION") EOF
这将扩展为
echo 5.0.17\(1\)-release
。将其作为环境变量传入并使用
<<'EOF'
禁用脚本内的插值。OUTER_VERSION="$BASH_VERSION" /bin/bash <<'EOF' echo "$OUTER_VERSION" EOF
这是我的选择。我更喜欢尽可能使用
<<'EOF'
形式。让父 shell 插入传递给子 shell 的脚本可能会令人困惑且难以推理。此外,显式$OUTER_VERSION
变量使它 crystal 清楚发生了什么。使用
bash -c 'script'
而不是 heredoc,然后将版本作为命令行参数传入。bash -c 'echo ""' bash "$BASH_VERSION"
我可能会选择单行脚本。
在第一种情况下,引号阻止了此处文档的任何更改,因此子 shell 看到 echo $BASH_VERSION
并扩展字符串并回显它。
在第二种情况下,没有引号意味着第一个 shell 扩展了信息并且它看到 echo 3.2.57(1)-release
,如果您在命令行中键入它,则会出现语法错误.
如果您在两者中都使用 echo "$BASH_VERSION"
,那么两者都可以工作,但不同的 shell 会扩展 $BASH_VERSION
。