确定shell脚本是通过"引用"执行的
(Determining whether shell script was executed "sourcing" it)这个问题的出现的原因以及解决方法
问题的出现原因:
在编写shell脚本时,我们可能会遇到一种情况,即通过source命令来运行脚本。当我们使用source命令来运行脚本时,脚本中的代码会在当前shell环境下执行,而不是启动一个新的子shell来执行。在这种情况下,我们可能需要根据脚本是通过直接执行还是通过source命令执行来做出不同的处理。因此,我们需要一种方法来确定脚本是通过source命令执行还是直接执行。
解决方法:
我们可以使用${BASH_SOURCE[0]}和$0这两个变量来判断脚本是通过source命令执行还是直接执行。在bash脚本中,${BASH_SOURCE[0]}表示当前脚本的路径,$0表示当前脚本的文件名。如果脚本是通过source命令执行的,那么${BASH_SOURCE[0]}和$0的值是相同的。如果脚本是直接执行的,那么${BASH_SOURCE[0]}和$0的值是不同的。
下面是一个可以判断脚本是通过source命令执行还是直接执行的示例代码:
[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"
在这个示例代码中,[[ ${BASH_SOURCE[0]} = $0 ]]表示判断${BASH_SOURCE[0]}和$0的值是否相等,如果相等,则执行后面的main "$@"代码。main "$@"表示调用main函数并传递所有的命令行参数。这样,当脚本是通过直接执行时,main函数将会被调用;当脚本是通过source命令执行时,main函数不会被调用。
通过以上的解决方法,我们可以根据脚本是通过source命令执行还是直接执行来做出不同的处理,从而更好地控制脚本的执行逻辑。
判断shell脚本是通过执行还是通过引用执行的问题是在Sam的需求中出现的。他希望有一个名为`myscript`的脚本,满足以下条件:
1. 不能直接通过`myscript`的名称来执行(也就是没有`chmod a-x`权限)
2. 不能通过`sh myscript`或`bash myscript`间接执行
3. 只有通过引用执行(`. myscript`)时才会运行其中的函数和命令
首先需要考虑以下几点:
1. 通过名称直接调用脚本(`myscript`)需要在脚本的第一行添加类似于`#!/bin/bash`的内容,这将决定将调用哪个已安装的bash可执行文件(或符号链接)来运行脚本。这将创建一个新的shell进程。脚本文件本身需要具有可执行权限。
2. 通过使用脚本的(路径+)名称作为参数调用shell二进制文件(`sh myscript`)与第一点相同,唯一的区别是不需要设置可执行标志,也不需要添加带有hashbang的第一行。唯一需要的是调用用户需要对脚本文件进行读取访问。
3. 通过引用其文件名(`. myscript`)调用脚本与第一点非常相似,唯一的区别是不会创建一个新的shell进程。所有脚本的命令都在当前shell中执行,使用其环境(并且还会将其环境中的任何(新)变量设置或更改“污染”当前环境。)
对于第一点:
要实现很容易:`chmod a-x myscript`将阻止`myscript`直接执行。但是这不会满足第二点和第三点的要求。
对于第二点和第三点:
要实现更困难。通过`sh myscript`调用需要对文件具有读取权限。因此,一个明显的解决方法似乎是使用`chmod a-r myscript`。然而,这也会禁止第三点:您将无法引用脚本。
那么在脚本中使用`Bashism`的方式怎么样呢?`Bashism`是一种其他shell不理解的特定操作方法:使用特定的变量、命令等。这可以在脚本中使用来检测此条件并“处理”它(例如“显示warning.txt”,“发送邮件给管理员”等)。但是,除非您通过调用`exit`来终止shell,否则绝对不会阻止`sh`、`bash`或任何其他shell读取和尝试执行脚本中的所有后续命令/行。
假设:
1. 脚本使用`#!/bin/bash`作为第一行,
2. 用户已将Bash设置为其shell,并且正在从Bash中调用以下表中的命令,并且它是他们的“登录shell”。
3. `sh`可用,并且它是指向`bash`或`dash`的符号链接。
这将意味着以下调用是可能的,并列出环境变量的值:
vars+invok's | ./scriptname | sh scriptname | bash scriptname | . scriptname ---------------+--------------+---------------+-----------------+------------- $0 | ./scriptname | ./scriptname | ./scriptname | -bash $SHLVL | 2 | 1 | 2 | 1 $SHELLOPTS | braceexpand: | (empty) | braceexpand:.. | braceexpand: $BASH | /bin/bash | (empty) | /bin/bash | /bin/bash $BASH_ARGV | (empty) | (empty) | (empty) | scriptname $BASH_SUBSHELL | 0 | (empty) | 0 | 0 $SHELL | /bin/bash | /bin/bash | /bin/bash | /bin/bash $OPTARG | (empty) | (empty) | (emtpy) | (emtpy)
现在,您可以在文本脚本中添加逻辑:
- 如果`$0`不等于`-bash`,则执行`exit $SOMERETURNVALUE`。
如果通过`sh myscript`或`bash myscript`调用脚本,则会退出调用的shell。如果在当前shell中运行,它将继续运行。(警告:如果脚本有任何其他`exit`语句,您的当前shell将被“终止”...)
因此,在不可执行的`myscript.txt`中的开头添加类似以下内容可能会实现您的目标:
echo BASH=$BASH test x${BASH} = x/bin/bash && echo "$? : FINE.... You're using 'bash ...'" test x${BASH} = x/bin/bash || echo "$? : RATS !!! -- You're not using BASH and I will kick you out!" test x${BASH} = x/bin/bash || exit 42 test x"${0}" = x"-bash" && echo "$? : FINE.... You've sourced me, and I'm your login shell." test x"${0}" = x"-bash" || echo "$? : RATS !!! -- You've not sourced me (or I'm not your bash login shell) and I will kick you out!" test x"${0}" = x"-bash" || exit 33
感谢您的详细分析。很棒的答案。我希望能给您不止一个赞的机会。
原因:用户想要判断shell脚本是通过直接运行还是通过引用执行的。
解决方法:在脚本中添加一行指示语句,提示用户应该通过引用方式运行脚本。具体做法是在脚本的开头添加以下代码:
#!/bin/echo Should be run as: source
这样,当用户通过直接运行脚本时,会显示一条提示信息告诉用户应该通过引用方式运行。而当用户通过引用方式运行脚本时,脚本会正常执行。
以下是一个示例脚本:
#!/bin/echo Should be run as: source export SOMEPATH="/some/path/on/my/system" echo "Your environment has been set up"
当用户通过直接运行该脚本时,会得到以下输出:
$ ./myscript.sh Should be run as: source ./myscript.sh
当用户通过引用方式运行该脚本时,会得到以下输出:
$ source ./myscript.sh Your environment has been set up
需要注意的是,用户仍然可以通过运行`sh ./myscript.sh`来绕过这个判断,但至少在三种情况中有两种是符合预期行为的。
这种方法可能并不完美,但是对于解决这个困扰已久的问题来说是一种巧妙的方式。