Bash:仅允许脚本通过被另一个脚本调用而运行

4
我们有两个bash脚本来启动应用程序。第一个(Start-App.sh)设置环境,第二个(startup.sh)来自我们尝试不进行大量编辑的第三方。如果有人在第一个之前运行第二个脚本,则应用程序无法正确启动。
有没有办法确保startup.sh只能从Start-App.sh脚本调用?
它们都在同一个目录中,并通过Red Hat Linux上的bash运行。

正确的问题是,有没有办法防止它自动运行(除了通过Start-App.sh),并明确表示它不应该手动运行? - chepner
通常情况下,设置环境的脚本会被需要这些环境的脚本调用。只需在shebang下面添加1行代码即可轻松修改startup.sh,使其能够调用source Start-App.sh - alvits
8个回答

3

简述:

[ $(basename "$0") = "Start-App.sh" ] || exit


解释

与其他已呈现的解决方案一样,这并不是完美无缺的,但它覆盖了我遇到的大多数情况,以防止意外直接运行脚本而不是从另一个脚本中调用。

与其他方法不同,这种方法:

  • 不依赖于为每个包含/源脚本手动设置文件名(即对文件名更改具有弹性)
  • 在所有主要*nix发行版中表现一致,带有bash
  • 不引入不必要的环境变量
  • 不绑定于单个父脚本
  • 通过显式调用bash(例如bash myscript.sh)来防止运行脚本

基本思想是将类似于此处放置在脚本顶部:

[ $(basename "$0") = $(basename "$BASH_SOURCE") ] && exit

$0返回执行链开始处的脚本名称。

$BASH_SOURCE始终指向当前正在执行代码所在的文件(如果没有文件,则为空,例如直接将文本传输到bash)。

basename仅返回主文件名,不包含任何目录信息(例如,basename "/user/foo/example.sh"将返回example.sh)。这很重要,这样您就不会从比较example.sh./example.sh中得到错误结果。

为了使其适应只允许从一个特定文件中进行源化并向最终用户提供有用的错误消息,您可以使用:

[ $(basename "$0") = "Start-App.sh"  ] || echo "[ERROR] To start MyApplication please run ./Start-App.sh" && exit

正如答案开头所提到的,这并不是任何严肃安全措施的意图,但我猜想这也不是你想要的。


3
有没有一种方法可以确保只有从Start-App.sh脚本中调用startup.sh才能运行?
确保?不行。而且根本不能完全不编辑startup.sh。但你可以做到相当接近。
下面是三个建议,您可以使用其中之一或任意组合。
最简单、也可能是最好的方法是在startup.sh顶部添加一行:
[ -z $CALLED_FROM_START_APP ] && { echo "Not called from Start-App.sh"; exit 42; }

然后像这样从Start-App.sh调用它:

export CALLED_FROM_START_APP=yes
sh startup.sh

当然,您可以自行从shell设置此环境变量,所以它实际上并不能"确保"任何内容,但我希望您的工程师团队已经足够成熟,不会这样做。
您也可以从startup.sh中删除执行权限:
$ chmod a-x startup.sh

这并不能防止人们使用 "sh startup.sh",所以这里有一个非常小的保证;但它可能会防止自动完成错误,而且它将标记该文件为“不打算执行” - 如果我看到一个只有一个可执行的 .sh 文件的目录,我会尝试运行那个,而不是其他的。
最后,你可以重命名startup.sh脚本;例如,你可以将它重命名为"do_not_run",或者通过将其重命名为".startup"来隐藏它。这样做可能不会干扰此脚本的操作(虽然我无法检查)。

-z测试中需要在"$CALLED_FROM_START_APP"周围加上引号,否则它总是会通过(即使为空),因为[ -z $var ]变成了[ -z ],而shell将其解释为[ -n -z ],这是真的(因为-z具有非零长度)。 - Etan Reisner
@EtanReisner 嗯,如果我在 dashbash 中尝试这个命令而不使用引号,它可以正常工作吗?例如 [ -z $XXX ] && echo Empty 将按预期输出 Empty,而 export XXX=yes; [ -z $XXX ] && echo Empty 则不会... 我也已经打了好几年没有用引号的命令了,无法回忆起曾经遇到过你所描述的问题... - Martin Tournoij
你是正确的。我把问题搞反了。[ -n $XXX ]永远不会失败,因为它退化成了[ -n ],而不是[ -n '' ]。尽管如此,不使用引号仍然是错误的,因为你最终还是会测试[ -n -z ]而不是[ -z '' ]并且如果你的值包含空格或通配符,你会得到一个错误。 - Etan Reisner

2
您可以通过输入chmod -x startup.sh使startup.sh无法执行。这样,用户就不能通过输入./startup.sh来运行它。
然后,在Start-App.sh中,通过显式调用shell来调用您的脚本:
sh ./startup.sh arg1 arg2 ...

或者

bash ./startup.sh arg1 arg2 ...

您可以通过检查startup.sh的第一行来确定应该在哪个shell中运行,它应该看起来像这样:
#!/bin/bash

1
这并不能阻止某人使用shbash运行脚本。 - Sean
它本身不会,但用户必须使用 bash startup.sh 显式调用它。这样,当用户执行 ls -l 命令时,只有 Start-App.sh 会被标记为可执行文件,这表明应该使用它而不是其他文件。我的理解是 OP 不想编辑 startup.sh,以便可以轻松地从上游更新。 - jeremija

1
你可以在第一个脚本中设置环境变量,并在运行第二个脚本之前检查该环境变量是否已正确设置。

这个方法可以行得通,但是OP说他们不想编辑第二个脚本。 - Sean
@sean,OP说他们不想“重度”编辑启动脚本。 - glenn jackman

1
另一个选择是检查父进程并找到调用脚本。这也需要在第二个脚本中添加一些代码。
例如,在被调用的脚本中,您可以检查此退出状态并终止。
ps $PPID | tail -1 | awk '$NF!~/parent/{exit 1}'

1
正如其他人所指出的那样,简短的答案是“不行”,尽管您可以整天玩权限,但这仍然不是绝对可靠的。既然您说您不介意编辑(只是不要大幅度编辑)第二个脚本,最好的方法是采用以下方式之一:

1)在父/第一个脚本中,导出一个带有其PID的环境变量。这成为父PID。例如,

# bash store parent pid
export FIRST_SCRIPT_PID = $$

2) 然后在第二个脚本中简要检查调用PID是否与已知可接受的父PID匹配。例如,

# confirm calling pid
if [ $PPID != $FIRST_SCRIPT_PID ] ; then
    exit 0
fi

请参考这些链接此处此处了解相关内容。

简要概述:最直接的方法是在第二个脚本中添加至少一两行代码,希望这不算是“大幅度编辑”。


1
你可以创建一个脚本,我们称其为check-if-my-env-set,其中包含:
#! /bin/bash

source Start-App.sh
exec /bin/bash $@

并且将 startup.sh 中的shebang(请参见this)替换为该脚本

#! /abs/path/to/check-if-my-env-set
#! /bin/bash
...

然后,每次运行startup.sh时,它将确保环境设置正确。


0
据我所知,没有办法以一种不可避免地绕过它的方式来做到这一点。
但是,您可以通过使用权限来阻止大多数尝试。
更改 startup.sh 文件的所有者:
sudo chown app_specific_user startup.sh

使startup.sh文件只能被所有者执行:
chmod u+x startup.sh

app_specific_user身份从Start-App.sh运行startup.sh

sudo -u app_specific_user ./startup.sh

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