![为什么 shell 脚本中有三种设置变量的语法?](https://linux22.com/image/67146/%E4%B8%BA%E4%BB%80%E4%B9%88%20shell%20%E8%84%9A%E6%9C%AC%E4%B8%AD%E6%9C%89%E4%B8%89%E7%A7%8D%E8%AE%BE%E7%BD%AE%E5%8F%98%E9%87%8F%E7%9A%84%E8%AF%AD%E6%B3%95%EF%BC%9F.png)
root@ubuntu:~# echo ${x=1}
1
root@ubuntu:~# echo ${x:=1}
1
root@ubuntu:~# echo ${x-1}
1
root@ubuntu:~#
为什么 shell 脚本中有三种设置变量的语法?
拥有三种语法的技术优势是什么?
即使是编程语言也不会超过一种。
答案1
它们做的事情略有不同。事实上,最后一个echo ${x-1}
实际上并没有设置X但仅在以下情况下才替换表达式中的值 1X未设置。
x=1
另一方面设置X无条件地。
至于 := 运算符。这是来自克什手动的:
${parameter:=word}
If parameter is not set or is null then set it to word; the
value of the parameter is then substituted. Positional parameters may
not be assigned to in this way.
在 Ruby 中,这就像||=
.
如果你看看这个家庭X= 运算符还有很多,:-
这是我遇到过的最流行的。那个替代品如果尚未设置变量,则将变量设置为默认值。所以你可以这样使用它:
x=${1:-10}
其英文为:如果 $1 未设置,则将 10 分配给X,否则分配给X1 美元的价值。在函数中,这具有为参数分配默认值的效果。所以
f() {
typeset x
x=${1-10}
...
}
相当于Python:
def f(x=10):
我认为大卫·科恩(David Korn)会承认运营商的多样性可能有些过分,但它现在已经成为了POSIX 标准,第 2.6.2 节所以它可能会留在那里。
即使是编程语言也不会超过一种。
正如上面提到的,有一个误解,认为这些是确切地相同。在编程语言中,赋值语句的变体并不罕见。我||=
在 Ruby 中提到过。许多语言都有+=
,-=
等等。在 Perl 中,如果你运行$x 之前没有被定义$x += 1
,它将设置$x
为 1(而且,如果你没有启用“严格”检查,那真是太遗憾了)。
答案2
我认为“为什么有这么多 shell 语法来分配变量?”这个问题的唯一答案。是“因为他们所做的事情都略有不同。”
在shell编程语言中,$x
或者${x}
是扩展到名为 的 shell 变量的值x
。换句话说,当 shell 解释包含 的行时$x
,它会替换$x
为变量的值:
$ x=me; filepath=/home/$x
$ echo $filepath
/home/me
shell 还实现了各种条件扩展,包括具有副作用的扩展:
条件作业
在替换之前有条件地进行分配:
${x=1}
x
如果之前未设置,则设置为 1。${x:=1}
x
如果之前未设置或设置为空字符串,则设置为 1。
例子:
$ unset x
$ echo Hello, "${x=world}"
Hello, world
$ echo "$x"
world
$ # This sets x to the empty string
$ x=
$ echo Hello, "${x=world}"
Hello,
$ echo "$x"
$ echo Hello, "${x:=world}"
Hello, world
$ echo "$x"
world
条件扩展
扩展是变量的值或某个固定字符串。变量是不是改变了。
${x-1}
展开为${x}
ifx
已设置。否则,扩展为1
.${x:-1}
${x}
如果x
已设置且不为空,则扩展为。否则,扩展为1
.${x+1}
展开为1
ifx
已设置。否则,扩展为空字符串。${x:+1}
1
如果x
已设置且不为空,则扩展为。否则,扩展为空字符串。
例子:
$ unset x
$ echo Hello, "${x-world}"
Hello, world
$ echo "$x"
$ # This sets x to the empty string
$ x=
$ echo Hello, "${x-world}"
Hello,
$ echo Hello, "${x:-world}"
Hello, world
$ echo "$x"
条件错误
${x?error message}
x
如果未设置,则失败并打印错误消息。${x:?error message}
x
如果未设置或设置为空字符串,则会失败并打印错误消息。
如果失败,则不会执行发生扩展的命令。如果 shell 不是交互式的,那么 shell 本身将退出。
冒号
在所有条件语法中,可以看出使用 a:使未设置的变量和设置为空字符串的变量以相同的方式起作用。这通常是个好主意,因为有时取消设置变量并不容易。例如,在上面显示的局部赋值语法中,将子变量分配给空字符串很容易,但从子环境中删除变量却不容易:
$ export foo=Goodbye
$ foo= dash -c 'echo "${foo-Hello}", world'
, world
$ foo= dash -c 'echo "${foo:-Hello}", world'
Hello, world
$ echo "$foo"
Goodbye
作为命令的条件扩展
有时,仅能够执行条件扩展以消除其副作用,而不是在命令中实际使用它是很方便的。从技术上讲,这是不可能的,但您可以使用特殊的内置命令:
,它不执行任何操作:
# Equivalent to:
# if [ -z "$x" ]; then x="default value"; fi
: "${x:=default value}"
# Equivalent to:
# if [ $# -lt 3 ]; then
# echo "Three arguments are required" 1>&2
# exit 1
# fi
: "${3?Three arguments are required.}"
变量赋值
shell 提供了一种简单的语法来为标量变量赋值:
var=value
如果命令仅包含赋值,则赋值将成为 shell 环境的一部分。但是,如果分配是命令行的一部分,位于实用程序/脚本名称之前,则分配将应用于孩子子进程使用的环境:
$ foo=Hello; echo "$foo, world"
Hello, world
$ echo "$foo"
Hello
$ unset foo
$ foo=Hello echo "$foo, world"
, world
$ echo "$foo"
在命令foo=Hello echo "$foo, world", the assignment is performed in the environment passed to
echo . However, the argument
"$foo, world" is evaluated in the main shell environment, in which
$foo has no value. Furthermore, when the child process (
echo , in this case) terminates, its environment is lost, so the assignment was useless. Contrast that with the case where the child process actually uses the variable: (
dash is a Posix-compatible shell; I'm just using it here for clarity.
bash` 中也会以同样的方式工作。)
$ unset foo
$ foo=Hello dash -c 'echo "$foo, world"'
Hello, world
$ echo "$foo"
扩展赋值语法
一些 shell(例如 bash、ksh 和 zsh)也提供语法
var+=value
这增加了value
变量。 “添加”的含义取决于变量的类型var
: 对于普通字符串变量,value
附加到 的末尾var
;而对于声明为整数的变量,value
则(以数学方式)相加。在 ksh、zsh 和某些版本的 bash 中,此赋值语法可作为命令的一部分用于子赋值。有关如何将变量声明为数字的详细信息,请参阅各个 shell 手册。 (这是不是足以简单地为变量分配一个数字。)
这些 shell 还具有数组变量,这些变量使用以下语法进行分配和附加:
array=(value1 value2 ...)
array+=(value3 ...)
上述赋值中的括号是语法性的;您可以将左括号视为赋值标记的一部分。数组赋值不能用作简单命令的一部分,因为数组无法导出到子环境。
某些 shell 具有关联数组,支持索引分配。有关关联数组的详细信息,包括如何声明它们,请参阅各个 shell 手册。
算术评估作业
在算术求值中,可以使用类似 C 的赋值运算符(=
、*=
、等),但它们仅执行算术赋值。 +=
Posix shell 只有两个算术求值上下文:算术扩展语法$((...))
和for ((expr; expr; expr))
复合语句。 (您可以使用:
上面的特殊内置函数将算术扩展转换为语句。)
然而,大多数 shell 也允许条件语句((...))
,并且还会在需要数字的上下文中使用算术求值,例如数组下标和对声明为整数的变量进行赋值。细节有所不同;请参阅 shell 手册。
需要谨慎对待算术求值上下文,因为在算术求值上下文中,变量扩展被重新解释为算术表达式,而不仅仅是整数。在某些 shell(例如 bash)中,可以通过注入攻击来利用这一点。最佳实践是永远不要在算术求值上下文中使用不受信任的变量。