mid*_*ite 5 shell shell-script posix
这个问题重点关注 POSIX 兼容的 shell 脚本。
通常通过以下方式递增变量:
i=3
: $(( i += 1 ))
echo "return code = $?" # return code = 0
echo "i = $i" # i = 4
Run Code Online (Sandbox Code Playgroud)
该命令是否$(( i += 1 ))
称为“副作用” :
?
(我在某处读到:
等于true
。我尝试用 or 替换:
,true
它false
有效。如果用 替换false
,则返回码为 1,正如预期的那样。)
为什么成功增加了值$i
,但赋值却不起作用,出现“副作用”?
a='four'
: a='five'
echo "return code = $?" # return code = 0
echo "a = $a" # a = four
Run Code Online (Sandbox Code Playgroud)
有时,我看到脚本在一个命令中运行多个命令。在大多数情况下,人们使用此结构来设置IFS
中的变量IFS='' read -r REPLY
。这是相同的结构,称为“副作用”吗?然而,所有副作用都a=6
在当前 shell 中产生实际影响。作业在这里进行。
a=6 b=7 c=8
echo "a = $a" # a = 6
echo "b = $b" # b = 7
echo "c = $c" # c = 8
Run Code Online (Sandbox Code Playgroud)
这种结构称为“副作用”吗?我在哪里可以找到它的记录?或者我可以在哪里了解它?我在下面的 POSIX 文档中找不到这个结构。
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09
ter*_*don 15
这:
称为空命令。您可以在以下位置找到其文档man bash
:
:[论据]
没有效果; 该命令除了扩展参数和执行任何指定的重定向之外不执行任何操作。返回状态为零。
或者,如果运行 bash shell,则使用help :
:
$ help :
:: :
Null command.
No effect; the command does nothing.
Exit Status:
Always succeeds.
Run Code Online (Sandbox Code Playgroud)
它位于 POSIX 规范中:
姓名
冒号 - null 实用程序概要:[参数...]
描述
该实用程序只能扩展命令参数。当需要命令时使用它,如 if 命令的 then 条件,但该命令不执行任何操作。
现在,您所说的副作用是上面第一句话中描述的结果:“该命令除了扩展参数之外不执行任何操作”。这:
是一个命令,因此后面的任何内容都是一个参数。这$(( ))
称为“算术扩展”,记录如下man bash
:
算术扩展
算术展开允许计算算术表达式并替换结果。
算术展开式的格式为:
Run Code Online (Sandbox Code Playgroud)$((expression))
该表达式被视为位于双引号内,但括号内的双引号不会被特殊处理。表达式中的所有标记都会经历参数和变量扩展、命令替换和引号删除。结果被视为要计算的算术表达式。算术展开式可以嵌套。
[。。.]
so 的$(( i += 1 ))
意思是“加i
一并返回结果”。然而,这需要评估、扩展,这就是为什么你看到它与 一起使用:
。由于:
将扩展其参数,: $(( i += 1 ))
因此平均值i
将增加 1。如果您尝试$(( i += 1 ))
单独运行,则首先会扩展为4
(在您的示例中),然后 shell 会尝试4
作为命令执行并返回错误:
$ $(( i += 1 ))
bash: 4: command not found
Run Code Online (Sandbox Code Playgroud)
但是,如果你运行: $(( i += 1 ))
,shell 会做两件事:首先,它将应用算术扩展并获取i
的值4
,然后它将执行: 4
这是一个空命令,因此不会返回错误,因为:
在它们之后忽略其参数已扩展:
$ : 4
$
Run Code Online (Sandbox Code Playgroud)
综上所述,我真的不知道为什么你会想要这样做,而不是更简单且 POSIX:
$ i=3
$ i=$(( i + 1 ))
$ echo "$i"
4
Run Code Online (Sandbox Code Playgroud)
或者,在 中bash
,但不在 POSIX shell 中:
$ i=3
$ (( i += 1 ))
$ echo "$i"
4
Run Code Online (Sandbox Code Playgroud)
这里的下一点是,这variable=value command
是一种不同的野兽,并且以不同的方式对待。POSIX 规范的相关部分是2.9.1 Simple Commands,其中最相关的部分是:
“简单命令”是一系列可选的变量分配和重定向,可以任意顺序,可选地后跟单词和重定向,并由控制运算符终止。
当需要执行给定的简单命令时[. 。.],以下扩展、赋值和重定向均应从命令文本的开头到结尾执行:
根据 Shell 语法规则识别为变量赋值或重定向的单词将被保存以便在步骤 3 和 4 中进行处理。
不是变量赋值或重定向的单词应该被扩展。如果扩展后仍有任何字段,则第一个字段应被视为命令名称,其余字段是命令的参数。
重定向应按照重定向中的描述执行。
每个变量赋值都应在赋值之前进行扩展,以进行波形符扩展、参数扩展、命令替换、算术扩展和引号删除。
和:
变量赋值应按如下方式执行:
[。。.]
- 如果命令名称不是特殊的内置实用程序或函数,则应为命令的执行环境导出变量分配[...]。
[。。.]
如果命令名称是特殊的内置实用程序,则变量赋值将影响当前的执行环境。除非 set -a 选项打开(参见 set),否则它是未指定的:
变量在执行特殊内置实用程序期间是否获得导出属性
在特殊内置实用程序完成后,由于变量分配而获得的导出属性是否仍然存在
因此,当 shell 读取命令以执行它时,它会首先解析它以查找任何看起来像变量赋值 ( foo=bar
) 的内容,然后分配相关值,当该命令是“正常”命令时,赋值将只影响该命令的运行环境,这就是为什么它有效:。
$ foo=bar sh -c 'echo "foo is $foo"'
foo is bar
$ echo "foo is $foo"
foo is
Run Code Online (Sandbox Code Playgroud)
变量分配起作用并存在于命令的执行环境中sh -c
,但它不存在于启动该命令的父 shell 中。这只是 shell 处理命令的一部分,并且是一种仅为单个命令设置变量而不影响 shell 的方法。
如果命令是特殊的内置命令(例如 ),则未指定分配是否在命令之后继续存在:
。这意味着有一些 shell在完成后var=foo :
使变量$var
可用command
,但这不是 POSIX 强制的,并且不同的 shell 的行为方式不同:
$ cat foo.sh
foo=bar :
echo "foo is $foo"
$ awk -F'/' '/^\//{print $NF}' /etc/shells | sort -u |
while read shell; do
echo "==== $shell ====";
"$shell" foo.sh;
done
==== bash ====
foo is
==== dash ====
foo is bar
==== fish ====
foo is
==== ksh ====
foo is bar
==== mksh ====
foo is bar
==== rbash ====
foo is
==== sh ====
foo is bar
==== yash ====
foo is bar
==== zsh ====
foo is
Run Code Online (Sandbox Code Playgroud)
无论如何,所有这一切正是您看到while IFS= read ...
和构造这样的原因:您不想弄乱您的$IFS
变量,因此您更改它只是为了运行您需要它具有不同值的特定命令。
最后,说一下这个方法有效的原因:
a=6 b=7 c=8
Run Code Online (Sandbox Code Playgroud)
是因为那里没有命令,只是变量赋值,所以这些变量确实是在当前shell中赋值的。