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

Posted

技术标签:

【中文标题】在 Bash 中的命令中扩展单引号内的变量【英文标题】:Expansion of variables inside single quotes in a command in Bash 【发布时间】:2022-01-09 09:48:29 【问题描述】:

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

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

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

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

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

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

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

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

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

请告诉我哪里出错了。

【问题讨论】:

repo forall -c ' ...before... '"$variable"' ...after...' @n.m.单引号是 repo 命令的一部分。我认为这行不通 bash 吃单引号。要么您不在bash 中,要么单引号不是repo 命令的一部分。 不确定我明白了,但如果需要单引号,不会回购 forall -c "' ...before... "$variable" ...after.. .'”工作? 它是一个在 bash shell 中运行的 bash 脚本,repo forall 命令将参数放在单引号内。如果我替换实际值,命令运行正常。 【参考方案1】:

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

这意味着你必须关闭引号,插入一些东西,然后重新输入。

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

单词连接只是通过并置完成。如您所见,上述每一行对 shell 来说都是一个单词。引号(单引号或双引号,视情况而定)不会隔离单词。它们仅用于禁用对各种特殊字符的解释,例如空格、$;... 有关引用的好教程,请参阅 Mark Reed 的答案。同样相关:Which characters need to be escaped in 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 的赋值中使用了单个刻度,这意味着它是按字面意思表示的,没有变量扩展或任何其他形式的解释。

【讨论】:

@Jo.SO 那行不通。因为 repo 命令将只接受参数,直到第一个引号被关闭。之后的任何内容都将被 repo 忽略并产生另一个错误。 @Rachit - 外壳不能那样工作。 "some"thing' like'this 对 shell 来说就是一个词。就解析而言,引号不会终止任何内容,并且 by shell 调用的命令无法说明引用的方式。 第二句话(“你甚至不能转义单引号”)使单引号成为 Shell 的黑洞。 @Evert:不知道怎么说更好。我已经删除了这句话。 @DanielKaplan 简单的规则是总是引用你的变量,否则你会想出鼻恶魔,比如在空格上分割甚至是全局扩展。总是引用你的变量。【参考方案2】:

repo 命令不关心它得到什么样的引号。如果需要参数扩展,请使用双引号。如果这意味着你最终不得不反斜杠很多东西,对大部分内容使用单引号,然后将它们分开并在需要扩展的部分使用双引号。

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

说明如下,如果您有兴趣。

当您从 shell 运行命令时,该命令作为参数接收的是一个以 null 结尾的字符串数组。这些字符串可能绝对包含 any 非空字符。

但是当 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 用于制表符, 和\xHH 用于十六进制代码HH 的字符)。但是,否则,它们表现为单引号字符串:不会发生参数或命令替换:

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

需要注意的重要一点是,在所有这些示例中,存储在reply 变量中的单个字符串完全相同。同样,在 shell 完成对命令行的解析之后,正在运行的命令无法准确说明每个参数字符串是如何实际键入的——甚至 if 它是键入的,而不是创建的以某种方式编程。

【讨论】:

是的。我现在知道了。它完美地工作。非常感谢您的帮助! 这个回复太棒了。 $'string' 格式是否符合 POSIX?另外,它有名字吗? @Wildcard $'string' 是一个非 POSIX 扩展,我看到它被称为“ANSI 字符串”;我已将这些事实纳入答案。此类字符串适用于大多数现代 Bourne 兼容的 shell:bash、dash、ksh(AT&T 和 PD)和 zsh 都支持它们。 链接回 - What characters are required to be escaped in command line arguments? 在 Unix 和 Linux 堆栈交换上。【参考方案3】:

以下是对我有用的方法-

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

【讨论】:

我希望 TBL_HDFS_DIR_PATH 不是用户提供的输入顺便说一句,这将允许一个很好的 sql 注入... QUOTE 放在一个单独的变量中是完全多余的;双引号内的单引号只是一个常规字符。 (另外,不要对私有变量使用大写。)【参考方案4】:

编辑:(根据有问题的 cmets:)

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

【讨论】:

感谢凯文的帮助。【参考方案5】:

只需使用 printf

而不是

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

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

例如:

template='.... %s'

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

【讨论】:

这个坏了; printf 在这里并没有真正给你买任何东西,并且未能引用命令替换会导致新的引用问题。【参考方案6】:

变量可以包含单引号。

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

repo forall -c $myvar

【讨论】:

他们可以,但这不是正确的做法——您仍应将"$myvar" 放在双引号中。【参考方案7】:

这对你有用吗?

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

【讨论】:

适合你吗?这个问题已经有五年的历史了,已经有了一些答案,甚至是一个被接受的答案......

以上是关于在 Bash 中的命令中扩展单引号内的变量的主要内容,如果未能解决你的问题,请参考以下文章

Bash:单引号/双引号中的变量扩展

Linux 命令中的单引号,不加任何参数以及双引号的作用

linux 的bash变量

有关变量定义的单引号双引号无引号区别总结

linux shell中单引号双引号和没有引号的区别

shell中的单引号和双引号的区别