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

此处在许多问答中对此进行了详细讨论(示例:为什么我的shell脚本会在空格或其他特殊字符上造成阻塞?,何时需要双引号?,扩展shell变量并对其进行glob和split效果,带引号与不带引号的字符串扩展)

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

现在,我们仍然在这里看到许多答案,并且偶
occasio最终公开发布的shell代码,其中不引用变量。您可能以为人们现在会
学习。

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


初学者。可以原谅这是完全不直观的语法。这是我们在此站点上进行教育的角色。
健忘的人。
即使反复敲打也不相信的人,
他们肯定Bourne shell的作者没有
让我们引用所有变量。

如果我们暴露与这种行为相关的风险,也许我们可以说服它们。

是什么如果您忘记引用变量,那将是最糟糕的事情。真的那么糟糕吗?

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

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

评论

BashPitfalls是您想要的东西。

我写的这篇文章的反向链接,感谢您的撰写

#1 楼

序言
首先,我想说这不是解决问题的正确方法。
有点像说“你不应该杀人,因为否则,你会入狱的”。
类似地,您不会引用变量,因为否则会引入安全漏洞。您引用
变量是因为这样做不对(如果担心监狱可以帮助,为什么不这样做)。
为刚上火车的人提供一些摘要。
在大多数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"

将出现在您其他熟悉的局域网中guage:
cmd($var)

