无支撑表达式的问题
将表达式字符串参数作为支撑字符串提供是一种很好的做法。 双重替代标题概述了背后的重要原因。
expr
命令评估基于运算符的表达式字符串以计算值。此字符串由调用中的参数构造。
expr 1 + 2 ; # three arguments
expr "1 + 2" ; # one argument
expr {1 + 2} ; # one argument
这三个调用是等效的,表达式字符串是相同的。
命令 if
,for
和 while
对其条件参数使用相同的求值程序代码:
if {$x > 0} ...
for ... {$x > 0} ... ...
while {$x > 0} ...
主要区别在于条件表达式字符串必须始终是单个参数。
与 Tcl 中命令调用中的每个参数一样,内容可能会也可能不会被替换,具体取决于它们如何被引用/转义:
set a 1
set b 2
expr $a + $b ; # expression string is {1 + 2}
expr "$a + $b" ; # expression string is {1 + 2}
expr \$a + \$b ; # expression string is {$a + $b}
expr {$a + $b} ; # expression string is {$a + $b}
第三和第四种情况存在差异,因为反斜杠/支撑会阻止替换。结果仍然相同,因为 expr
中的赋值器本身可以执行 Tcl 变量替换并将字符串转换为 {1 + 2}
。
set a 1
set b "+ 2"
expr $a $b ; # expression string is {1 + 2}
expr "$a $b" ; # expression string is {1 + 2}
expr {$a $b} ; # expression string is {$a $b}: FAIL!
这里我们遇到了 braced 参数的问题:当 expr
中的求值程序执行替换时,表达式字符串已经被解析为运算符和操作数,因此求值程序看到的是一个由两个操作数组成的字符串,它们之间没有运算符。 (错误消息是“missing operator at _@_ in expression "$a _@_$b"
”。)
在这种情况下,调用 expr
之前的变量替换可以防止错误。支持参数阻止了变量替换,直到表达式评估,这导致了错误。
这种情况可能会发生,最常见的情况是将要评估的表达式作为变量或参数传入。在这些情况下,除了保持参数 unbraced 以允许参数赋值器解包表达式字符串以传递给 expr
之外别无选择。
但在大多数其他情况下,支持表达并没有什么坏处,实际上可以避免很多问题。一些例子:
双重替代
set a {[exec make computer go boom]}
expr $a ; # expression string is {[exec make computer go boom]}
expr {$a} ; # expression string is {$a}
unbraced 表单将执行命令替换,这是一个以某种方式销毁计算机的命令(或加密或格式化硬盘,或你有什么)。支撑形式将执行变量替换,然后尝试(并且失败)来创建字符串“[exec make computer go boom]”。避免灾难。
无尽的循环
set i 10
while "$i > 0" {puts [incr i -1]}
这个问题影响了 for
和 while
。虽然看起来这个循环会倒计数到 0 并退出,但 while 的条件参数实际上总是 10>0
,因为这是在 while
命令被激活时被评估的参数。当参数被支撑时,它将作为 $i>0
传递给 while
命令,并且变量将在每次迭代时被替换一次。改为使用它:
while {$i > 0} {puts [incr i -1]}
总评估
set a 1
if "$a == 0 && [incr a]" {puts abc}
运行此代码后 a
的价值是多少?由于 &&
运算符仅在左操作数为真时才计算右操作数,因此该值仍应为 1.但实际上,它是 2.这是因为在表达式字符串为时,参数赋值器已经执行了所有变量和命令替换评估。改为使用它:
if {$a == 0 && [incr a]} {puts abc}
几个运算符(逻辑连接词||
和 &&
,以及条件运算符 ?:
)被定义为不评估它们的所有操作数,但它们只能在表达式字符串被支撑的情况下按设计工作。