通常包含脚本的方式是"source"
eg:
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脚本,它将无法找到包含,除非它在您的路径中。
确保脚本能够找到包含脚本的好方法是什么,特别是在脚本需要可移植的情况下?
Shell脚本加载器是我的解决方案。
它提供了一个名为include()的函数,可以在许多脚本中多次调用该函数以引用单个脚本,但只加载脚本一次。该函数可以接受完整路径或部分路径(脚本在搜索路径中搜索)。还提供了一个名为load()的类似函数,它将无条件加载脚本。
它适用于bash, ksh, pd ksh和zsh与优化脚本为他们每一个;以及其他与原始sh兼容的shell,如ash、dash、传家宝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。
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文件夹中,并使用导入函数来加载它们。
文件夹结构
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.sh和requirements.sh中的函数
待办事项:编写一个链接器来构建一个sh文件
根据我的说法,脚本包含的合适位置是/usr/local/lib/
/usr/local/lib
与本地安装程序相关联的文件。
我个人更喜欢/usr/local/lib/bash/includes。
有bash-helper库用于以这种方式包含库:
#!/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
这是一个很好的函数。它建立在@sacii所做的基础上。谢谢你!
它将允许您列出任意数量的空格分隔的脚本名称到source(相对于调用source_files的脚本)。
可选的是,你可以传递一个绝对路径或相对路径作为第一个参数,它将从那里来源。
您可以多次调用它(参见下面的示例)以从不同的dirs中获取脚本
#!/usr/bin/env bash
function source_files() {
local scripts_dir
scripts_dir="$1"
if [ -d "$scripts_dir" ]; then
shift
else
scripts_dir="${BASH_SOURCE%/*}"
if [[ ! -d "$scripts_dir" ]]; then scripts_dir="$PWD"; fi
fi
for script_name in "$@"; do
# shellcheck disable=SC1091 disable=SC1090
. "$scripts_dir/$script_name.sh"
done
}
下面是一个示例,您可以运行来展示如何使用它
#!/usr/bin/env bash
function source_files() {
local scripts_dir
scripts_dir="$1"
if [ -d "$scripts_dir" ]; then
shift
else
scripts_dir="${BASH_SOURCE%/*}"
if [[ ! -d "$scripts_dir" ]]; then scripts_dir="$PWD"; fi
fi
for script_name in "$@"; do
# shellcheck disable=SC1091 disable=SC1090
. "$scripts_dir/$script_name.sh"
done
}
## -- EXAMPLE -- ##
# assumes dir structure:
# /
# source_files.sh
# sibling.sh
# scripts/
# child.sh
# nested/
# scripts/
# grandchild.sh
cd /tmp || exit 1
# sibling.sh
tee sibling.sh <<- EOF > /dev/null
#!/usr/bin/env bash
export SIBLING_VAR='sibling var value'
EOF
# scripts/child.sh
mkdir -p scripts
tee scripts/child.sh <<- EOF > /dev/null
#!/usr/bin/env bash
export CHILD_VAR='child var value'
EOF
# nested/scripts/grandchild.sh
mkdir -p nested/scripts
tee nested/scripts/grandchild.sh <<- EOF > /dev/null
#!/usr/bin/env bash
export GRANDCHILD_VAR='grandchild var value'
EOF
source_files 'sibling'
source_files 'scripts' 'child'
source_files 'nested/scripts' 'grandchild'
echo "$SIBLING_VAR"
echo "$CHILD_VAR"
echo "$GRANDCHILD_VAR"
rm sibling.sh
rm -rf scripts nested
cd - || exit 1
打印:
sibling var value
child var value
grandchild var value