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

时间:2008-10-10 17:14:39

标签: bash

通常包含脚本的方式是“source”

例如:

main.sh:

#!/bin/bash

source incl.sh

echo "The main script"

incl.sh:

echo "The included script"

执行“./main.sh”的输出是:

The included script
The main script

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

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

22 个答案:

答案 0 :(得分:204)

我倾向于使我的脚本彼此相对。 这样我可以使用dirname:

#!/bin/sh

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

"$my_dir/other_script.sh"

答案 1 :(得分:164)

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

DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"

.(点)命令是源的别名,$PWD是工作目录的路径,BASH_SOURCE是一个数组变量,其成员是源文件名,{{1}从$ string

后面剥离$ substring的最短匹配

答案 2 :(得分:50)

替代:

scriptPath=$(dirname $0)

是:

scriptPath=${0%/*}

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

答案 3 :(得分:37)

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

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"

答案 4 :(得分:25)

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

#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh

引用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

我从未遇到过MY_DIR未正确计算的用例。如果您通过$PATH中的符号链接访问您的脚本,则可以使用。

答案 5 :(得分:19)

SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"

答案 6 :(得分:18)

此问题的答案组合提供了最强大的解决方案。

它适用于我们的生产级脚本,并且非常支持依赖项和目录结构:

#!/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
  • 更强大

答案 7 :(得分:12)

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

source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"

答案 8 :(得分:7)

您需要指定其他脚本的位置,没有别的办法。我建议在脚本顶部添加一个可配置变量:

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

. $installpath/incl.sh

echo "The main script"

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

答案 9 :(得分:6)

1。最巧妙

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

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/^.\+-> //')

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

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"))

您可以在taskrunner来源:https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner

中看到它的实际效果

希望这可以帮助那些人:)

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

答案 10 :(得分:5)

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

然后,所有其他脚本将使用setenv脚本来源此脚本,以便所有脚本在所有脚本中都是通用的。

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

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

答案 11 :(得分:3)

Steve的回复肯定是正确的技术,但它应该被重构,以便你的installpath变量位于一个单独的环境脚本中,在那里进行所有这样的声明。

然后所有脚本都来源该脚本并且应该安装路径更改,您只需要在一个位置更改它。使事情变得更多,呃,具有前瞻性。上帝,我恨这个词! ( - :

顺便说一句你应该按照示例中显示的方式使用$ {installpath}来引用变量:

. ${installpath}/incl.sh

如果省略了大括号,有些shell会尝试扩展变量“installpath / incl.sh”!

答案 12 :(得分:2)

Shell Script Loader 是我的解决方案。

它提供了一个名为include()的函数,可以在许多脚本中多次调用以引用单个脚本,但只会加载一次脚本。该函数可以接受完整路径或部分路径(在搜索路径中搜索脚本)。还提供了一个名为load()的类似函数,它将无条件地加载脚本。

适用于 bash ksh pd ksh zsh及优化脚本;通过自动优化的通用脚本,以及其他通常与 ash 破折号传家宝sh 等原始产品兼容的外壳它的功能取决于shell可以提供的功能。

[Fowarded示例]

<强> start.sh

这是一个可选的启动脚本。在这里放置启动方法只是一种方便,可以放在主脚本中。如果要编译脚本,也不需要此脚本。

#!/bin/sh

# load loader.sh
. loader.sh

# include directories to search path
loader_addpath /usr/lib/sh deps source

# load main script
load main.sh

<强> main.sh

include a.sh
include b.sh

echo '---- main.sh ----'

# remove loader from shellspace since
# we no longer need it
loader_finish

# main procedures go from here

# ...

<强> a.sh

include main.sh
include a.sh
include b.sh

echo '---- a.sh ----'

<强> b.sh

include main.sh
include a.sh
include b.sh

echo '---- b.sh ----'

<强>输出:

---- b.sh ----
---- a.sh ----
---- main.sh ----

最好的是基于它的脚本也可以编译为使用可用的编译器形成单个脚本。

这是一个使用它的项目:http://sourceforge.net/p/playshell/code/ci/master/tree/。无论是否编译脚本,它都可以移植。编译以生成单个脚本也可能发生,并且在安装过程中很有用。

我还为任何可能希望简要了解实现脚本如何工作的保守派创建了一个更简单的原型:https://sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype.bash。它很小,任何人都可以在他们的主脚本中包含代码,如果他们的代码是打算使用Bash 4.0或更新版本运行,并且它也不使用eval

答案 13 :(得分:2)

个人将所有库放在import文件夹中,并使用script.sh函数加载它们。

文件夹结构

enter image description here

# Imports '.sh' files from 'lib' directory function import() { local file="./lib/$1.sh" local error="\e[31mError: \e[0mCannot find \e[1m$1\e[0m library at: \e[2m$file\e[0m" if [ -f "$file" ]; then source "$file" if [ -z $IMPORTED ]; then echo -e $error exit 1 fi else echo -e $error exit 1 fi } 内容

import "utils"
import "requirements"

请注意,此导入功能应在脚本的开头,然后您可以像这样轻松导入库:

IMPORTED="$BASH_SOURCE"

在每个库的顶部添加一行(即utils.sh):

utils.sh

现在您可以访问requirements.sh的{​​{1}}和script.sh内部的功能

TODO:编写链接器以构建单个sh文件

答案 14 :(得分:1)

我将所有启动脚本放在.bashrc.d目录中。 这是/etc/profile.d等

等常用技术
while read file; do source "${file}"; done <<HERE
$(find ${HOME}/.bashrc.d -type f)
HERE

使用globbing的解决方案的问题......

for file in ${HOME}/.bashrc.d/*.sh; do source ${file};done

...你可能有一个“太长”的文件列表。 像......这样的方法。

find ${HOME}/.bashrc.d -type f | while read file; do source ${file}; done

...运行但不会根据需要改变环境。

答案 15 :(得分:1)

使用source或$ 0不会为您提供脚本的真实路径。您可以使用脚本的进程ID来检索其真实路径

ls -l       /proc/$$/fd           | 
grep        "255 ->"            |
sed -e      's/^.\+-> //'

我正在使用这个脚本,它一直很好用我:)

答案 16 :(得分:1)

当然,对于他们自己,但我认为下面的块是相当坚实的。我相信这涉及到最好的&#34;找到目录的方式,以及&#34; best&#34;调用另一个bash脚本的方法:

scriptdir=`dirname "$BASH_SOURCE"`
source $scriptdir/incl.sh

echo "The main script"

所以这可能是最好的&#34;包含其他脚本的方法。这是基于另一个&#34; best&#34;回答tells a bash script where it is stored

答案 17 :(得分:1)

这应该可靠地运作:

source_relative() {
 local dir="${BASH_SOURCE%/*}"
 [[ -z "$dir" ]] && dir="$PWD"
 source "$dir/$1"
}

source_relative incl.sh

答案 18 :(得分:0)

我在这里看到的大多数答案似乎都把事情复杂化了。这种方法对我来说一直很可靠:

FULLPATH=$(readlink -f $0)
INCPATH=${FULLPATH%/*}

INCPATH 将保存脚本的完整路径,不包括脚本文件名,无论脚本如何调用(通过 $PATH、相对或绝对)。

之后,只需要将文件包含在同一目录中即可:

. $INCPATH/file_to_include.sh

参考:TecPorto / Location independent includes

答案 19 :(得分:0)

根据man hier,适合脚本包含的位置是/usr/local/lib/

  

/ usr / local / lib

     

与本地安装的程序相关联的文件。

我个人更喜欢/usr/local/lib/bash/includes进行包含。 有bash-helper个库以这种方式包含库:

#!/bin/bash

. /usr/local/lib/bash/includes/bash-helpers.sh
STATUS_ALIGN=30

include api-client || exit 1                   # include shared functions
include mysql-status/query-builder || exit 1   # include script functions

# include script functions with status message
include mysql-status/process-checker; status 'process-checker' $? || exit 1
include mysql-status/nonexists; status 'nonexists' $? || exit 1

enter image description here

答案 20 :(得分:0)

我们只需找出存储incl.sh和main.sh的文件夹;只需更改你的main.sh:

main.sh

#!/bin/bash

SCRIPT_NAME=$(basename $0)
SCRIPT_DIR="$(echo $0| sed "s/$SCRIPT_NAME//g")"
source $SCRIPT_DIR/incl.sh

echo "The main script"

答案 21 :(得分:-5)

您也可以使用:

PWD=$(pwd)
source "$PWD/inc.sh"