【问题标题】:Scope of variables executing functions in a subshell在子shell中执行函数的变量范围
【发布时间】:2015-11-21 10:45:46
【问题描述】:

这就是在 subshel​​l 环境中执行命令时发生的情况:

  1. 该命令将在当前shell execution environment的副本中运行

  2. “影响 shell 环境的变量分配和内置命令在命令完成后不会保持有效” (quote)

例子:

#!/bin/sh

export TOTO=123
FOO=abc

(mycmd)

在这种情况下,mycmd 将能够读取 TOTO 而不是 FOO,并且 mycmd 对这两个变量的值所做的每一次更改都不会在当前 shell 中可见。

但是当我们对一个函数做同样的事情时会发生什么呢?

例子:

#!/bin/sh

export TOTO=123
FOO=abc

function (){
     echo $TOTO
     echo $FOO
     TOTO=${TOTO}456
     FOO=${FOO}def
}

(function)
echo $TOTO
echo $FOO

结果:

123
abc
123
abc

合理地,在子 shell 中执行的函数无法更改父 shell 中定义的变量的内容,另一方面,它能够不加选择地读取所有变量,即使它们没有被导出。

有人可以用更好的方式解释这种行为吗? 非常感谢一些参考的链接,因为我找不到任何关于它的内容。

【问题讨论】:

  • 谢谢。我知道这一点,但我想了解的是为什么在子 shell 中执行的函数能够读取所有变量,即使它们没有被导出。
  • 当你在子shell中调用函数时,函数及其变量在子shell中执行,现在我看到你的问题——同样的规则——子shell可以访问TOTO和@ 987654327@ 是函数范围,如果在该子 shell 中执行,则不应影响脚本主体中声明的 TOTOFOO 的现有值。即使没有重新声明,由于 subshel​​l 继承了 parent 的环境,同样的结果也会适用。
  • 所以每当一个函数在子shell中执行时,它会接收到其作用域的所有变量?有没有办法避免它并像普通命令一样执行它?
  • 是的 - 连同父环境的副本(包括变量),但函数可以重新声明变量,就像您所做的那样。如果你没有在函数中重新声明变量,那么当不在子shell中运行时,它会修改主体中的变量,但是当在子shell中运行时,它会被阻止修改父环境中的任何东西——包括变量。 (现在它仍然可以return 一个整数值)。
  • 另请注意,子shell 对于它们继承的环境有自己的规则,与它们是否在新进程中执行无关(它们通常但不是必须这样做):将echo $$ $BASHPID(echo $$ $BASHPID) 在 bash 4 或更高版本中。

标签: shell subshell


【解决方案1】:

您所观察到的与函数无关。子shell 获取所有环境,甚至包括未导出的变量。为了说明,让我们定义两个变量:

$ alpha=123
$ export beta=456

观察一个子shell可以访问两者:

$ (echo $alpha $beta)
123 456

如果我们启动一个新进程,它只会看到导出的变量:

$ bash -c 'echo $alpha $beta'
456

文档

man bash 记录子外壳如下:

(list)
list 在子shell环境中执行(见COMMAND 执行环境如下)。变量赋值和内置 影响 shell 环境的命令不再有效 命令完成后。返回状态是退出状态 列表。

如果我们去查看“命令执行环境”,我们发现它包括

通过变量赋值或从环境中的外壳父级设置或继承的外壳参数。

换句话说,它包括变量是否它们已被导出。

如果我们进一步阅读,我们会发现这与“除了内置或 shell 函数之外的简单命令”形成对比。此类命令仅接收导出的变量。

【讨论】:

  • 谢谢,很清楚。我认为 subshel​​l 环境不包含所有变量(作为每个正常的非 bultin 子进程)。那么无论(非内置)命令“mycmd”(第一个示例)是什么,如果直接执行mycmd 或在子shell 中执行(mycmd),结果将是相同的,对吗?
  • 是的,mycmd,无论是直接作为mycmd 执行还是通过(mycmd) 在子shell 中执行,都会看到相同的环境。
【解决方案2】:

这是一个简短的答案,希望能解释这些差异。它有两个函数myfunmyfunlocal。在 myfunlocal 中,totofoo 都被声明为具有 localtotofoo 的副本,它们在函数中更新时不会反映在脚本的主体中,而 myfun不进行local 重新声明,因此对totofoo 的更改会反映在主体中。但是,如果(myfun) 在子shell 中运行,则对totofoo 的更改不会反映在脚本的main 正文中:

#!/bin/bash

function myfun {

    printf "\n               myfun - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"

    toto=lizard
    foo=456

    printf "               myfun - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"
}

function myfunlocal {

    printf "\n          myfunlocal - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"

    local toto=lizard
    local foo=456

    printf "          myfunlocal - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"
}

toto=dog
foo=123

printf "\n before myfunlocal   : toto : %-6s  foo : %s\n" "$toto" "$foo"

myfunlocal

printf "\n after myfunlocal    : toto : %-6s  foo : %s\n" "$toto" "$foo"

(myfun)

printf "\n after subshell myfun: toto : %-6s  foo : %s\n" "$toto" "$foo"

myfun

printf "\n after simple myfun  : toto : %-6s  foo : %s\n\n" "$toto" "$foo"

输出

$ bash functionsubshell.sh

 before myfunlocal   : toto : dog     foo : 123

          myfunlocal - toto : dog     foo : 123
          myfunlocal - toto : lizard  foo : 456

 after myfunlocal    : toto : dog     foo : 123

               myfun - toto : dog     foo : 123
               myfun - toto : lizard  foo : 456

 after subshell myfun: toto : dog     foo : 123

               myfun - toto : dog     foo : 123
               myfun - toto : lizard  foo : 456

 after simple myfun  : toto : lizard  foo : 456

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-08
    • 2013-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多