了解“副作用”,或一个命令中的多个命令?

了解“副作用”,或一个命令中的多个命令?

这个问题重点关注 POSIX 兼容的 shell 脚本。

通常通过以下方式递增变量:

i=3
: $(( i += 1 ))
echo "return code = $?"        # return code = 0
echo "i = $i"                  # i = 4

该命令是否$(( i += 1 ))称为“副作用” :

(我在某处读到:等于true。我尝试:trueor替换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 简单命令,其中最相关的部分是:

“简单命令”是一系列可选的变量分配和重定向,可以任意顺序,可选地后跟单词和重定向,并由控制运算符终止。

当需要执行给定的简单命令时[. 。 .],以下扩展、赋值和重定向均应从命令文本的开头到结尾执行:

  1. 根据 Shell 语法规则识别为变量赋值或重定向的单词将被保存以便在步骤 3 和 4 中进行处理。

  2. 非变量赋值或重定向的词应扩展。如果扩展后仍有任何字段,则第一个字段应被视为命令名称,其余字段是命令的参数。

  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 个。

相关内容