忘记在bash / POSIX shell中引用变量的安全隐患


206

如果您已经关注unix.stackexchange.com一段时间了,那么您现在应该希望知道,echo $var在Bourne / POSIX shell(zsh是例外)的列表上下文中(如)在列表上下文中不加引号的变量具有非常特殊的含义,并且除非您有充分的理由,否则不应该这样做。

在此处的许多问答中对此进行了详细讨论(示例:为什么我的shell脚本为什么在空格或其他特殊字符上感到窒息?何时需要双引号?shell变量的扩展以及glob对其的影响和split引用vs无引号的字符串扩展

自从70年代末Bourne shell首次发布以来就是这种情况,但Korn shell(David Korn的最大遗憾之一(问题#7))并没有改变它,或者bash主要是复制了Korn shell,这就是POSIX / Unix如何指定的。

现在,我们仍然在这里看到许多答案,甚至偶尔看到一些不引用变量的公开发布的shell代码。您可能以为人们现在已经学到了。

根据我的经验,主要有3种类型的人忽略了引用其变量:

  • 初学者。可以原谅这是完全不直观的语法。这是我们在此站点上进行教育的职责。

  • 健忘的人。

  • 甚至在反复敲打之后仍然没有被说服的人,他们认为Bourne shell的作者肯定不打算让我们引用所有变量

如果我们暴露与此类行为相关的风险,也许我们可以说服他们。

如果忘记引用变量,最糟糕的事情莫过于发生。真的那么糟糕吗?

我们在这里谈论什么样的漏洞?

在什么情况下会出现问题?


8
我想BashPitfalls是您想要的东西。
pawel7318 2014年

我写的这篇文章的反向链接,谢谢您的写作
mirabilos 2014年

5
我想建议增加一个第四组:被过度引用过多次而被打中头的人,也许是第三组成员对其他人(他们成为欺负者)感到沮丧。当然,可悲的是,第四组的人可能在最重要的时候最终无法引用。
确认

Answers:


201

前言

首先,我想这不是解决问题的正确方法。这有点像说“ 您不应该谋杀人民,否则会入狱 ”。

同样,您不要引用变量,因为否则会引入安全漏洞。您引用变量是因为不这样做是错误的(但是如果担心入狱会有所帮助,为什么不这样做)。

刚上火车的人的摘要。

在大多数shell中,不加引号的可变扩展名(尽管(以及该答案的其余部分)也适用于命令替换(`...`$(...))和算术扩展($((...))$[...]))具有非常特殊的含义。描述它的最好方法是就像调用某种隐式split + glob运算符¹

cmd $var

用另一种语言写的像是:

cmd(glob(split($var)))

$var首先根据涉及$IFS特殊参数的复杂规则将其拆分为单词列表(拆分部分),然后将由此拆分产生的每个单词视为一种模式,然后扩展为与其匹配的文件列表(全局部分) 。

举个例子,如果$var包含*.txt,/var/*.xml$IFS 包含,cmd将与多个参数,第一个是所谓的cmd,下的人是在txt 当前目录中的文件和xml文件中/var

如果cmd只想使用两个文字参数cmd 和进行调用*.txt,/var/*.xml,则可以编写:

cmd "$var"

用您其他熟悉的语言:

cmd($var)

我们所说的外壳漏洞是什么意思?

毕竟,从很早就知道,不应在安全敏感的上下文中使用shell脚本。当然,好的,不加引号的变量是一个错误,但是不会造成太大的危害,可以吗?

好吧,尽管事实上有人会告诉您不要将Shell脚本用于Web CGI,或者值得庆幸的是,当今大多数系统都不允许使用setuid / setgid shell脚本,这是shellshock的一件事(可远程利用的bash错误导致2014年9月的头条新闻)揭示了shell仍在可能不应该使用的地方得到广泛使用:在CGI,DHCP客户端钩子脚本,sudoers命令中, setuid命令调用(如果不是as ...)

有时在不知不觉中。例如system('cmd $PATH_INFO')php/ perl/ pythonCGI脚本执行调用一个shell来解释命令行(更不用说事实,cmd本身可能是一个shell脚本,它的作者可能从未预料到从CGI调用)。

当存在特权升级的路径时,即有人(让他称为攻击者)能够执行他无意做的事情时,您就会遇到漏洞。

这总是意味着攻击者提供数据,而该数据正在由特权用户/进程处理,而该用户/进程无意中执行了某些错误,而该错误在大多数情况下是不应该做的。

基本上,当您的错误代码在攻击者的控制下处理数据时,您会遇到问题。

现在,数据的来源并不总是很明显,而且通常很难判断您的代码是否能够处理不受信任的数据。

就变量而言,就CGI脚本而言,很明显,数据是CGI GET / POST参数以及cookie,path,host ...等参数。

对于setuid脚本(在被另一个用户调用时以一个用户身份运行),它是参数或环境变量。

另一个非常常见的向量是文件名。如果您从目录中获取文件列表,则可能是攻击者将文件植入了该目录中

就这一点而言,即使在交互式shell提示下,您也可能容易受到攻击( 例如,在其中/tmp或其中处理文件时~/tmp)。

甚至a ~/.bashrc都可能是易受攻击的(例如, 在服务器部署中bash被调用ssh以在客户端控制下运行带有某些变量的ForcedCommand类似对象时,将对其进行解释git)。

现在,可能不会直接调用脚本来处理不受信任的数据,但是可能会被另一个执行该命令的命令调用。否则,您的错误代码可能会被复制粘贴到可以执行此操作的脚本中(由您自己决定三年或您的一位同事)。尤其重要的地方是问答网站中的答案,因为您永远不会知道代码的副本可能在哪里结束。

正事;有多糟

到目前为止,与外壳程序代码相关的安全漏洞的头号来源是不加引号的变量(或命令替换)未被引用。一方面是因为这些错误通常会转化为漏洞,另一方面是因为经常看到未引用的变量。

实际上,在寻找Shell代码中的漏洞时,要做的第一件事就是寻找未加引号的变量。很容易发现,通常是一个很好的候选人,通常很容易追溯到攻击者控制的数据。

不带引号的变量有多种方法可以转化为漏洞。我只在这里给出一些共同的趋势。

信息披露

大多数人会因为分割而碰到与未引用变量相关联的错误(例如,如今文件名中经常有空格,而空格是IFS的默认值)。许多人会忽略 球状部分。的水珠部分是至少与作为危险 分割部分。

在未经过滤的外部输入下进行通配,意味着攻击者可以使您读取任何目录的内容。

在:

echo You entered: $unsanitised_external_input

如果$unsanitised_external_input包含/*,则表示攻击者可以看到的内容/。没什么大不了。但是,它变得更加有趣,/home/*它为您提供了机器上的用户名列表/tmp/*/home/*/.forward以提示其他危险行为,/etc/rc*/*启用的服务……无需单独命名它们。值/* /*/* /*/*/*...将仅列出整个文件系统。

拒绝服务漏洞。

与前面的案例相距太远,我们有了DoS。

实际上,列表上下文中任何未引用输入的未引用变量至少是一个DoS漏洞。

即使是专业的Shell脚本编写者,也通常会忘记引用以下内容:

#! /bin/sh -
: ${QUERYSTRING=$1}

:是no-op命令。可能出什么问题了?

这是要分配$1$QUERYSTRINGif $QUERYSTRING 未设置。这也是使CGI脚本也可以从命令行调用的快速方法。

$QUERYSTRING仍然是虽然扩大,因为它没有被引用,那么拆分+水珠操作符被调用。

现在,有一些全局扩展尤其昂贵。在/*/*/*/*一个已经够糟糕了,因为这意味着上市目录,多达4倍的水平下降。除了磁盘和CPU活动外,这还意味着存储成千上万个文件路径(这里在最小的服务器VM上为40k,其中有10k为目录)。

现在/*/*/*/*/../../../../*/*/*/*意味着40k x 10k, /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*足以让最强大的机器瘫痪。

自己尝试一下(尽管为机器崩溃或挂起做好准备):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

当然,如果代码是:

echo $QUERYSTRING > /some/file

然后,您可以填满磁盘。

只需在shell cgibash cgiksh cgi上进行google搜索,就会发现一些页面,向您展示如何在shell中编写CGI。请注意,那些过程参数中有一半是易受攻击的。

甚至大卫·科恩(David Korn)自己的人也 很脆弱(请查看cookie处理)。

多达任意代码执行漏洞

任意代码执行是最严重的漏洞,因为如果攻击者可以运行任何命令,则对其执行的操作没有任何限制。

这一般是在分裂的部分,导致这些。这种拆分导致在仅需要一个参数时将几个参数传递给命令。虽然第一个将在预期的上下文中使用,但其他将在不同的上下文中,因此可能会有不同的解释。一个更好的例子:

awk -v foo=$external_input '$2 == foo'

在此,意图是将$external_inputshell变量的内容分配 给该foo awk变量。

现在:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

拆分后的第二个单词$external_input 未分配给您,foo但被视为awk代码(在此执行任意命令:)uname

这尤其是一个问题,可以执行其他命令的命令(awkenvsed(GNU一个)perlfind...),尤其是在GNU变种(其参数之后接受选项)。有时候,你不会怀疑的命令可以执行其他人一样kshbash或者zsh[printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

如果我们创建一个名为的目录x -o yes,则测试将变为肯定,因为它是我们要评估的完全不同的条件表达式。

更糟糕的是,如果我们创建一个名为的文件x -a a[0$(uname>&2)] -gt 1,且至少具有所有ksh实现(包括sh 大多数商业Unices和某些BSD的实现),则该文件会执行,uname 因为这些shell对[命令的数字比较运算符执行算术求值。

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

与一样bash的文件名x -a -v a[0$(uname>&2)]

当然,如果他们无法任意执行,则攻击者可能会接受较小的损害赔偿(这可能有助于获得任意执行)。任何可以写入文件或更改权限,所有权或具有任何主要或副作用的命令都可以被利用。

各种各样的事情都可以通过文件名来完成。

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

最终使您..可写(使用GNU递归 chmod)。

在公共可写区域(例如,可写区域)中自动处理文件的脚本/tmp必须非常小心地编写。

关于什么 [ $# -gt 1 ]

我觉得这很生气。有些人不知所措,想知道某个特定的扩展是否可能会成为决定是否可以省略引号的问题。

这就像在说。嘿,看起来好像$#不能使用split + glob运算符,让我们让shell对其进行split + glob。还是,嘿,仅仅因为错误不太可能被发现,让我们编写不正确的代码

现在怎么可能呢?OK,$#(或$!$?或任何算术替代)只能包含数字(或-某些数字),因此glob部分不可用。为了使拆分部分能够执行某些操作,我们所需要做的就是$IFS包含数字(或-)。

对于某些shell,$IFS可能是从环境继承而来的,但是如果环境不安全,则无论如何都是游戏。

现在,如果您编写类似以下的函数:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

这意味着函数的行为取决于调用它的上下文。换句话说,$IFS 成为它的输入之一。严格来说,当您为函数编写API文档时,应该是这样的:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

调用函数的代码需要确保其中$IFS不包含数字。所有这些都是因为您不想键入这两个双引号字符。

现在,要使该[ $# -eq 2 ]漏洞成为漏洞,您需要以某种方式使其价值$IFS受到攻击者的控制。可以想象,除非攻击者设法利用另一个错误,否则通常不会发生这种情况。

但这并非闻所未闻。常见的情况是,人们忘记在算术表达式中使用数据之前先清理数据。上面我们已经看到,它可以允许在某些shell中执行任意代码,但是在所有这些shell中,它都允许 攻击者为任何变量提供整数值。

例如:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

并且使用$1with值(IFS=-1234567890),该算术运算会影响设置IFS的副作用,并且下[ 一条命令将失败,这意味着将绕过检查过多的args

如果不调用split + glob运算符,该怎么办?

在另一种情况下,变量和其他扩展名需要加引号:用作模式时。

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

不测试$a$b是否相同(除非zsh),但是否$a匹配中的模式$b。而你需要引用$b如果要比较的字符串(同样的事情在"${a#$b}""${a%$b}""${a##*$b*}"其中$b应该被引用,如果它不被视为一种模式)。

这也就意味着,[[ $a = $b ]]在可能的情况下返回true $a从不同$b(例如当$aanything$b*),或当它们是相同的可能会返回错误(例如,当这两个$a$b[a])。

这可以造成安全漏洞吗?是的,像任何错误一样。在这里,攻击者可以更改脚本的逻辑代码流和/或破坏脚本所做的假设。例如,使用如下代码:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

攻击者可以通过绕过检查'[a]' '[a]'

现在,如果既不使用模式匹配也不使用split + glob运算符,则不给变量加引号的危险是什么?

我必须承认我确实写过:

a=$b
case $a in...

在那里,引用无害,但并非绝对必要。

但是,在这些情况下(例如,在问答中)省略引号的一个副作用是,它可能向初学者发送错误消息:不引用变量也可以

例如,他们可能开始认为,如果a=$b还可以,那么export a=$b也可以(在很多shell中,因为它不在export命令的参数中,所以在列表上下文中)或env a=$b

zsh

zsh确实解决了大多数设计上的尴尬。在zsh(至少在非sh / ksh仿真模式下),如果要splittingglobbingpattern匹配,则必须明确请求:$=varsplit和$~varglob或将变量的内容视为一种模式。

但是,拆分(而不是遍历)仍然在无引号的命令替换后隐式完成(如中所述echo $(cmd))。

同样,有时不引用变量有时会产生副作用,就是清空容器。该zsh行为类似于通过完全禁用glob(与set -f)和拆分(with与IFS='')在其他Shell中可以实现的功能。还在:

cmd $var

没有split + glob,但是如果$var为空,则cmd不会接收任何参数,而不是接收一个空参数。

这可能会导致错误(如显而易见的[ -n $var ])。这可能会破坏脚本的预期和假设并导致漏洞,但是我暂时无法提供一个不太牵强的示例)。

什么,当你对需要拆分+水珠操作?

是的,通常是在您确实希望不使用变量的情况下。但是随后您需要确保在使用前正确调整了splitglob运算符。如果您只希望分割部分而不是glob部分(大多数情况下是这种情况),那么您确实需要禁用globbing(set -o noglob/ set -f)并修复$IFS。否则,您也会造成漏洞(例如上述的David Korn的CGI示例)。

结论

简而言之,在shell中不加引号的变量(或命令替换或算术扩展)确实非常危险,尤其是在错误的上下文中完成时,并且很难知道哪些是错误的上下文。

这就是为什么将其视为不良做法的原因之一。

感谢您到目前为止的阅读。如果它超过您的头,请不要担心。不能期望每个人都能以编写代码的方式理解编写代码的所有含义。这就是为什么我们有良好实践建议的原因 ,因此不必理解为什么就可以遵循它们。

(以防万一,请避免在shell中编写安全敏感的代码)。

并且请在此站点的答案中引用您的变量!


¹在ksh93and pdksh及其衍生物中,除非禁用globlob(即使版本为ksh93u +以上,即使禁用了该选项),也都执行大括号扩展ksh93braceexpand


请注意,使用时[[,只需引用比较的RHS:if [[ $1 = "$2" ]]; then
mirabilos 2014年

2
@mirabilos,是的,但LHS无须没有被引用,所以没有令人信服的理由不存在帖吧(如果我们要主动采取措施,以默认引用,因为它似乎是做的最明智的事情)。还要注意的[[ $* = "$var" ]]是不一样的[[ "$*" = "$var" ]],如果第一个字符$IFS是不是有空间bash(以及mksh是否$IFS为空,但在这种情况下,我不知道什么$*是等于,我应该提出一个错误?))。
斯特凡Chazelas

1
是的,您可以在此处默认引用。现在请不要再有任何有关字段拆分的错误,在我们重新评估之前,我仍然必须先解决我(与您和他人)有关的问题。
mirabilos 2014年

2
@Barmar,假设您的意思是foo='bar; rm *',不会,但是它将列出当前目录的内容,这可能会视为信息泄漏。print $fooin ksh93(其中print的替代品echo解决了它的一些缺点)确实存在代码注入漏洞(例如使用foo='-f%.0d z[0$(uname>&2)]')(您实际上需要print -r -- "$foo"echo "$foo"仍然是错误的并且不可修复(尽管危害程度通常较小))。
斯特凡Chazelas

3
我不是bash专家,但是我已经在其中进行编码十多年了。我经常使用引号,但主要用于处理嵌入的空格。现在,我将更多地使用它们!如果有人能对此答案进行扩展,使其更容易吸收所有优点,那就太好了。我得到了很多,但我也错过了很多。这已经是一篇很长的文章了,但是我知道还有很多东西需要我学习。谢谢!
2014年

34

[受到cas 这个答案的启发。]

但是,如果……呢?

但是,如果我的脚本在使用变量之前将其设置为已知值怎么办?特别是,如果将变量设置为两个或多个可能值之一(但始终将其设置为已知值),并且这些值都不包含空格或glob字符,该怎么办?在这种情况下不带引号使用它是否安全?

如果可能的值之一是空字符串,而我依赖于“清空容器”怎么办?即,如果变量包含空字符串,则我不想在命令中获取空字符串。我什么也不想得到。例如,

如果有条件
然后
    ignorecase =“-i”
其他
    ignorecase =“”
科幻
                                        #请注意,以上命令中的引号不是严格需要的。
grep $ ignorecase   other_ grep _args

我不能说; 如果为空字符串将失败。grep "$ignorecase" other_grep_args$ignorecase

响应:

如另一个答案中所述,如果IFS包含a -或an ,则此操作仍将失败i。如果确保IFS变量中不包含任何字符(并且确保变量中不包含任何glob字符),那么这可能是安全的。

但是有一种更安全的方法(尽管有点丑陋而且很不直观):use ${ignorecase:+"$ignorecase"}。根据POSIX Shell命令语言规范,在  2.6.2参数扩展下

${parameter:+[word]}

    使用替代值。  如果parameter未设置或为null,则应替换为null;否则为null。否则,将替换扩展名word (或word省略空字符串)。

这样的窍门是,我们使用ignorecaseas parameter"$ignorecase"as word。所以${ignorecase:+"$ignorecase"}意味着

如果$ignorecase未设置或为null(即,为空),则应替换为null(即,不带引号的内容);否则为null 。否则,"$ignorecase"将替换的扩展名。

这使我们到达了想要去的地方:如果将变量设置为空字符串,则将其“删除”(整个复杂的表达式将不计算任何值,甚至不包括空字符串),并且该变量具有非整数。 -空值,我们用引号得到该值。


但是,如果……呢?

但是,如果我有一个想要/需要分解成单词的变量怎么办?(这与第一种情况类似;我的脚本已设置了变量,并且我确定它不包含任何全局字符。但是它可能包含空格,并且我希望在空格处将其拆分为单独的参数
PS 。我仍然希望清空。)

例如,

如果有条件
然后
    条件=“-类型f”
其他
    条件=“”
科幻
如果some_other_condition
然后
    条件=“ $ criteria -mtime +42”
科幻
查找“ $ start_directory” $ criteria   other_查找_args

响应:

您可能会认为这是使用的情况eval  没有!  抵制诱惑,甚至考虑在eval这里使用。

同样,如果你已经确保IFS不包含任何字符在变量(除了空间,要兑现),你确信你的变量不包含任何水珠字符,则上述可能安全。

但是,如果您使用的是bash(或ksh,zsh或yash),则有一种更安全的方法:使用数组:

如果有条件
然后
    criteria =(-type f)#您可以说`criteria =(“-type”“ f”)`,但这实际上是不必要的。
其他
    条件=()#请勿在此命令上使用任何引号!
科幻
如果some_other_condition
然后
    条件+ =(-mtime +42)#注意:不是将“ + =”添加到数组中,而是将其添加到数组中。
科幻
找到“ $ start_directory”“ $ {criteria [@]}””   other_找到_args

bash(1)

数组的任何元素都可以使用引用。...如果是或  ,字扩展到所有成员。仅当单词出现在双引号中时,这些下标才不同。如果单词被双引号,… 会将每个元素扩展为一个单独的单词。${name[subscript]}subscript@*name${name[@]}name

因此"${criteria[@]}",在上例中扩展为criteria数组的零个,两个或四个元素,每个元素都被引用。特别是,如果既不的条件  s为真,则criteria数组没有内容(由设置criteria=()语句),并"${criteria[@]}"评估对什么 (甚至不是一个不方便的空字符串)。


当您处理多个单词时,这变得特别有趣且复杂,其中一些单词是动态(用户)输入,您事先不知道这些单词,并且可能包含空格或其他特殊字符。考虑:

printf“输入要查找的文件名:”
读fname
如果[“ $ fname”!=“”]
然后
    条件+ =(-名称“ $ fname”)
科幻

请注意,$fname被引用的每个使用它的时间。即使用户输入诸如foo barfoo*;  "${criteria[@]}"评估为-name "foo bar"-name "foo*"。(请记住,该数组的每个元素都用引号引起来。)

数组并非在所有POSIX Shell中都有效;数组是ksh / bash / zsh / yash-ism。除了…… ,所有外壳程序都支持一个数组:参数列表aka "$@"。如果完成了调用您的参数列表的操作(例如,您已将所有“位置参数”(参数)复制到变量中,或进行了其他处理),则可以将arg列表用作数组:

如果有条件
然后
    set--type f#您可以说`set-“ -type”“ f”`,但这实际上是不必要的。
其他
    设置-
科幻
如果some_other_condition
然后
    设置-“ $ @” -mtime +42
科幻
#同样:set-“ $ @” -name“ $ fname”
查找“ $ start_directory”“ $ @”   other_查找_args

"$@"结构(其中,从历史上看,是先)具有相同的语义-它扩展每个参数(即参数列表中的每个元素)为一个词,因为如果你输入了。"${name[@]}""$1" "$2" "$3" …

摘录自POSIX Shell命令语言规范(在2.5.2特殊参数下)

@

    从一个开始扩展到位置参数,最初为每个设置的位置参数产生一个字段。...,初始字段应保留为单独的字段,...。如果没有位置参数,则@即使@在双引号内,扩展也会产生零字段;…

全文有些含糊;关键是它指定"$@"在没有位置参数时应生成零字段。历史记录:"$@"1979年首次在Bourne shell(bash的前身)中引入时,它有一个错误,"$@"当没有位置参数时,该错误由一个空字符串替换;请参阅shell脚本中的${1+"$@"}含义,以及与shell脚本有什么不同"$@",  传统的伯恩贝壳家族Bourne Shell Family),  ${1+"$@"}是什么意思?在哪里需要?,并"$@"${1+"$@"}


数组也有助于解决第一种情况:

如果有条件
然后
    ignorecase =(-i)#您可以说`ignorecase =(“-i”)`,但这实际上是不必要的。
其他
    ignorecase =()#请勿在此命令上使用任何引号!
科幻
grep“ $ {ignorecase [@]}”   other_ grep _args

____________________

PS(csh)

这不言而喻,但是,对于这里的新手来说,Csh,tcsh等不是Bourne / POSIX shell。他们是一个完全不同的家庭。不同颜色的马。一整个其他的球赛。另一种猫。另一根羽毛的鸟。而且,最特别的是,还有另一罐蠕虫。

本页上的某些内容适用于csh;例如:除非您有充分的理由不这么做,否则最好对所有变量加引号,并且您确定自己知道自己在做什么。但是,在csh中,每个变量都是一个数组-碰巧几乎每个变量都是一个只有一个元素的数组,并且其行为与Bourne / POSIX shell中的普通shell变量非常相似。和语法是非常不同的(我的意思是非常)。因此,我们在这里不再赘述csh-family shell。


1
请注意,在中csh,您宁愿使用而$var:q不是,"$var"因为后者不适用于包含换行符的变量(或者,如果您想单独引用数组的元素,而不是将它们与空格连接成单个参数)。
斯特凡Chazelas

在bash中,bitrate="--bitrate 320"可与一起使用${bitrate:+$bitrate},但bitrate=--bitrate 128不会起作用,${bitrate:+"$bitrate"}因为它会破坏命令。${variable:+$variable}没有可以安全使用"吗?
Freedo

@Freedo:我发现您的评论不清楚。我尤其不清楚您想知道我还没有说过什么。请参阅第二个“但是,如果...”标题-“但是,如果我有一个想要/需要分解为单词的变量,该怎么办?”这是您的情况,您应该遵循那里的建议。但是,如果不能(例如,因为您使用的不是bash,ksh,zsh或yash之外的外壳,并且您正在使用  $@其他东西),或者只是出于其他原因而拒绝使用数组,我指的是您要“如另一个答案中所述”。…(续)
G-Man

(续)......你的建议(使用${variable:+$variable}没有"如果IFS包含)会失败  -,  023abeir或  t
G-Man

好吧,我的变量同时具有a,e,b,i 2和3个字符,并且仍然可以正常使用${bitrate:+$bitrate}
Freedo

11

我对Stéphane的回答表示怀疑,但是有可能滥用$#

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

或$ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

这些是人为的例子,但潜力确实存在。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.