shell中的漏洞是什么意思?
脚本不应用于安全性-敏感的上下文。
当然,好吧,不加引号的变量是一个bug,但是不能
造成那么多的伤害,可以吗?
好吧,尽管有人会告诉您shell
脚本应该从未用于Web CGI,或者令人庆幸的是,
当今大多数系统都不允许使用setuid / setgid shell脚本,
shellshock的一件事(可远程利用的bash bug
成为头条新闻。 2014年9月)表明,
shell仍然在可能不应该使用的地方得到广泛使用:
在CGI,DHCP客户端挂钩脚本,sudoers命令中,
(如果不是,则调用) setuid命令...
有时在不知不觉中。例如,在system('cmd $PATH_INFO') / php / perl中的python
确实会调用Shell来解释该命令行(不是 cmd本身可能是Shell脚本及其
作者的事实。
当存在特权升级的路径时,即有人(将其称为攻击者)时,您就会遇到漏洞。
能够做他不想要的事情。
这总是意味着攻击者提供了数据,该数据被特权用户/进程处理而无意中做了
在大多数情况下,这样做是由于
的错误。
基本上,当您的错误代码在攻击者的控制下处理数据时,您就会遇到问题。
现在,数据并不总是很清楚,
,通常很难判断代码是否能够处理
不可信数据。
就变量而言,在对于CGI脚本,
很明显,数据是CGI GET / POST参数以及
诸如cookie,path,host ...等参数。
对于setuid脚本(当由
另一个用户调用时以一个用户身份运行时,它是参数或环境变量。
另一个非常常见的向量是文件名。如果您得到
目录中的文件列表,可能是攻击者
将文件植入了其中。
就这一点而言,即使在交互式shell的提示下,您也可能容易受到攻击(在处理时/tmp~/tmp
中的文件)。
~/.bashrc可能也很容易受到攻击(例如,bash通过ssh调用以运行ForcedCommand
时会对其进行解释,就像git中一样在客户端的
控制下具有某些变量的服务器部署)。
现在,可能无法直接调用脚本来处理不受信任的数据,但是另一个脚本可能会调用该脚本。否则,您的
不正确的代码可能会被复制粘贴到可以执行此操作的脚本中(由您下行3年或您的一位同事)。
特别重要的一个地方是在问答网站的答案中,您将
永远都不知道代码的副本可能在哪里结束。它有多严重?
不加引号的变量(或命令替换)是迄今为止
与外壳程序代码关联的第一大安全漏洞来源。一方面是因为这些错误通常会转化为
漏洞,另一方面是因为看到不带引号的变量是很常见的。
实际上,当在shell代码中查找漏洞时,
要做的第一件事寻找未加引号的变量。
很容易发现,通常是一个很好的候选者,通常很容易追溯到攻击者控制的数据。
有无数种方法可以将无引号的变量转化为
脆弱性。我只在这里给出一些常见的趋势。
信息披露
由于分割的部分,大多数人会碰到与未引用的变量相关的错误(例如,
很常见如今文件名中带有空格,而空格
是IFS的默认值)。许多人会忽略
全局部分。球形部分至少与
分割部分一样危险。
在未经过滤的外部输入下进行的通配符表示
攻击者可以让您读取任何目录的内容。
在:
echo You entered: $unsanitised_external_input

如果$unsanitised_external_input包含/*,则表示
攻击者可以看到/的内容。没什么大不了的。尽管有了/home/*,它变得更有趣了,它为您提供了机器上的用户名列表,/tmp/*/home/*/.forward,这些内容用于
其他危险惯例的提示,/etc/rc*/*用于启用的
服务。 ..无需分别命名。值/* /*/* /*/*/*...只会列出整个文件系统。
拒绝服务漏洞。
把前一种情况考虑得太远了,我们有一个DoS。
实际上,任何未引用的变量在具有未消毒输入的列表上下文中,至少是一个DoS漏洞。
即使是专家级的shell脚本编写者也常常忘记引用报价
,例如:
#! /bin/sh -
: ${QUERYSTRING=}

:是没有- op命令。可能出什么问题了?
如果未设置
,则是将$QUERYSTRING分配给$QUERYSTRING。这也是使CGI脚本也可以从命令行调用的一种快速方法。
尽管$QUERYSTRING仍在扩展,并且由于未引用它,所以调用了split + glob运算符。 br />现在,有些球对于扩展来说特别昂贵。 /*/*/*/*的问题已经很严重了,因为它意味着最多列出4个级别的目录。除了磁盘和CPU活动之外,这还意味着存储数万个文件路径(在最小的服务器VM上存储40k,在其中有10k的目录)。
现在/*/*/*/*/../../../../*/*/*/*表示40k x 10k和
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*足以
使最强大的机器屈膝。
亲自尝试一下(尽管要为机器崩溃或挂起做好准备:
/>
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

当然,如果代码是:
echo $QUERYSTRING > /some/file

那么就可以填满磁盘。
只要在shell上用Google搜索
cgi或bash
cgi或ksh
cgi,您会发现一些页面,向您展示如何在shell中编写CGI。请注意
过程参数中有一半是易受攻击的。
即使是大卫·科恩(David Korn)的
自己的
一个
也是易受攻击的(请查看cookie处理)。
最多是任意代码执行漏洞
任意代码执行是最严重的漏洞,
由于攻击者可以运行任何命令,因此对执行操作没有任何限制。
通常这是导致这些问题的主要部分。
拆分会导致几个参数传递给命令,而只需要一个参数。虽然其中的第一个将在预期的上下文中使用,但其他的将在不同的上下文中
,因此可能会有不同的解释。最好举个例子:
awk -v foo=$external_input ' == foo'

这里的目的是将
$external_input shell变量的内容分配给foo awk变量。
现在:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input ' == foo'
Linux

$external_input拆分产生的第二个单词未分配给foo,而是被视为awk代码(此处
执行任意命令:uname)。
这对于可以执行其他
命令(awkenvsed(GNU一个),perlfind ...)的命令,尤其是具有GNU变体(在参数后接受选项)的
。 ,您不会怀疑命令能够执行
其他命令,例如kshbashzsh[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实现(包括大多数商业Unices和某些BSD的sh
),该文件执行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。
或者,嘿,让我们写错误的代码只是因为该错误是
不太可能被击中。
现在怎么可能呢?好的,$#(或$!$?或任何
算术替代)可能只包含数字(或-表示
some²),因此全局部分不存在。对于拆分部分,我们需要做的只是让$IFS包含数字(或-)。
对于某些壳,$IFS可能是从环境继承的,
环境不安全,反正还是游戏。
现在,如果您编写如下函数:
my_function() {
  [ $# -eq 2 ] || return
  ...
}

这意味着您函数的行为取决于
上下文被称为。或换句话说,$IFS 编写函数的API文档时,应该
像这样:
# my_function
#   inputs:
#     : source directory
#     : destination directory
#   $IFS: used to split $#, expected not to contain digits...

调用函数的代码需要确保$IFS不包含数字。所有这些都是因为您不想输入
那两个双引号字符。
现在,要使[ $# -eq 2 ]错误成为一个漏洞,
您需要某种方式获取$IFS的值成为攻击者的控制之下。可以想象,除非攻击者设法利用另一个错误,否则通常不会发生。
这并不是闻所未闻的。一个常见的情况是,人们
在算术表达式中使用数据之前就忘了清理数据。上面我们已经看到,它可以允许在某些shell中执行任意代码,但是在所有这些shell中,它都允许攻击者为任何变量提供整数值。例如:
n=$(( + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

并使用具有值(IFS=-1234567890),该算术评估具有设置IFS的副作用,下一个[
命令失败,这意味着检查了太多的参数是
被绕过。
当未调用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 ]]可能返回如果$a$b不同(例如,当$aanything$b*时)为true,或者当它们相同时(例如,当$a$b均为[a]时)可能返回false。
这可以造成安全漏洞吗?是的,像任何错误一样。在这里,攻击者可以更改脚本的逻辑代码流和/或破坏脚本所做的假设。例如,使用如下代码:
if [[  =  ]]; then
   echo >&2 ' and  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也将是一样的(因为它在export命令的参数中并不存在于许多shell中,所以在列表上下文中是这样)或env a=$b
zsh呢? 。在zsh中(至少在不处于sh / ksh仿真模式下时),如果要拆分或glob匹配或模式匹配,则必须明确请求:zsh拆分,$=var要求glob或将变量内容设置为
但是,拆分(但不是遍历)仍然是在无引号的命令替换后隐式完成的(如$~var一样)。
没有引用变量的情况有时是不希望出现的副作用是空值去除。 echo $(cmd)的行为类似于通过完全禁用glob(使用zsh)和拆分(使用set -f)可以在其他shell中实现的功能。仍然在以下位置:
cmd $var

没有split + glob,但是如果IFS=''为空,则$var不会接收任何参数,而不是接收到一个空参数。
这可能会导致错误(例如明显的cmd)。这可能会破坏脚本的预期和假设并导致漏洞。
由于空变量可能导致某个参数被删除,这意味着下一个参数可能在错误的上下文中被解释。
例如,
printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2

如果[ -n $var ]为空,则$attacker_supplied1将被解释为算术表达式(对于$attacker_supplied2),而不是字符串(对于%d),并且在算术表达式中使用的所有未过滤数据都是命令注入漏洞
$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>

$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>

%s任意命令已运行。
什么时候需要分割怎么办+ glob运算符?
是的,通常是在您确实希望不使用变量的情况下。但是随后您需要确保在使用前正确调整了split和glob运算符。如果只希望分割部分而不是glob部分(大多数情况下是这种情况),那么您确实需要禁用globbing(uname / set -o noglob)并修复set -f。否则,还会导致漏洞(例如上述的David Korn的CGI示例)。
结论
简而言之,可以在shell中不引用变量(或命令替换或
算术扩展)确实非常危险
,尤其是在错误的上下文中进行操作时,并且很难
弄清哪些是错误的上下文。
这就是为什么将其视为不良做法的原因之一。
感谢您到目前为止的阅读。如果它超过您的头,请
担心。不能指望每个人都能理解以自己的方式编写代码的所有含义。这就是为什么我们有
良好做法建议的原因,因此可以遵循它们而不必
一定要理解为什么。
(如果还不那么清楚,请避免在其中编写
安全敏感代码贝壳)。
并且请在此站点的答案中引用您的变量!

¹在$IFSksh93及其衍生版本中,除非禁用了glolobing,否则还将执行大括号扩展(对于ksh93u +以上的pdksh版本,即使ksh93选项已禁用)。
²在braceexpandksh93中,算术扩展还可以包括yash1,21e+66inf之类的东西。 nan中还有更多功能,其中包括zsh,它是带有#的glob运算符,但即使在extendedglob仿真中,zsh也不会在算术扩展时进行split + glob

评论


请注意,使用[[时,只需引用比较的RHS:if [[$ 1 =“ $ 2”]];然后

– mirabilos
2014年12月9日14:09

@mirabilos,是的,但是不必在LHS上加引号,因此没有令人信服的理由不在此处加引号(如果我们有意识地决定默认情况下使用引号,因为这似乎是最明智的选择)。还要注意,如果$ IFS的第一个字符不是带bash的空格,则[[$ * =“ $ var”]]与[[“ $ *” =“ $ var”]]不相同(如果$与$ IFS是空的,尽管在那种情况下我不确定$ *等于什么,我应该提出一个错误吗?))。

–StéphaneChazelas
2014年12月9日14:22



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

– mirabilos
2014年12月9日15:20

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

–StéphaneChazelas
2014-12-10 22:48



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

–乔
2014-12-16 6:33



#2 楼

[灵感来自cas。]

但是,如果……怎么办?


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

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


if some_condition
then
    ignorecase="-i"
else
    ignorecase=""
fi
                                        # Note that the quotes in the above commands are not strictly needed.
grep  $ignorecase  other_grep_args


我不能说grep "$ignorecase" other_grep_args
如果$ignorecase为空字符串,该操作将失败。 br />
响应:

如另一个答案中所述,
如果IFS包含-i,这仍然会失败。
如果确保IFS的变量中不包含任何字符
(并且确保变量中不包含任何glob字符),则
这可能是安全的。

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


${parameter:+[word]}

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


,这里的窍门是
,因为我们将ignorecase用作parameter
,将"$ignorecase"用作word
${ignorecase:+"$ignorecase"}的意思是


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


这会将我们带到我们要去的地方:
如果变量设置为空字符串,则
将被“删除”(整个复杂的表达式
将不会得到任何结果-甚至不是一个空字符串),
如果变量具有非空值,我们将得到该值并用引号引起来。 br />

但是如果...呢?


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

例如,


if some_condition
then
    criteria="-type f"
else
    criteria=""
fi
if some_other_condition
then
    criteria="$criteria -mtime +42"
fi
find "$start_directory"  $criteria  other_find_args


响应:

您可能会认为这是使用eval的情况。
不!
在这里甚至不想考虑使用eval的诱惑。

再次,如果您确保IFS变量中不包含任何字符
(除了对于要兑现的空格),
,请确保您的变量不包含任何glob字符,
那么以上内容可能是安全的。

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

if some_condition
then
    criteria=(-type f)  # You could say `criteria=("-type" "f")`, but it’s really unnecessary.
                        # But do not say `criteria=("-type f")`.
else
    criteria=()         # Do not use any quotes on this command!
fi
if some_other_condition
then
    criteria+=(-mtime +42)      # Note: not `=`, but `+=`, to add (append) to an array.
fi
find "$start_directory"  "${criteria[@]}"  other_find_args


在bash(1)中,可以使用${name[subscript]}引用数组的任何元素

...如果subscript@*
该词将扩展到name的所有成员。
仅当单词出现在双引号中时,这些下标才有所不同。
如果单词被双引号,则…${name[@]}
name的每个元素扩展为一个单独的单词。


所以"${criteria[@]}"扩展为(在上面的示例中)
引用criteria数组的零个,两个或四个元素。
特别是,如果两个条件都不成立,则
criteria数组不包含任何内容
(由criteria=()语句设置),
,并且"${criteria[@]}"的结果为空
(不甚至是一个不方便的空字符串)。


当您处理多个单词时,这会变得特别有趣和复杂

其中一些是动态(用户)输入,您可能事先不知道
,并且其中可能包含空格或其他特殊字符。
考虑:

printf "Enter file name to look for: "
read fname
if [ "$fname" != "" ]
then
    criteria+=(-name "$fname")
fi


请注意,每次使用$fname时都被引用。
即使用户输入了诸如foo barfoo*之类的内容,该方法仍然有效;
"${criteria[@]}"评估为-name "foo bar"-name "foo*"
(请记住,数组的每个元素都用引号引起来。)

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

if some_condition
then
    set -- -type f      # You could say `set -- "-type" "f"`, but it’s really unnecessary.
else
    set --
fi
if some_other_condition
then
    set -- "$@" -mtime +42
fi
# Similarly:    set -- "$@" -name "$fname"
find "$start_directory"  "$@"  other_find_args


"$@"构造(历史上第一个出现)
"${name[@]}"具有相同的语义—
将每个参数(即参数列表的每个元素)扩展为一个单独的单词,就像您键入了"" "" "" …一样。

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


@

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


全文有点晦涩难懂;
关键是它指定
"$@"将在没有位置的情况下生成零字段
参数。
历史注释:1979年在bourne外壳(bash的前身)中首次引入"$@"时,
有一个错误,即"$@"被单个空字符串替换了。
没有位置参数;
请参阅${1+"$@"}在shell脚本中是什么意思,
"$@"有什么不同?,
传统的Bourne Shell系列,
${1+"$@"}是什么意思的意思是...以及在什么地方需要?,
"$@"${1+"$@"}


数组也可以解决第一种情况:

if some_condition
then
    ignorecase=(-i)     # You could say `ignorecase=("-i")`, but it’s really unnecessary.
else
    ignorecase=()       # Do not use any quotes on this command!
fi
grep  "${ignorecase[@]}"  other_grep_args


____________________

PS (csh)

这应该不用多说,但是,为了新手在这里的利益:
csh,tcsh等不是Bourne / POSIX外壳。
他们是一个完全不同的家庭。
不同颜色的马。
还有其他一场比赛。
另一种猫。
另一只羽毛的鸟。
,尤其是另一种蠕虫。

本页中的某些内容适用于csh;
例如:引用所有变量
,除非您有充分的理由不这样做,
您确定自己知道自己在做什么。
但是,在csh中,每个变量都是一个数组—
碰巧几乎每个变量
都是一个只有一个元素的数组,并且行为与普通变量非常相似
Bourne / POSIX shell中的shell变量。
语法完全不同(我的意思是非常糟糕)。
所以我们在这里不多讲csh-family shells。

评论


请注意,在csh中,您宁愿使用$ var:q而不是“ $ var”,因为后者不适用于包含换行符的变量(或者如果您想单独引用数组的元素,而不是联接它们)并在单个参数中添加空格)。

–StéphaneChazelas
18年2月8日在16:38

在bash中,bitrate =“-bitrate 320”与$ {bitrate:+ $ bitrate}一起使用,而bitrate =-bitrate 128将无法使用$ {bitrate:+“ $ bitrate”},因为它会破坏命令。使用$ {variable:+ $ variable}且不带“”是否安全?

– Freedo
19年6月7日在7:14



@Freedo:我发现您的评论不清楚。我特别不清楚您想知道Ihaven尚未说过什么。请参阅第二个“ Butwhatif”标题-“但是,如果我拥有将想要/需要分解成单词的变量,该怎么办?”就是这种情况,您应该按照那里的建议进行操作。但是,如果不能(例如,因为您使用的不是bash,ksh,zsh或yash之外的其他shell,而您使用$ @则是其他原因),或者您只是出于其他原因而拒绝使用数组,我建议您“其他答案中已讨论过”。 …(续)

– G-Man说“恢复莫妮卡”
19年6月7日在19:15



(续)…if如果IFS包含-,0、2、3,a,b,e,i,r ort,则您的建议(使用不带“的$ {variable:+ $ variable})将失败。

– G-Man说“恢复莫妮卡”
19年6月7日在19:15



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

– Freedo
19年6月8日在9:57

#3 楼

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

 $ set `seq 101`

$ IFS=0

$ echo $#
1 1
 


或$ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1


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