在Bash中,单引号内的变量无法扩展为其值。

647

我想从一个 bash 脚本 中运行一个命令,该命令包含单引号、一些其他的命令和变量。

例如:repo forall -c '....$variable'

在这种格式下,$ 被转义了,变量不会被展开。

我尝试了以下变化,但它们都被拒绝了:

repo forall -c '...."$variable" '

repo forall -c " '....$variable' "

" repo forall -c '....$variable' "

repo forall -c "'" ....$variable "'"

如果我在变量的位置替换数值,命令就可以被成功执行。

请告诉我我做错了什么。


60
repo forall -c ' ...before... '"$variable"' ...after...' - n. m.
3
@n.m. 中的单引号是 repo 命令的一部分。我认为这样做不会起作用。 - Rachit
1
bash 会吃掉单引号。要么你不在 bash 中,要么单引号不是 repo 命令的一部分。 - n. m.
@Kevin。不,它出现了错误:'before value after':'before value after' 没有这样的文件或目录。 - Rachit
还有更广泛的问题:何时在Shell变量周围加引号? - tripleee
显示剩余8条评论
8个回答

997

单引号内的所有内容都是逐字保留的,毫无例外。

这意味着您必须关闭引号,插入内容,然后再次输入引号。

'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

单词串联可以通过并置简单完成。您可以验证,上述每行都是Shell中的一个单词。引号(单引号或双引号,取决于情况)不会隔离单词。它们仅用于禁用各种特殊字符的解释,例如空格,$;等。有关引用的良好教程,请参见Mark Reed的答案。还相关的:在Bash中需要转义哪些字符?

不要连接由Shell解释的字符串

绝对应避免通过连接变量来构建Shell命令。这类似于SQL片段的连接(SQL注入!)。

通常可以在命令中使用占位符,并将命令和变量一起提供,以便被调用方可以从调用参数列表中接收它们。

例如,以下操作非常不安全。不要这样做。

script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"

如果$myvar的内容不可信,请注意以下漏洞:

myvar='foo"; echo "you were hacked'

使用位置参数而不是上述调用。以下调用更好--它是不可利用的:

script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"
请注意在赋值给script时使用了单引号,这意味着它会被字面地使用,不会发生变量扩展或任何其他形式的解释。

10
@Rachit - shell不是这样工作的。 "some"thing' like'this 对于shell来说是一个单词。就语法分析而言,引号不会终止任何内容,由shell调用的命令无法确定被引用的具体内容。 - Mark Reed
3
第二句话(“你甚至无法逃避单引号”)使得单引号在Shell中成为了黑洞。 - user707650
在第一个例子中,为什么要用""将变量包起来? - Daniel Kaplan
3
@DanielKaplan 简单的规则是始终引用您的变量,否则您会召唤出鼻音恶魔,例如在空格上分割甚至全局扩展。请始终引用您的变量。 - Jo So
这个回答对我来说非常有用。 - forresthopkinsa
显示剩余3条评论

140
repo命令不在乎使用哪种引号。如果需要参数扩展,请使用双引号。如果这意味着你需要反斜杠转义很多内容,大部分情况下请使用单引号,然后在需要进行扩展的部分跳出单引号,切换到双引号。
repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

如果你有兴趣,下面是解释。

当你从shell运行一个命令时,该命令接收的参数是一个以空字符结尾的字符串数组。这些字符串可以包含任何非空字符。

但是当shell从命令行构建这个字符串数组时,它会特殊解释一些字符;这是为了使命令更容易(实际上是可能)输入。例如,空格通常表示数组中字符串之间的边界;因此,shell中的字符串通常被称为“单词”。但是一个shell单词可能仍然包含空格;你只需要告诉shell你想要的是什么。

你可以在任何字符(包括空格或另一个反斜杠)前面使用反斜杠来告诉shell将该字符视为字面量。但是虽然你可以做类似于以下操作:

reply=\”That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

...它可能会变得令人厌烦。因此,Shell提供了一个替代方案:引号。这些引号有两种主要变体。

双引号被称为“分组引号”。它们阻止通配符和别名的扩展,但主要用于在单词中包含空格。其他类似通过$标记的参数和命令扩展仍然会发生。当然,如果你想在双引号内部使用字面上的双引号,你必须使用反斜杠进行转义:

reply="\"That'll be \$4.96, please,\" said the cashier"

