如何定义要获取的Shell脚本无法运行


40

我正在定义用户应该source而不是执行的shell脚本。

是否有常规或智能的方式来提示用户这种情况,例如通过文件扩展名?

我是否可以在文件本身中编写外壳代码,这将导致该文件回显消息并在执行而不是源代码时退出,以便我可以帮助用户避免这一明显的错误?


1
因此,如果用户正在编写x仅包含命令的单行shell脚本,就. your-script-to-be-sourced可以了,但是如果他想执行bash your-script-to-be-sourced该脚本,应该禁止它吗?此限制的意义是什么?
user1934428 '02

8
@ user1934428当然。对于计算大量env变量并将其保留为实际脚本输出的脚本来说,这是正常的。如果您允许新手执行,那么新手将在数日之内陷入困境。
kubanczyk

Answers:


45

假设您正在运行bash,请将以下代码放在要获取但不执行的脚本的开头附近:

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

在bash下,${BASH_SOURCE[0]}将包含shell正在读取的当前文件的名称,而不管它是源文件还是执行文件。

相反,$0是正在执行的当前文件的名称。

-ef测试这两个文件是否为同一文件。如果是这样,我们会提醒用户并退出。

无论是-ef也不BASH_SOURCE是POSIX。虽然-ef由ksh,yash,zsh和Dash支持,但BASH_SOURCE需要bash。 zsh,然而,${BASH_SOURCE[0]}可能被替换${(%):-%N}


2
只是 echo "Usage: source \"$myfile\""
kubanczyk

6
@kubanczyk source不可移植。考虑到此答案是特定于bash的,虽然还不错,但是使用便携式设备是个好习惯.
gronostaj

33

不可执行的文件可以被获取但不能执行,因此,作为第一道防线,不设置可执行标志应该是一个很好的提示...

编辑:我偶然发现的把戏:使shebang成为不是shell解释器的任何可执行文件,/bin/false使脚本返回错误(rc!= 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"

7
然而,非可执行文件仍然可以通过如执行bash somefile.sh...
twalberg

1
如果您知道可以用bash(vd perl,python,awk ...)执行它,那么您已经看过源代码并看到说不做的评论:)
xenoid

1
Perl脚本通常以somefile.plPython 命名somefile.py,所以,不,我可能还没有读过注释(这些是什么,无论如何?),而且bash somefile.shchmod +x somefile.sh; ./somefile.sh... 短
。– twalberg

另外,一些类似Bourne的shell(包括bash)会首先尝试访问execve文件,但是如果失败,它们会手动检查文件并手动解释#!并通过该解释器调用它:这是#!纯用户空间时代以来的遗留问题。约定,而不是由内核本身处理。我认为 bash至少不会对不可执行的文件执行此操作,但是我不知道是否可以从用户可能从中调用脚本的所有shell中获得这种合理的行为。
mtraceur

“如果您知道可以使用bash来执行它,” Um,不,有时用户只是不知道存在其他外壳bash ,这bash script.sh可能是危险的。
Sergiy Kolodyazhnyy

10

此Stack Overflow帖子中建议了几种方法,其中,我最喜欢Wirawan PurwantoMr.spuratic建议的基于函数的方法:

正如Wirawan Purwanto所建议的,最可靠的方法是FUNCNAME[1] 在一个函数中进行检查 :

function mycheck() { declare -p FUNCNAME; }
mycheck

然后:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

这等效于检查caller,值 的输出mainsource区分调用者的上下文。使用 FUNCNAME[]保存您捕获和解析caller输出。不过,您需要知道或计算本地通话深度是否正确。诸如从其他函数或脚本中获取脚本的情况将导致数组(堆栈)更深。(FUNCNAME是一个特殊的bash数组变量,它应该具有与调用堆栈相对应的连续索引,只要它从不为准unset。)

因此,您可以添加到脚本的开头:

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check

7

假设执行脚本只是无用而不有害,您可以添加

return 0 || printf 'Must be sourced, not executed\n' >&2

到脚本末尾return除非正在获取文件,否则函数外部的退出代码为非零。


3
请注意,这将返回0退出状态。试试我改用这个类似的成语:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea

5

当您获取shell脚本时,shebang行将被忽略。通过输入无效的shebang,您可以提醒用户该脚本已错误执行:

#!/bin/bash source-this-script
# ...

错误消息将是这样的:

/bin/bash: source-this-script: No such file or directory

(任意)参数名称已经提供了很强的提示,但是错误消息仍然不是100%清晰。我们可以使用实用程序脚本来解决此问题,该脚本source-this-script位于您的某个位置PATH

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

现在,错误消息将是这样的:

This script must be sourced, not executed: path/to/script.sh

与其他方法的比较

与其他答案相比,此方法只需要对每个脚本进行最少的更改(并且具有shebang行有助于编辑器中的文件类型检测并指定shell脚本方言,因此甚至有好处)。不利之处是有些不清楚的错误消息,或者(一次性)添加了另一个shell脚本。

但是,它不会阻止通过显式调用bash path/to/script.sh(感谢@muru!)。


1
另一个缺点是,这将无法避免bash some/script.sh,也将忽略shebang。
muru

4
您可以通过使用shebang #!/bin/echo 'You must source this script!'或类似的方式使消息更清晰。
克里斯(Chris)

1
@Chris:是的,但是随后我将丢失文件类型检测(例如,在Vim中)以及有关这是哪种外壳方言的文档。如果您不关心这些,那么您的建议确实会摆脱第二个脚本!
Ingo Karkat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.