如何最好地包含其他脚本?

Aar*_* H. 338 bash

通常包含脚本的方式是"源"

例如:

main.sh:

#!/bin/bash

source incl.sh

echo "The main script"
Run Code Online (Sandbox Code Playgroud)

incl.sh:

echo "The included script"
Run Code Online (Sandbox Code Playgroud)

执行"./main.sh"的输出是:

The included script
The main script
Run Code Online (Sandbox Code Playgroud)

...现在,如果您尝试从其他位置执行该shell脚本,除非它在您的路径中,否则无法找到包含.

确保脚本可以找到包含脚本的好方法是什么,特别是如果脚本需要可移植的话?

Chr*_*ran 215

我倾向于使我的脚本彼此相对.那样我就可以使用dirname:

#!/bin/sh

my_dir="$(dirname "$0")"

"$my_dir/other_script.sh"
Run Code Online (Sandbox Code Playgroud)

  • 没有可靠的方法来确定shell脚本的位置,请参阅http://mywiki.wooledge.org/BashFAQ/028 (39认同)
  • @Philipp,该条目的作者是正确的,它是复杂的,并且有一些陷阱.但它缺少一些关键点,首先,作者假设你将使用bash脚本做很多事情.我不希望python脚本在没有依赖项的情况下运行.Bash是一种粘合语言,可以让你快速做事,否则很难.当您需要构建系统工作时,实用主义(以及关于脚本无法找到依赖关系的好警告)获胜. (11认同)
  • 刚刚了解了`BASH_SOURCE`数组,以及该数组中的第一个元素如何始终指向当前源. (11认同)
  • 如果脚本位于不同的位置,则无效.例如/home/me/main.sh调用/home/me/test/inc.sh作为dirname将返回/ home/me.使用BASH_SOURCE的sacii答案是一个更好的解决方案http://stackoverflow.com/a/12694189/1000011 (6认同)
  • 如果脚本是通过$ PATH执行的,那么这将不起作用.然后`哪个$ 0`将是有用的 (5认同)

小智 174

我知道我迟到了,但无论你如何启动脚本并专门使用内置函数,这都应该有效:

DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"
Run Code Online (Sandbox Code Playgroud)

