为什么在Bash函数中可以设置环境变量,但在脚本本身中却不能?

7

为什么这个可以工作:

# a.sh
setEnv() {
    export TEST_A='Set'
}

当这个不起作用时:
# b.sh
export TEST_B='Set'

示例:

> source a.sh
> setEnv
> env | grep TEST_A
TEST_A=Set
> b.sh
> env | grep TEST_B

我明白为什么运行脚本不起作用以及如何使其起作用(例如source b.sh等),但我很好奇函数为什么有效。 如果有影响,这是在OS X上。


1
当您运行脚本时,它会创建一个子shell,并在其中定义变量,但不会在父shell中定义。函数调用仍然在当前shell中进行。您也可以使用source命令导入其他脚本。 - karakfa
7
对脚本进行对称处理就可以使其正常工作。先运行 source b.sh 然后运行 env | grep TEST_B 就可以了。如果没有运行 a.sh,您将没有可用的函数来运行。这与子shell有关(运行 b.sh 会创建一个新的shell,并设置其环境),而不是使用 source (使用 source 不会创建新的shell)。 - Jonathan Leffler
3个回答

12
你需要理解sourcingexecuting脚本之间的区别。 Sourcing是在调用脚本的父Shell中运行脚本;所有环境变量都将保留,直到父Shell终止(终端关闭、变量重置或取消设置),而Execute会从父Shell中fork出一个新的Shell,在子Shell的环境中保留那些变量,包括你的export变量,并在脚本结束时销毁。
也就是说,第一种情况中创建的子Shell(想象成一个环境)并不是在一个单独的子环境范围内分配变量,而只是将其添加到父级(例如,想象一个由父级维护的额外记忆单元)环境中,该环境在会话打开期间一直存在。但是执行脚本类似于调用一个函数,其变量存储在堆栈中,在函数调用结束时失去作用域。同样,派生Shell的环境在终止时失去作用域。
因此,即使您有一个将变量导出的function,如果您不将其source到当前Shell中,而只是简单地execute它,则变量不会被保留。
# a.sh
setEnv() {
    export TEST_A='Set'
}

如果您在shell中运行它

bash script.sh    # unlike/NOT source script.sh
env | grep TEST_A
                  # empty

他承认在没有使用“source”的情况下运行脚本是他问题的原因。 - chepner
1
同意@chepner的观点:在这种情况下,让OP知道它们之间的区别会使他也“source”第二个脚本,对吗?您认为我应该添加其他信息吗? - Inian
1
这个问题的重点似乎是为什么运行一个函数可以工作,而运行脚本却不行。 - chepner
@chepner:这会让答案更好吗? - Inian
1
不,你现在关注的是源码和执行之间的差异,而这个问题OP已经理解了。问题完全是关于为什么在函数执行完成后,对变量的改变仍然可见。 - chepner
这是一个微妙的区别,但是(直接调用的)脚本在一个子进程中运行,该子进程恰好是一个 shell(可能是另一个 shell),这与子 shell 不同(后者在命令替换的上下文中创建)。 后者是当前 shell 的分叉副本。 - mklement0

8

执行函数本身不会像b.sh那样启动新进程。

从man页面中可以看到(最后一句话的重点在于):

FUNCTIONS
       A shell function, defined  as  described  above  under  SHELL  GRAMMAR,
       stores  a  series  of commands for later execution.  When the name of a
       shell function is used as a simple command name, the list  of  commands
       associated with that function name is executed.  **Functions are executed
       in the context of the current shell;  no  new  process  is  created  to
       interpret  them  (contrast  this with the execution of a shell script).**

6
我明白为什么直接运行脚本不起作用以及如何使其起作用(例如:source b.sh)。因此,您已经了解到在子进程中直接执行b.sh,对环境所做的更改基本上不会在当前进程(shell)中可见,因此我们可以排除这种情况。我好奇函数为什么有效。
  • 当你source一个脚本时,你在当前shell的上下文中执行它 - 粗略地说,这就像你直接在提示符处键入脚本的内容一样:对环境的任何更改,包括特定于shell的元素,如shell变量、别名、函数,都会对当前shell可见。

  • 因此,在执行source a.sh后,函数setEnv现在在当前shell中可用,并调用它会执行export TEST_A='Set',该命令在当前 shell中定义了环境变量TEST_A(随后创建的子进程将看到它)。

  • 也许你的误解在于chepner's helpful answer所涉及的内容:在类POSIX的shell中,函数在当前shell中运行 - 与脚本(在没有source的情况下运行)形成对比,后者会创建一个子进程

如果这个问题与OS X有关,则需要考虑。

在这种情况下,仅使用内置于 bash 中的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接