脚本通常以 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