关于shell:在Bash中的命令中扩展变量的单引号

Expansion of variable inside single quotes in a command in Bash

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

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

在这种格式中,对$进行转义,不展开变量。

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

1
2
3
4
5
6
7
repo forall -c '...."$variable" '

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

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

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

如果我用这个值代替变量,命令就可以执行了。

请告诉我哪里出错了。


在单引号内,所有的东西都是字面上保存的,毫无例外。

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

1
2
3
'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

正如您可以验证的那样,以上每一行对于shell都是一个单独的单词。字符串连接只是通过并列完成的。引号(单引号或双引号,视情况而定)用于禁止解释各种特殊字符,如空格、$;…有关引用的好教程,请参阅马克·里德的答案。同样相关:哪些字符需要在bash中转义?

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

您应该绝对避免通过连接变量来构建shell命令。这是一个坏主意,类似于SQL片段的串联(SQL注入!).

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

例如,以下内容非常不安全。不要这样做

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

如果EDOCX1[2]的内容不可信,则存在以下漏洞:

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

不要使用上面的调用,而是使用位置参数。下面的调用更好——它是不可利用的:

1
2
script='echo"arg 1 is: $1"'
/bin/sh -c"$script" --"$myvar"

请注意,在分配给script时使用了单勾号,这意味着它是按字面意思进行的,没有变量扩展或任何其他形式的解释。


repo命令不关心它得到什么样的报价。如果需要参数扩展,请使用双引号。如果这意味着你不得不反斜杠很多东西,使用单引号的大部分,然后打破它们,进入双打的部分,你需要扩展发生。

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

如果你感兴趣的话,解释如下。

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

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

您可以在任何字符(包括空格或其他反斜杠)前面使用反斜杠来告诉shell从字面上处理该字符。但是当你可以这样做的时候:

echo \"Thank\ you.\ \ That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

…会让人厌烦的。因此,壳牌公司提供了另一种选择:引号。这些有两个主要品种。

双引号称为"分组引号"。它们防止通配符和别名被扩展,但主要是用于在一个单词中包含空格。其他事情,如参数和命令扩展(由$发出信号的那种事情)仍然会发生。当然,如果您希望在双引号内使用文字双引号,则必须将其反斜杠:

echo"\"Thank you. That'll be \$4.96, please,\" said the cashier"

单引号更严厉。他们之间的一切都是完全按照字面意思,包括反斜杠。绝对没有办法在单引号中得到一个字面单引号。

幸运的是,外壳中的引号不是单词分隔符;它们本身不会终止单词。您可以在同一单词内输入和输出引号,包括不同类型的引号,以获得所需的结果:

echo '"Thank you. That'\''ll be $4.96, please," said the cashier'

这样做更容易——反斜杠更少,尽管右单引号、反斜杠文字单引号、左单引号序列需要一些适应。

现代shell添加了另一种POSIX标准中未指定的引用样式,其中前导单引号的前缀是美元符号。如此引用的字符串遵循类似于ANSI C编程语言中字符串文字的约定,因此有时称为"ansi字符串"和$''对"ansi引号"。在这样的字符串中,上面关于反斜杠的建议不再适用。相反,它们又变得特别了——不仅可以通过在其前面加上反斜杠来包含一个单引号或反斜杠,而且shell还扩展了ansi c字符转义(例如,换行符为
,制表符为\t,十六进制代码为HH的字符为\xHH)。但是,否则,它们将表现为单引号字符串:不会发生参数或命令替换:

1
echo $'"Thank you.  That\'ll be $4.96, please," said the cashier'

需要注意的是,作为echo命令的参数接收的单个字符串在所有这些示例中都是完全相同的。在shell完成对命令行的分析之后,无法让正在运行的命令知道引用了什么。即使它想。


编辑:(根据相关评论:)

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


下面是对我有用的-

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


只使用PrimTf

而不是

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

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

例如:

1
2
3
template='.... %s'

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


变量可以包含单引号。

1
2
3
myvar=\'....$variable\'

repo forall -c $myvar


这对你有用吗?

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