在 Bash 中扩展命令中单引号内的变量

Expansion of variables inside single quotes in a command in Bash

提问人:Rachit 提问时间:12/10/2012 最后编辑:codeforesterRachit 更新时间:7/30/2023 访问量:628360

问:

我想从 bash 脚本运行一个命令,该脚本在单引号和变量内有单引号和其他一些命令。

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

在此格式中,转义且变量未展开。$

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

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

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

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

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

如果我用值代替变量,则命令执行得非常好。

请告诉我我哪里出了问题。

Bash shell 变量 行情

评论

60赞 n. m. could be an AI 12/10/2012
repo forall -c ' ...before... '"$variable"' ...after...'
3赞 Rachit 12/10/2012
@n.m. 单引号是 repo 命令的一部分。我认为这行不通
1赞 n. m. could be an AI 12/10/2012
bash吃单引号。要么不在 中,要么单引号不是命令的一部分。bashrepo
0赞 ecv 12/10/2012
不确定我是否理解你,但万一需要单引号,不会 repo forall -c “' ...以前。。。“$variable”......之后......'”工作?
0赞 Rachit 12/10/2012
它是一个在 bash shell 中运行的 bash 脚本,repo forall 命令采用单引号内的参数。如果我替换实际值,则该命令运行良好。

答:

995赞 Jo So 12/10/2012 #1

在单引号中,所有内容都按字面意思保留,无一例外。

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

'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

评论

0赞 Rachit 12/11/2012
@Jo.SO 那行不通。因为 repo 命令将只接受参数,直到第一个引号关闭。之后的任何内容都将被忽略,并生成另一个错误。
10赞 Mark Reed 12/11/2012
@Rachit - 外壳不是那样工作的。 都是一个字到壳。就解析而言,引号不会终止任何内容,并且 shell 调用的命令无法告诉引用的内容。"some"thing' like'this
3赞 1/12/2016
第二句话(“你甚至无法逃脱单引号”)使单引号成为壳牌的黑洞。
0赞 Jo So 3/9/2017
@Evert:不知道怎么说更好。我删除了这句话。
3赞 Jo So 6/17/2021
@DanielKaplan 简单的规则是始终引用你的变量,否则你会召唤出鼻恶魔,比如在空格上分裂,甚至是球形扩展。始终引用您的变量。
140赞 Mark Reed 12/11/2012 #2

该命令不在乎它得到什么样的报价。如果需要参数扩展,请使用双引号。如果这意味着你最终不得不反切很多东西,那么大部分内容都使用单引号,然后从中分离出来,在需要扩展的部分变成双引号。repo

repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

如果您有兴趣,请进行说明。

从 shell 运行命令时,该命令作为参数接收的是以 null 结尾的字符串数组。这些字符串绝对可以包含任何非 null 字符。

但是,当 shell 从命令行构建该字符串数组时,它会专门解释某些字符;这是为了让命令更容易(实际上,可能)键入。例如,空格通常表示数组中字符串之间的边界;因此,shell 中的字符串通常称为“单词”。但是,一个 shell 单词中可能仍然有空格;你只需要某种方式来告诉 shell 这就是你想要的。

您可以在任何字符(包括空格或其他反斜杠)前面使用反斜杠来告诉 shell 按字面意思处理该字符。但是,虽然你可以做这样的事情:

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

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

双引号称为“分组引号”。它们可以防止扩展通配符和别名,但大多数情况下,它们用于在单词中包含空格。其他事情,如参数和命令扩展(由 a 发出信号的那种事情)仍然会发生。当然,如果你想在双引号内有一个文字双引号,你必须反斜杠它:$

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\xHHHH

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

需要注意的重要一点是,在所有这些示例中,存储在变量中的单个字符串完全相同。同样,在 shell 完成对命令行的解析后,运行的命令无法准确告知每个参数字符串的实际类型化方式,或者即使它被类型化,也无法以某种方式以编程方式创建。reply

评论

1赞 Wildcard 1/22/2016
格式是否符合 POSIX?另外,它有名字吗?$'string'
1赞 Wildcard 3/20/2016
链接返回 - 在 Unix 和 Linux 堆栈交换中,命令行参数中需要转义哪些字符。
1赞 Elan Hasson 2/25/2022
最重要的信息是:shell 中的>引号不是单词分隔符;
0赞 Mark Reed 9/21/2022
@Wildcard $'string' 是一个非 POSIX 扩展,我见过它被称为“ANSI 字符串”;我已将这些事实纳入答案中。这样的字符串在大多数现代 Bourne 兼容的 shell 中工作:bash、ksh 和 zsh 都支持它们。但它们不适用于严格的 POSIX 外壳,甚至不适用于 ash 和 dash 等大部分 POSIX 外壳。
2赞 ecv 12/11/2012 #3

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

从那时起,我一直在研究这个问题。我很幸运,我有回购。不过,我不清楚你是否需要强行将命令括在单引号之间。我研究了 repo 语法,我认为您不需要。您可以在命令周围使用双引号,然后在内部使用所需的任何单引号和双引号,前提是您转义了双引号。

5赞 Manmohan 3/18/2017 #4

以下是对我有用的东西 -

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"

评论

2赞 domenukk 11/4/2017
顺便说一句,我希望TBL_HDFS_DIR_PATH不是用户提供的输入,这将允许一个很好的 sql 注入......
2赞 tripleee 3/23/2019
放入一个单独的变量是完全多余的;双引号内的单引号只是一个常规字符。(此外,不要对私有变量使用大写字母。QUOTE
-4赞 drgnfr 1/23/2018 #5

这对你有用吗?

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

评论

10赞 Nico Haase 1/23/2018
适合你吗?这个问题已经有五年了,已经有了一些答案,甚至是公认的答案......
0赞 Charles Duffy 12/3/2022
eval在通过解析器运行该字符串之前,将其所有参数混合到一个字符串中;在这种情况下,它引起的问题远远多于它解决的问题。
0赞 user3734617 6/24/2018 #6

变量可以包含单引号。

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

repo forall -c $myvar

评论

1赞 tripleee 3/23/2019
他们可以,但这不是正确的方法 - 您仍然应该加双引号。"$myvar"
2赞 AndrewD 10/21/2018 #7

只需使用 printf

而不是

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

使用 printf 将变量 token 替换为扩展变量。

例如:

template='.... %s'

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

评论

1赞 tripleee 3/23/2019
这坏了; 在这里并没有真正买到任何东西,并且未能引用命令 subsitution 会导致新的引用问题。printf
-1赞 Mr.G 12/3/2022 #8

我想知道为什么我永远无法从 ssh 会话中打印我的 awk 声明,所以我找到了这个论坛。这里没有任何东西直接帮助我,但如果有人遇到类似于下面的问题,请给我投赞成票。似乎任何形式的单引号或双引号都无济于事,但后来我没有尝试所有方法。

check_var="df -h / | awk 'FNR==2{print $3}'"
getckvar=$(ssh user@host "$check_var")
echo $getckvar

你会得到什么?一无所有。

修复:在打印功能中转义。\$3

评论

0赞 Mr.G 12/10/2022
谁是 ibug 是什么,为什么它在 \$3 前面加了一个额外的 \?我不是想逃避。我很欣赏页面源代码中项目周围的单引号。