脚本可以作为子 shell 运行吗?

脚本可以作为子 shell 运行吗?

脚本通常以 shebang 开头,例如#!/usr/bin/env bash,它指定要用于执行的 shell。 shebang 不存在时的执行行为似乎是直到调用 shell。无论哪种方式,脚本都是从“新”shell 运行的,该 shell 不知道调用 shell 中定义的变量和函数。

或者,也可以将脚本source放入 shell 中,根据我的理解,这相当于将脚本内容复制粘贴到当前 shell 中。如果脚本中定义了任何函数或变量,那么它们在“执行”后将保留在调用 shell 中,这可能是不可取的。

这两个选项之间有什么关系吗?是否可以将脚本作为调用 shell 的子 shell 来执行,这样我们就可以访问调用 shell 中定义的所有内容,但不会修改它(除非可能使用export这样的命令)?

写作(source myscript.sh)似乎就是我所追求的;这是正确的做法吗?是否有一个等效的 shebang 可以通过调用来产生相同的行为./myscript.sh

答案1

(. ./myscript.sh)

是正确的方法(标准形式是., 不是source,并且在 ).上没有指定目录的搜索PATH

使用 shebang 执行此操作需要有一种可靠的方法来查找运行父脚本的 shell 的二进制文件,没有依赖环境变量。在 Linux 上,人们可能会想象/proc/parent类似的/proc/self,指向父进程;然后就可以写

#! /proc/parent/exe -

让 shell 脚本使用父进程运行的任何二进制文件。 (即使原始 shell 被删除或替换,这也可以工作;/proc/self/exe即使二进制文件被删除也可以使用,并且/proc/parent/exe可以以相同的方式运行。)但是盲目依赖父进程的二进制文件才能运行脚本本身是不可靠的(如果父进程在子进程启动时死亡,则会出现竞争)。

答案2

POSIX(IEEE 标准 1003.1-2017,第 2.12 节)指定:

应创建子 shell 环境作为 shell 环境的副本,但未被忽略的信号陷阱应设置为默认操作。对子 shell 环境所做的更改不应影响 shell 环境

因此,您认为子 shell 可以访问调用 shell 中的所有内容是正确的,但关于命令(例如export能够修改父 shell 的环境)的说法是错误的。

当然,POSIX 也规定了格式,如下:

( compound-list )
    Execute compound-list in a subshell environment; ...

是否有一个等效的 shebang 可以通过调用 ./myscript.sh 来产生相同的行为?

假设你的脚本中有一个 shebang。它将使用您指定的任何解释器(例如 bash)运行,但作为单独的过程,而不是作为子外壳。但这仍然可以实现您期望的结果,即运行一个不会修改父 shell 环境的脚本。

$ cat script.sh
#!/usr/bin/env bash
VAR=modified
echo "VAR is $VAR"
echo "PID: $$"

$ echo "Parent: $$"
Parent: 793915

$ VAR=initial
$ ./script.sh
VAR is modified
PID: 799149

$ echo $VAR
initial

$ bash script.sh
VAR is modified
PID: 799784

$ echo $VAR
initial

$ . script.sh
VAR is modified
PID: 793915

$ echo $VAR
modified

注意:点.命令相当于source.


更新:忘记说明以脚本形式运行不会导出任何环境变量,除非set -a设置了该选项。不过请记住,只有分配的变量该选项打开将被导出。如果这不是您想要的,那么您最好的选择是(. script.sh)

从手册:

-a

启用此选项时,应为要执行赋值的每个变量设置导出属性

$ cat script.sh
#!/usr/bin/env bash
echo "VAR is $VAR"
VAR=modified
echo "VAR now set to $VAR"

$ VAR=original
$ (. script.sh)
VAR is original
VAR now set to modified
$ echo $VAR
original
$ # works as expected, unchanged

$ ./script.sh
VAR is
VAR now set to modified
$ # var not sent over

$ set -a
$ VAR=original
$ ./script.sh
VAR is original
VAR now set to modified
$ echo $VAR
original
$ # works, and your variable remains unchanged
$ set +a # turn the option off

相关内容