.(dot)命令是source的别名,$PWD是工作目录的路径,BASH_SOURCE是一个数组变量,其成员是源文件名,${string%substring}从$ string后面剥离$ substring的最短匹配

  • 这是一直为我工作的线程中唯一的答案 (6认同)
  • @sacii我可以知道何时是[if [[!-d"$ DIR"]]; 那么DIR ="$ PWD"; 需要吗?如果将命令粘贴到bash提示符以运行,我可以找到它的需要.但是,如果在脚本文件上下文中运行,我看不到它的需要...... (3认同)
  • 这不应该是$ {BASH_SOURCE [0]}`吗,因为您只想拨打最新的电话?另外,使用`DIR = $(dirname $ {BASH_SOURCE [0]})`将使您摆脱if条件 (3认同)
  • 这太棒了。参见`man bash`。 (2认同)
  • 值得注意的是,它可以在多个`source`s中按预期工作(我的意思是,如果你'源代码`一个脚本'在另一个目录中源另一个,依此类推,它仍然可以工作). (2认同)
  • 如果您使用 `$ bash script.sh` 从脚本目录调用脚本,其中前导点不包含在 `$BASH_SOURCE` 中,并且 `DIR` 解析为 `script.sh` 而 `$(dirname $BASH_SOURCE) ` 正确解析为 `.` (2认同)

tar*_*ate 50

替代方案:

scriptPath=$(dirname $0)
Run Code Online (Sandbox Code Playgroud)

是:

scriptPath=${0%/*}
Run Code Online (Sandbox Code Playgroud)

..优点是没有依赖于dirname,这不是内置命令(并不总是在模拟器中可用)

  • `basePath = $(dirname $ 0)`在提供包含脚本文件时给了我空白值. (2认同)

dsm*_*dsm 38

如果它位于同一目录中,则可以使用 dirname $0:

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"
Run Code Online (Sandbox Code Playgroud)

  • 两个陷阱:1)`$ 0`是`./t.sh`,dirname返回`.`; 2)在`cd bin`之后返回的`.`不正确.`$ BASH_SOURCE`并不是更好. (2认同)
  • `source "$(dirname $0)/incl.sh"` 适用于这些情况 (2认同)

小智 27

我认为最好的方法是使用Chris Boran的方式,但你应该这样计算MY_DIR:

#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh
Run Code Online (Sandbox Code Playgroud)

引用readlink的手册页:

readlink - display value of a symbolic link

...

  -f, --canonicalize
        canonicalize  by following every symlink in every component of the given 
        name recursively; all but the last component must exist
Run Code Online (Sandbox Code Playgroud)

我从来没有遇到过MY_DIR没有正确计算的用例.如果你通过它的符号链接访问你的脚本$PATH.


Bri*_*aak 20

这个问题的答案组合提供了最强大的解决方案.

它在生产级脚本中为我们工作,具有很强的依赖性和目录结构支持:

#!/bin/bash

# Full path of the current script
THIS=`readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0`

# The directory where current script resides
DIR=`dirname "${THIS}"`

# 'Dot' means 'source', i.e. 'include':
. "$DIR/compile.sh"

该方法支持所有这些:

  • 路径中的空间
  • 链接(通过readlink)
  • ${BASH_SOURCE[0]} 比...更健壮 $0

  • 我把它放在这一行:`DIR=$(dirname $(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0)) # https://stackoverflow.com/a /34208365/` (2认同)

Max*_*Max 19

SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"
Run Code Online (Sandbox Code Playgroud)

  • 即使从当前目录执行脚本,此代码也将返回绝对路径.仅$(dirname"$ 0")将仅返回"." (7认同)

Ale*_*ato 14

即使脚本来源,这也有效:

source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"
Run Code Online (Sandbox Code Playgroud)

  • @Ray `BASH_SOURCE` 是一个路径数组,对应于一个调用堆栈。第一个元素对应于堆栈中最新的脚本,即当前正在执行的脚本。实际上,作为变量调用的“$BASH_SOURCE”默认会扩展到其第一个元素,因此这里不需要“[0]”。有关详细信息,请参阅[此链接](http://mywiki.wooledge.org/BashFAQ/028)。 (2认同)

Ale*_*xar 9

最好的

我探讨了几乎所有的建议,这是最适合我的建议:

script_root=$(dirname $(readlink -f $0))

即使脚本符号链接到$PATH目录,它也可以工作.

请在此处查看:https://github.com/pendashteh/hcagent/blob/master/bin/hcagent

2.最酷的

# Copyright https://stackoverflow.com/a/13222994/257479
script_root=$(ls -l /proc/$$/fd | grep "255 ->" | sed -e 's/^.\+-> //')
Run Code Online (Sandbox Code Playgroud)

这实际上来自这个页面上的另一个答案,但我也将它添加到我的答案中!

2.最可靠

或者,在极少数情况下,这些不起作用,这是防弹方法:

# Copyright http://stackoverflow.com/a/7400673/257479
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

script_root=$(dirname $(whereis_realpath "$0"))
Run Code Online (Sandbox Code Playgroud)

您可以在taskrunner源代码中看到它的运行情况:https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner

希望这能帮到那里的人:)

另外,如果一个不适合您并提及您的操作系统和模拟器,请将其留作评论.谢谢!


Ste*_*ker 7

您需要指定其他脚本的位置,没有其他方法.我建议在脚本的顶部放置一个可配置的变量:

#!/bin/bash
installpath=/where/your/scripts/are

. $installpath/incl.sh

echo "The main script"
Run Code Online (Sandbox Code Playgroud)

或者,您可以坚持要求用户维护一个环境变量,指示您的程序所在的位置,例如PROG_HOME或其他.这可以通过在/etc/profile.d/中创建包含该信息的脚本自动为用户提供,该脚本将在用户每次登录时获取.

  • 因为脚本执行的目录不一定是您要在脚本中包含的脚本所在的位置.您希望加载安装它们的脚本,并且没有可靠的方法来确定它在运行时的位置.不使用固定位置也是包含错误(即黑客提供)脚本并运行它的好方法. (4认同)

Rob*_*lls 6

我建议你创建一个setenv脚本,其唯一目的是为整个系统中的各种组件提供位置.

然后,所有其他脚本将获取此脚本,以便使用setenv脚本在所有脚本中使用所有位置.

这在运行cronjobs时非常有用.在运行cron时,您获得了最小的环境,但如果您首先使所有cron脚本都包含setenv脚本,那么您就可以控制和同步您希望cronjobs执行的环境.

我们在构建猴子上使用了这种技术,用于在大约2,000 kSLOC的项目中进行持续集成.