![了解“副作用”,或一个命令中的多个命令?](https://linux22.com/image/220712/%E4%BA%86%E8%A7%A3%E2%80%9C%E5%89%AF%E4%BD%9C%E7%94%A8%E2%80%9D%EF%BC%8C%E6%88%96%E4%B8%80%E4%B8%AA%E5%91%BD%E4%BB%A4%E4%B8%AD%E7%9A%84%E5%A4%9A%E4%B8%AA%E5%91%BD%E4%BB%A4%EF%BC%9F.png)
这个问题重点关注 POSIX 兼容的 shell 脚本。
通常通过以下方式递增变量:
i=3
: $(( i += 1 ))
echo "return code = $?" # return code = 0
echo "i = $i" # i = 4
该命令是否$(( i += 1 ))
称为“副作用” :
?
(我在某处读到:
等于true
。我尝试:
用true
or替换false
,它有效。如果用 替换false
,则返回码为 1,正如预期的那样。)
为什么成功增加了值$i
,但赋值却不起作用,出现“副作用”?
a='four'
: a='five'
echo "return code = $?" # return code = 0
echo "a = $a" # a = four
有时,我看到脚本在一个命令中运行多个命令。在大多数情况下,人们使用此结构来设置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
这种结构称为“副作用”吗?我在哪里可以找到它的记录?或者我可以在哪里了解它?我在下面的 POSIX 文档中找不到这个结构。
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09
答案1
这:
称为空命令。您可以在以下位置找到其文档man bash
:
:[论点]
没有效果;该命令除了扩展参数和执行任何指定的重定向之外不执行任何操作。返回状态为零。
或者,如果运行 bash shell,则使用help :
:
$ help :
:: :
Null command.
No effect; the command does nothing.
Exit Status:
Always succeeds.
它在 POSIX 规范中这里:
姓名
冒号 - null 实用程序概要:[参数...]
描述
该实用程序只能扩展命令参数。当需要命令时使用它,如 if 命令的 then 条件,但该命令不执行任何操作。
现在,您所说的副作用是上面第一句话中描述的结果:“该命令不执行任何操作除了扩大论据之外”。:
是一个命令,因此后面的任何内容都是一个参数。$(( ))
被称为“算术扩展”,记录man bash
如下:
算术扩展
算术展开允许计算算术表达式并替换结果。
算术展开式的格式为:
$((expression))
该表达式被视为位于双引号内,但括号内的双引号不会被特殊处理。表达式中的所有标记都会经历参数和变量扩展、命令替换和引号删除。结果被视为要计算的算术表达式。算术展开式可以嵌套。
[。 。 .]
so 的$(( i += 1 ))
意思是“加i
一并返回结果”。然而,这需要评估,扩大,这就是为什么你会看到它与 一起使用:
。由于:
将扩展其参数,: $(( i += 1 ))
因此平均值i
将增加 1。如果您尝试$(( i += 1 ))
单独运行,则首先会扩展为4
(在您的示例中),然后 shell 会尝试4
作为命令执行并返回错误:
$ $(( i += 1 ))
bash: 4: command not found
但是如果你运行: $(( i += 1 ))
它,shell 会做两件事:首先,它将应用算术扩展并取i
的值4
,然后它将执行: 4
一个空命令,因此不会返回任何错误,因为:
在扩展后会忽略它的参数:
$ : 4
$
综上所述,我真的不知道为什么你会想要这样做,而不是更简单且 POSIX:
$ i=3
$ i=$(( i + 1 ))
$ echo "$i"
4
或者,在POSIX shell 中bash
,但不在 POSIX shell 中:
$ i=3
$ (( i += 1 ))
$ echo "$i"
4
这里的下一点是,这variable=value command
是一种不同的野兽,并且以不同的方式对待。 POSIX 规范的相关部分是2.9.1 简单命令,其中最相关的部分是:
“简单命令”是一系列可选的变量分配和重定向,可以任意顺序,可选地后跟单词和重定向,并由控制运算符终止。
当需要执行给定的简单命令时[. 。 .],以下扩展、赋值和重定向均应从命令文本的开头到结尾执行:
根据 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
变量分配起作用并存在于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
无论如何,所有这一切正是您看到while IFS= read ...
和构造这样的原因:您不想弄乱您的$IFS
变量,因此您更改它只是为了运行您需要它具有不同值的特定命令。
最后,说一下这个方法有效的原因:
a=6 b=7 c=8
是因为那里没有命令,只是变量赋值,所以这些变量确实是在当前shell中赋值的。
答案2
该命令是否
$(( i += 1 ))
称为“副作用”:
?
不。增加 的值i
是 的副作用评估表达式(不是命令)$(( i += 1 ))
。
换句话说,如果i
为 3,则表达式$(( i += 1 ))
将扩展为值4
,但它也具有将 4 存储回 的副作用i
。
在你的情况下,你正在评估表达式仅有的因为它的副作用;你不关心它扩展到的值。但它仍然做扩展为某些内容,并且您不能只编写
$(( i += 1 ))
为命令,因为这会导致4: command not found
.所以我们需要以某种方式丢弃这个值。这时我们转向该:
命令,该命令不执行任何操作并忽略其参数。: $(( i += 1))
展开为: 4
,这是一个成功执行任何操作的命令。但作为副作用其参数已被扩展,i
现在包含 4 个。