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

通常包含脚本的方式是"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脚本,它将无法找到包含,除非它在您的路径中。

确保脚本能够找到包含脚本的好方法是什么,特别是在脚本需要可移植的情况下?

388231 次浏览
我倾向于让我的脚本都是相对的。 这样我就可以使用dirname:

#!/bin/sh


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


"$my_dir/other_script.sh"

如果在同一个目录中,你可以使用dirname $0:

#!/bin/bash


source $(dirname $0)/incl.sh


echo "The main script"

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

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

这在运行cronjobs时非常有用。当运行cron时,你会得到一个最小的环境,但是如果你让所有的cron脚本首先包含setenv脚本,那么你就能够控制和同步你想要cronjob在其中执行的环境。

我们在构建猴子上使用了这样的技术,用于跨大约2000 kSLOC的项目的持续集成。

您需要指定其他脚本的位置,没有其他方法可以绕过它。我建议在你的脚本顶部设置一个可配置的变量:

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


. $installpath/incl.sh


echo "The main script"

或者,您可以坚持让用户维护一个环境变量来指示您的程序所在的位置,比如PROG_HOME或类似的变量。这可以通过在/etc/profile中创建带有该信息的脚本自动提供给用户D /,它将在用户每次登录时被引用。

替代:

scriptPath=$(dirname $0)

是:

scriptPath=${0%/*}

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

Steve的回答绝对是正确的技术,但它应该被重构,以便您的installpath变量在一个单独的环境脚本中,所有这些声明都是在该脚本中进行的。

然后,所有脚本都以该脚本为源,如果安装路径发生更改,则只需在一个位置更改它。让事情更,呃,不受未来影响。天啊,我讨厌这个词!(-):

顺便说一句,当你以你的例子中所示的方式使用它时,你应该使用${installpath}引用变量:

. ${installpath}/incl.sh

如果省略大括号,一些shell将尝试展开变量“installpath/ include .sh”!

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

Shell Script Loader是我的解决方案。

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

它适用于bashkshpd kshzsh与每个优化脚本;和其他与原始sh兼容的shell,如破折号传家宝sh等,通过一个通用脚本自动优化其函数,这取决于shell可以提供的特性。

(前进牌汽车的例子)

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

我把我所有的启动脚本放在。bashrc。d目录。 这是在/etc/profile.之类的地方常用的技术d等。< / p >
while read file; do source "${file}"; done <<HERE
$(find ${HOME}/.bashrc.d -type f)
HERE

使用通配符解决方案的问题…

for file in ${HOME}/.bashrc.d/*.sh; do source ${file};done
< p >…你的文件列表可能太长了。 一种方法是……< / p >
find ${HOME}/.bashrc.d -type f | while read file; do source ${file}; done

…运行但不按预期改变环境。

你还可以使用:

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

我认为最好的方法是使用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中的符号链接访问你的脚本,它就可以工作。

我知道我迟到了,但这应该工作,无论你如何开始脚本和使用内置专属:

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

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

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

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

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

我们只需要找到include .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"

当然,每个人都有自己的,但我认为下面的块是相当扎实的。我相信这涉及到查找目录的“最佳”方式,以及调用另一个bash脚本的“最佳”方式:

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


echo "The main script"

因此,这可能是包含其他脚本的“最佳”方式。这是基于另一个“最佳”答案告诉bash脚本它的存储位置

这应该可靠地工作:

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


source_relative incl.sh

即使脚本是原始的,这也是有效的:

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

这个问题的答案的组合提供了最健壮的解决方案。

它在产品级脚本中工作,支持依赖关系和目录结构:

#!/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更健壮

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

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

3.最可靠

或者,在极少数情况下,这些方法都不起作用,下面是万无一失的方法:

# 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中看到它的作用

希望这能帮助到一些人:)

此外,如果其中一个不适合你,请留下评论,并提到你的操作系统和模拟器。谢谢!

我个人把所有的库都放在lib文件夹中,并使用import函数来加载它们。

文件夹结构

enter image description here

script.sh内容

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

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

IMPORTED="$BASH_SOURCE"

现在你可以从script.sh访问utils.shrequirements.sh中的函数

待办事项:编写一个链接器来构建一个sh文件

根据man hier,适合脚本的地方包括/usr/local/lib/

/usr/local/lib

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

就我个人而言,我更喜欢/usr/local/lib/bash/includes包含。 有bash-helper lib用于以这种方式包含库:

#!/bin/bash


. /usr/local/lib/bash/includes/bash-helpers.sh


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

bash-helpers includes status output

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

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

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

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

. $INCPATH/file_to_include.sh

参考:TecPorto /位置独立包含