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

Sté*_*las 241 shell security bash shell-script quoting

如果您已经关注 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 的作者肯定没有打算让我们引用所有变量

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

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

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

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

Sté*_*las 251

前言

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

同样,您不引用您的变量,因为否则您将引入安全漏洞。你引用你的变量是因为不引用是错误的(但如果对监狱的恐惧可以提供帮助,为什么不呢)。

给刚跳上火车的人一个小总结。

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

cmd $var
Run Code Online (Sandbox Code Playgroud)

用另一种语言写成:

cmd(glob(split($var)))
Run Code Online (Sandbox Code Playgroud)

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

例如,如果$varcontains*.txt,/var/*.xml$IFS contains ,cmd将使用多个参数调用,第一个是cmd,下一个是txt 当前目录中的xml文件和/var.

如果您只想cmd使用两个字面参数cmd and进行调用*.txt,/var/*.xml,您可以这样写:

cmd "$var"
Run Code Online (Sandbox Code Playgroud)

这将是您更熟悉的其他语言:

cmd($var)
Run Code Online (Sandbox Code Playgroud)

我们所说的 shell 中漏洞是什么意思?

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

好吧,尽管事实上有人会告诉你 shell 脚本永远不应该用于 Web CGI,或者谢天谢地,现在大多数系统不允许 setuid/setgid shell 脚本,shellshock(可远程利用的 bash 错误导致在2014年9月)的头条新闻透露的是,炮弹还在广泛使用,他们可能是不应该:在CGI中,在DHCP客户端钩子脚本,在sudoers中命令,调用(如果不作为)的setuid命令...

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

当存在特权提升路径时,您就会遇到漏洞,即当某人(让我们称他为攻击者)能够做他不应该做的事情时。

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

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

现在,这些数据可能来自哪里并不总是很明显,而且通常很难判断您的代码是否会处理不受信任的数据。

就变量而言,在 CGI 脚本的情况下,很明显,数据是 CGI GET/POST 参数以及诸如 cookie、路径、主机...参数之类的东西。

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

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

在这方面,即使在提示符下交互的shell,你可能会受到(在处理文件时/tmp~/tmp 为实例)。

甚至 a~/.bashrc也可能是脆弱的(例如,bash当调用它ssh以在客户端控制下的某些变量的服务器部署中运行ForcedCommand 类似时,将解释它git)。

现在,可能不会直接调用脚本来处理不受信任的数据,但它可能会被另一个执行的命令调用。或者您不正确的代码可能会被复制粘贴到执行此操作的脚本中(由您或您的一位同事使用 3 年)。一个特别重要的地方是问答网站中的答案,因为您永远不知道您的代码副本可能会在哪里结束。

正事;有多糟糕?

到目前为止,不加引号的变量(或命令替换)是与 shell 代码相关的安全漏洞的第一大来源。部分原因是这些错误通常会转化为漏洞,而且还因为看到未引用的变量非常普遍。

实际上,在查找 shell 代码中的漏洞时,首先要做的是查找未加引号的变量。它很容易被发现,通常是一个很好的候选者,通常很容易追溯到攻击者控制的数据。

未加引号的变量可以通过多种方式变成漏洞。我在这里只给出一些常见的趋势。

信息披露

由于拆分部分,大多数人会遇到与未加引号的变量相关的错误(例如,现在文件名称中有空格是很常见的,而空格是 IFS 的默认值)。许多人会忽略 glob部分。的水珠部分至少一样危险的 分裂部分。

对未经处理的外部输入进行通配意味着攻击者可以让您阅读任何目录的内容。

在:

echo You entered: $unsanitised_external_input
Run Code Online (Sandbox Code Playgroud)

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

拒绝服务漏洞。

前一个案例有点过头了,我们有一个 DoS。

实际上,列表上下文中任何未加引号且输入未经处理的变量至少是 DoS 漏洞。

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

#! /bin/sh -
: ${QUERYSTRING=$1}
Run Code Online (Sandbox Code Playgroud)

:是无操作命令。什么可能出错?

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

$QUERYSTRING仍然是扩展的,并且因为它没有被引用,所以调用了split+glob操作符。

现在,有些 glob 的扩展成本特别高。在/*/*/*/*一个已经够糟糕的,因为它意味着列出目录多达4倍的水平下降。除了磁盘和 CPU 活动之外,这意味着存储数以万计的文件路径(在最小的服务器 VM 上为 40k,其中 10k 为目录)。

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

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

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Run Code Online (Sandbox Code Playgroud)

当然,如果代码是:

echo $QUERYSTRING > /some/file
Run Code Online (Sandbox Code Playgroud)

然后你可以填满磁盘。

只需对shell cgibash cgiksh cgi进行谷歌搜索,您就会找到几页向您展示如何在 shell 中编写 CGI。请注意其中一半的过程参数是脆弱的。

即使是David Korn 自己的也 很脆弱(看看 cookie 处理)。

高达任意代码执行漏洞

任意代码执行是最糟糕的漏洞类型,因为如果攻击者可以运行任何命令,他可以做的事情就没有限制。

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

awk -v foo=$external_input '$2 == foo'
Run Code Online (Sandbox Code Playgroud)

这里的目的是将$external_inputshell 变量的内容分配给 foo awk变量。

现在:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Run Code Online (Sandbox Code Playgroud)

分裂后的第二个字$external_input 没有分配给foo而是被视为awk代码(这里执行任意命令:)uname

对于可以执行其他命令 ( awk, env, sed(GNU one), perl, find...) 的命令来说,这尤其是一个问题,尤其是对于 GNU 变体(在参数后接受选项)。有时,您不会怀疑命令能够执行其他命令,例如ksh,bashzsh's[printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done
Run Code Online (Sandbox Code Playgroud)

如果我们创建一个名为 的目录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
Run Code Online (Sandbox Code Playgroud)

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

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

各种事情都可以用文件名来完成。

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
Run Code Online (Sandbox Code Playgroud)

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

在公共可写区域中对文件进行自动处理的脚本/tmp必须非常小心地编写。

关于什么 [ $# -gt 1 ]

这是我觉得令人恼火的事情。有些人不厌其烦地想知道某个特定的扩展是否有问题,以决定是否可以省略引号。

就像在说。嘿嘿,好像$#不能用 split+glob 操作符,让 shell 来 split+glob it 吧。或者嘿,让我们写错误的代码只是因为这个错误不太可能被击中

现在可能性有多大?好的,$#(或$!$?或任何算术替换)可能只包含数字(或-对于 some²),因此glob部分已出局。不过,对于拆分部分要做的事情,我们所需要的只是$IFS包含数字(或-)。

使用一些shell,$IFS可能会从环境中继承,但是如果环境不安全,无论如何游戏都结束了。

现在,如果你编写一个函数,如:

my_function() {
  [ $# -eq 2 ] || return
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...
Run Code Online (Sandbox Code Playgroud)

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

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

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

例如:

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

并且使用$1with value (IFS=-1234567890),该算术评估具有设置 IFS 的副作用,并且下一个[ 命令失败,这意味着绕过了对太多 args的检查。

什么时候分+水珠,则不会调用操作?

还有另一种情况,变量和其他扩展需要引号:当它用作模式时。

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

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

这意味着[[ $a = $b ]]$a不同于$b(例如当$aisanything$bis *)的情况下可能返回 true或者当它们相同时可能返回 false (例如当两者$a$bare 时[a])。

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

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi
Run Code Online (Sandbox Code Playgroud)

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

现在,如果该模式匹配和split+glob运算符都不适用,那么不加引号的变量有什么危险?

我不得不承认我确实写过:

a=$b
case $a in...
Run Code Online (Sandbox Code Playgroud)

在那里,引用没有害处,但不是绝对必要的。

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

例如,他们可能会开始认为如果没问题a=$b,那么export a=$b也可以(它在许多 shell 中并不像在export命令的参数中那样,所以在列表上下文中)或env a=$b.

怎么样zsh

zsh确实解决了大部分设计尴尬。在zsh(至少在不处于 sh/ksh 仿真模式时),如果您想要splittingglobbingpattern matching,您必须明确请求它:$=var拆分,和$~varglob 或变量的内容被视为一种模式。

但是,拆分(但不是通配)仍然在未加引号的命令替换(如 中echo $(cmd))时隐式完成。

此外,有时不引用变量的不良副作用是清空删除。该zsh行为类似于您在其他 shell 中可以通过完全禁用通配 (with set -f) 和拆分 (with IFS='') 实现的行为。还在:

cmd $var
Run Code Online (Sandbox Code Playgroud)

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

这可能会导致错误(如明显的[ -n $var ])。这可能会破坏脚本的预期和假设并导致漏洞。

由于空变量可能会导致一个参数被移除,这意味着下一个参数可能会在错误的上下文中被解释。

举个例子,

printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
Run Code Online (Sandbox Code Playgroud)

如果$attacker_supplied1为空,$attacker_supplied2则将被解释为算术表达式 (for %d) 而不是字符串 (for %s),并且算术表达式中使用的任何未经处理的数据都是类似 Korn 的 shell 中的命令注入漏洞,例如 zsh

$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>
Run Code Online (Sandbox Code Playgroud)

很好,但是:

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

uname 任意命令已运行。

当您确实需要split+glob运算符时呢?

是的,这通常是您确实希望不带引号的变量。但是,您需要确保在使用之前正确调整splitglob运算符。如果您只想要拆分部分而不是glob部分(大多数情况下都是这种情况),那么您确实需要禁用 globbing ( set -o noglob/ set -f) 并修复$IFS. 否则你也会导致漏洞(就像上面提到的 David Korn 的 CGI 示例)。

结论

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

这就是为什么它被认为是不好的做法的原因之一。

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

(如果这还不明显,请避免在 shell 中编写安全敏感代码)。

请在本网站上的答案中引用您的变量!


¹在ksh93andpdksh和衍生品中,除非禁用通配符,否则也会执行大括号扩展ksh93对于 ksh93u+及以下的版本,即使该braceexpand选项被禁用)。

² 在ksh93and 中yash,算术扩展还可以包括诸如1,2, 1e+66, inf, 之类的东西nan。在 中还有更多zsh,包括#哪个是 glob 运算符extendedglob,但zsh在算术扩展时从不进行 split+glob,即使在sh仿真中

  • @Barmar,假设你的意思是 `foo='bar; rm *'`,不,不会,但是会列出当前目录的内容,这可能算作信息泄露。`ksh93` 中的 `print $foo`(其中 `print` 是 `echo` 的替代品,解决了它的一些缺点)确实存在代码注入漏洞(例如使用 `foo='-f%.0d z[ 0$(uname&gt;&amp;2)]'`)(你实际上需要 `print -r -- "$foo"`。`echo "$foo"` 仍然是错误的并且无法修复(尽管通常危害较小))。 (3认同)
  • 我不是 bash 专家,但我已经用它编码了十多年。我经常使用引号,但主要是为了处理嵌入的空格。现在,我会更多地使用它们!如果有人能扩展这个答案,让它更容易吸收所有的要点,那就太好了。我得到了很多,但我也错过了很多。这篇文章已经很长了,但我知道这里还有很多东西要我学习。谢谢! (3认同)
  • @mirabilos,是的,但是 LHS 不需要 **not** 被引用,所以没有令人信服的理由不在那里引用它(如果我们有意识地决定_引用默认_,因为它似乎是最做明智的事情)。另请注意,如果 `$IFS` 的第一个字符不是空格,则 `[[ $* = "$var" ]]` 与 `[[ "$*" = "$var" ]]` 不同bash`(如果 `$IFS` 为空,还有 `mksh`,但在这种情况下,我不确定 `$*` 等于什么,我应该提出错误吗?))。 (2认同)

G-M*_*ca' 41

[灵感来自此答案中科院]

但是如果……怎么办?

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

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

如果some_condition
然后
    忽略大小写=“-i”
别的
    忽略大小写=""
菲
                                        # 注意上面命令中的引号并不是严格需要的。
grep $ignorecase   other_ grep _args

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

回复:

正如在另一个答案中所讨论的,如果IFS包含 a-或 an ,这仍然会失败i。如果您确保IFS变量中不包含任何字符(并且您确定您的变量不包含任何全局字符),那么这可能是安全的。

但是有一种更安全的方法(尽管它有点丑陋且非常不直观):使用${ignorecase:+"$ignorecase"}. 来自POSIX Shell 命令语言规范,在 2.6.2 Parameter Expansion 下

${parameter:+[word]}

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

这里的诀窍,就像它一样,是我们使用ignorecaseasparameter"$ignorecase"as word。所以${ignorecase:+"$ignorecase"}意味着

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

这得到我们,我们想去的地方:如果变量设置为空字符串,这将是“删除”(这整个的,令人费解表达式会到什么-甚至不是一个空字符串),如果变量具有非-空值,我们得到那个值,引用。


但是如果……怎么办?

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

例如,

如果some_condition
然后
    标准=“-类型f”
别的
    标准=""
菲
如果some_other_condition
然后
    标准="$标准-mtime +42"
菲
找到“$start_directory” $criteria   other_ find _args

回复:

您可能认为这是使用eval.  不!  抵制甚至考虑使用eval这里的诱惑。

同样,如果您确保IFS变量中不包含任何字符(除了空格,您希望得到尊重),并且您确定您的变量不包含任何 glob 字符,那么上面的可能是安全的。

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

如果some_condition
然后
    criteria=(-type f) # 你可以说`criteria=("-type" "f")`,但这真的没有必要。
                        # 但不要说`criteria=("-type f")`。
别的
    criteria=() #不要在这个命令上使用任何引号!
菲
如果some_other_condition
然后
    标准+=(-mtime +42) # 注意:不是`=`,而是` + =`,用于添加(追加)到数组中。
菲
find "$start_directory" "${criteria[@]}"   other_ find _args

bash(1)

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

所以"${criteria[@]}"扩展到(在上面的例子中)criteria数组的零、两个或四个元素,每个元素都被引用。特别是,如果条件?s? 都不为真,则criteria数组没有内容(由criteria=()语句设置),并且"${criteria[@]}"计算结果为零 (甚至不是不方便的空字符串)。


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

printf "输入要查找的文件名:"
读取文件名
如果 [ "$fname" != "" ]
然后
    标准+=(-name "$fname")
菲

请注意,每次使用$fname都会引用它。即使用户输入类似或的内容,这也有效;  评估为或。(请记住,数组的每个元素都被引用。)foo barfoo*"${criteria[@]}"-name "foo bar"-name "foo*"

数组不适用于所有 POSIX shell;数组是 ksh?/?bash?/?zsh?/?yash-ism。除了……有一个所有 shell 都支持的数组:参数列表,又名"$@". 如果您完成了调用时使用的参数列表(例如,您已将所有“位置参数”?(参数)复制到变量中,或以其他方式处理它们),则可以将 arg 列表用作数组:

如果some_condition
然后
    set -- -type f # 你可以说`set -- "-type" "f"`,但这真的没必要。
别的
    放  - 
菲
如果some_other_condition
然后
    设置 -- "$@" -mtime +42
菲
# 同样: set -- "$@" -name "$fname"
find "$start_directory" "$@"   other_ find _args

"$@"构造(从历史上看,首先出现)具有相同的语义- 它将每个参数(即参数列表的每个元素)扩展为一个单独的单词,就好像您输入了。"${name[@]}""$1" "$2" "$3" …

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

@

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

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


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

如果some_condition
然后
    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。


Ste*_*nny 11

我对 Stéphane 的回答持怀疑态度,但有可能滥用$#

$ set `seq 101`

$ IFS=0

$ echo $#
1 1
Run Code Online (Sandbox Code Playgroud)

或 $?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1
Run Code Online (Sandbox Code Playgroud)

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


归档时间:

查看次数:

34702 次

最近记录:

4 年,10 月 前