单引号更严格。它们之间的所有内容都被完全按字面意义解释,包括反斜杠。在单引号内部绝对无法得到一个字面上的单引号。
幸运的是,在shell中,引号不是词的分隔符;它们本身并不终止一个词。您可以在同一个词中进入和退出引号,包括不同类型的引号之间,以获得所需的结果。
reply='"That'\''ll be $4.96, please," said the cashier'

所以这样更容易 - 反斜杠要少得多,尽管关闭单引号、反斜杠转义的字面单引号、开放单引号序列需要一些时间来适应。
现代shell添加了另一种在POSIX标准中未指定的引用样式,其中前导的单引号被前缀为美元符号。这样引用的字符串遵循与C编程语言ANSI标准版本中的字符串字面值类似的约定,因此有时被称为“ANSI字符串”,而$'...'对则被称为“ANSI引号”。在这样的字符串中,关于反斜杠被直接解释的建议不再适用。相反,它们再次变得特殊 - 你不仅可以通过在其前面加上反斜杠来包含一个字面单引号或反斜杠,而且shell还会展开ANSI C字符转义(例如换行符的\n,制表符的\t,以及具有十六进制代码HH的字符的\xHH)。然而,除此之外,它们的行为与单引号字符串相同:不进行参数或命令替换。
reply=$'"That\'ll be $4.96, please," said the cashier'

需要注意的重要事项是,在所有这些示例中,存储在reply变量中的单个字符串是完全相同的。同样地,在shell完成解析命令行之后,被运行的命令无法准确地知道每个参数字符串是如何输入的 - 或者甚至是否是通过编程方式创建的,而不是手动输入的。

1
$'string' 格式是否符合 POSIX 标准?此外,它有没有一个名称? - Wildcard
1
在Unix&Linux堆栈交换上,链接回命令行参数中需要转义哪些字符? - Wildcard
1
最重要的信息是:
在 shell 中,引号不是单词分隔符;
- Elan Hasson
@Wildcard $'string' 是一个非 POSIX 扩展,我曾看到它被称为 "ANSI 字符串";我已经将这些事实纳入了答案。这样的字符串在大多数现代 Bourne 兼容的 shell 中都可以工作:bash、ksh 和 zsh 都支持它们。但是它们在严格的 POSIX shell 中不起作用,甚至在类似 ash 和 dash 的大多数 POSIX shell 中也不起作用。 - Mark Reed

5
以下是对我有用的内容 -
QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"

2
我希望TBL_HDFS_DIR_PATH不是用户提供的输入,这样会导致漂亮的SQL注入... - domenukk
2
QUOTE放在单独的变量中是完全多余的;双引号内的单引号只是一个普通字符。(另外,不要使用大写字母表示私有变量。) - tripleee

2

仅使用printf

而不是

repo forall -c '....$variable'

使用printf函数将变量标记替换为扩展的变量。
例如:
template='.... %s'

repo forall -c $(printf "${template}" "${variable}")

1
这是有问题的;在这里 printf 并没有真正帮助你,而且未能引用命令替换会导致新的引用问题。 - tripleee

2

编辑:(根据问题中的评论)

从那时起,我一直在研究这个问题。很幸运,我有一个存储库可供使用。但我仍然不清楚是否需要强制将命令用单引号括起来。我查看了存储库的语法,我认为你不需要这样做。你可以在命令周围使用双引号,然后使用任何你需要的单引号和双引号,只要你转义双引号即可。


0

变量可以包含单引号。

myvar=\'....$variable\'

repo forall -c $myvar

1
他们可以这样做,但这不是正确的方法 - 你仍然应该将"$myvar"放在双引号中。 - tripleee

-1
我一直在想为什么我无法从ssh会话中打印出我的awk语句,所以我找到了这个论坛。虽然这里没有直接帮助我,但如果有人遇到类似以下的问题,请给我点赞。似乎任何单引号或双引号都没有起到帮助作用,但我并没有尝试所有可能性。
check_var="df -h / | awk 'FNR==2{print $3}'"
getckvar=$(ssh user@host "$check_var")
echo $getckvar

你得到了什么?一堆空白。
解决方法:在你的打印函数中转义 \$3

ibug是谁或什么,为什么在$3前面加了一个额外的\?我不是想要转义。我确实欣赏页面源代码中该项周围的单引号。 - Mr.G

-4

这个对你有用吗?

eval repo forall -c '....$variable'

10
这个问题已经五年了,已经有一些答案,甚至有一个被采纳的答案... 对你有帮助吗? - Nico Haase
“eval”将其所有参数压缩成一个字符串,然后通过解析器运行该字符串;在这种情况下,它引起的问题比解决的问题要多得多。 - Charles Duffy

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