dr_ Asked: 2024-08-28 21:08:35 +0800 CST2024-08-28 21:08:35 +0800 CST 2024-08-28 21:08:35 +0800 CST 算术扩展中的“$var”和“var”有什么区别? 772 Bash 接受以下两种语法: FOO=$(($BAR + 42)) 和 FOO=$((BAR + 42)) 哪一个是正确的/最可移植的/不容易出错?还是两者都同样有效? bash 1 个回答 Voted Best Answer ilkkachu 2024-08-28T21:29:11+08:002024-08-28T21:29:11+08:00 这取决于您想做什么,但您应该首先验证算术环境中使用的任何输入(见结尾)。 参数扩展(例如$var和命令替换)是在解析算术表达式本身之前完成的。(*) 该扩展与算术语法无关,因此$var您可以随意处理它。 另一方面,不带$符号的var是在解析算术表达式后进行求值的,并且必须是可以求值的东西。但在 Bash 中,它可以是一个完整的表达式(**)。 (* 除了一些 shell 有例外,这样$(( a[$key] = 1 ))即使key是也能使类似的操作有效]。) (** 大多数其他 shell 也允许在变量中使用完整的表达式,但 Debian 和 Ubuntu 上的 Dash 除外/bin/sh。) 因此,例如$var如果变量为空,使用会产生语法错误,而使用var它将被视为零。 $ echo $((10 - var)) 10 $ echo $((10 - $var)) bash: 10 - : syntax error: operand expected (error token is "- ") 10 - $var产生的只是10 -无效的表达式。(这不会发生在您的示例中,$BAR + 42因为+ 42是有效表达式。) 另一方面,您可以使用$var包含部分表达式: $ op=+; echo $(( 1 $op 2 )) 3 使用var,您只能包含完整的子表达式(在 Bash 中): $ var=1+2 $ echo $(( 10 * var )) 30 但请注意,$var在这里使用会改变评估的顺序! $ var=1+2 $ echo $(( 10 * $var )) 12 这里,首先扩展变量,得到10 * 1+2,然后使用正常算术规则进行解析,先乘法,与相同(10 * 1) + 2。 $(( 10 * var ))没有美元符号实际上会在变量周围放置隐式括号,因此有点像$(( 10 * (1+2) ))。 作为将变量解释为完整表达式的一部分,如果一个变量命名了另一个变量,Bash 将追踪名称链: $ one=two two=three three=four four=4; echo $(( one )) 4 没有美元符号的形式仅适用于实际命名的变量。如果您想使用位置参数$1、$2...或算术中的特殊变量(如$#或)$?,则必须使用美元形式。(这应该是相当明显的,但仍然如此。) 另请参阅Bash:算术扩展、参数扩展和逗号运算符,了解另一个有趣的区别。 虽然你应该像往常一样引用扩展,但你不需要在算术表达式中引用变量扩展,这样做实际上会破坏许多 shell。所以,请使用"$(( ...))",但不要$(( "$foo" ))(例如 zsh 在 上发出i=123; echo $(( "$i" ))) 无论如何,请注意,无论哪种情况,Bash 的算术评估都容易受到简单的命令注入攻击,如下所示: $ var='a[$(date >&2)]' $ echo $(( $var * 10 )) Wed Aug 28 16:25:57 EEST 2024 0 $ echo $(( var * 10 )) Wed Aug 28 16:26:49 EEST 2024 0 这意味着如果你从外部获取值,在算术上下文中使用它们之前,你应该先验证它们。如果在验证之后你知道你得到的是一个简单的数字,那么使用哪种形式就无关紧要了。 (这在 Dash 中不会发生,因为它不支持数组,而 zsh 似乎不受此影响。)
这取决于您想做什么,但您应该首先验证算术环境中使用的任何输入(见结尾)。
参数扩展(例如
$var
和命令替换)是在解析算术表达式本身之前完成的。(*) 该扩展与算术语法无关,因此$var
您可以随意处理它。另一方面,不带
$
符号的var
是在解析算术表达式后进行求值的,并且必须是可以求值的东西。但在 Bash 中,它可以是一个完整的表达式(**)。(* 除了一些 shell 有例外,这样
$(( a[$key] = 1 ))
即使key
是也能使类似的操作有效]
。)(** 大多数其他 shell 也允许在变量中使用完整的表达式,但 Debian 和 Ubuntu 上的 Dash 除外
/bin/sh
。)因此,例如
$var
如果变量为空,使用会产生语法错误,而使用var
它将被视为零。10 - $var
产生的只是10 -
无效的表达式。(这不会发生在您的示例中,$BAR + 42
因为+ 42
是有效表达式。)另一方面,您可以使用
$var
包含部分表达式:使用
var
,您只能包含完整的子表达式(在 Bash 中):但请注意,
$var
在这里使用会改变评估的顺序!这里,首先扩展变量,得到
10 * 1+2
,然后使用正常算术规则进行解析,先乘法,与相同(10 * 1) + 2
。$(( 10 * var ))
没有美元符号实际上会在变量周围放置隐式括号,因此有点像$(( 10 * (1+2) ))
。作为将变量解释为完整表达式的一部分,如果一个变量命名了另一个变量,Bash 将追踪名称链:
没有美元符号的形式仅适用于实际命名的变量。如果您想使用位置参数
$1
、$2
...或算术中的特殊变量(如$#
或)$?
,则必须使用美元形式。(这应该是相当明显的,但仍然如此。)另请参阅Bash:算术扩展、参数扩展和逗号运算符,了解另一个有趣的区别。
虽然你应该像往常一样引用扩展,但你不需要在算术表达式中引用变量扩展,这样做实际上会破坏许多 shell。所以,请使用
"$(( ...))"
,但不要$(( "$foo" ))
(例如 zsh 在 上发出i=123; echo $(( "$i" ))
)无论如何,请注意,无论哪种情况,Bash 的算术评估都容易受到简单的命令注入攻击,如下所示:
这意味着如果你从外部获取值,在算术上下文中使用它们之前,你应该先验证它们。如果在验证之后你知道你得到的是一个简单的数字,那么使用哪种形式就无关紧要了。
(这在 Dash 中不会发生,因为它不支持数组,而 zsh 似乎不受此影响。)