Ответ 1
Я стараюсь, чтобы мои скрипты были относительно друг другу. Таким образом, я могу использовать dirname:
#!/bin/sh
my_dir="$(dirname "$0")"
"$my_dir/other_script.sh"
Как обычно вы включаете script с "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
... Теперь, если вы попытаетесь выполнить эту оболочку script из другого места, она не сможет найти include, если она не указана в вашем пути.
Какой хороший способ обеспечить, чтобы ваш script мог найти include script, особенно если, например, script должен быть переносимым?
Я стараюсь, чтобы мои скрипты были относительно друг другу. Таким образом, я могу использовать dirname:
#!/bin/sh
my_dir="$(dirname "$0")"
"$my_dir/other_script.sh"
Я знаю, что опаздываю на вечеринку, но это должно работать независимо от того, как вы начинаете script и используете исключительно встроенные функции:
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"
.
(dot) команда является псевдонимом для источника, $PWD
- это путь для рабочего каталога, BASH_SOURCE
- это переменная массива, члены которой являются исходными именами файлов, ${string%substring}
разбивает кратчайшее соответствие $substring из назад $string
Альтернатива:
scriptPath=$(dirname $0)
является:
scriptPath=${0%/*}
.. Преимущество заключается в отсутствии зависимости от dirname, которая не является встроенной командой (и не всегда доступна в эмуляторах)
Если он находится в том же каталоге, вы можете использовать dirname $0
:
#!/bin/bash
source $(dirname $0)/incl.sh
echo "The main script"
Я думаю, что лучший способ сделать это - использовать способ Криса Борана, НО вы должны вычислить MY_DIR следующим образом:
#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh
Чтобы процитировать страницы man для 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
неправильно вычисляется. Если вы используете script через символическую ссылку в $PATH
, она работает.
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/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
Это работает, даже если найден script:
source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"
Вам нужно указать расположение других скриптов, нет другого пути. Я бы рекомендовал настраиваемую переменную в верхней части вашего script:
#!/bin/bash
installpath=/where/your/scripts/are
. $installpath/incl.sh
echo "The main script"
В качестве альтернативы вы можете настаивать на том, чтобы пользователь поддерживал переменную окружения, указывающую, где находится ваша домашняя страница программы, например PROG_HOME или что-то подобное. Это может быть предоставлено пользователю автоматически, создав script с этой информацией в файле /etc/profile.d/, который будет использоваться каждый раз, когда пользователь войдет в систему.
Я изучил почти каждое предложение, и вот самое подходящее, которое сработало для меня:
script_root=$(dirname $(readlink -f $0))
Он работает даже тогда, когда скрипт связан с каталогом $PATH
.
Смотрите это в действии здесь: https://github.com/pendashteh/hcagent/blob/master/bin/hcagent
# Copyright https://stackoverflow.com/a/13222994/257479
script_root=$(ls -l /proc/$$/fd | grep "255 ->" | sed -e 's/^.\+-> //')
Это на самом деле из другого ответа на этой самой странице, но я тоже добавляю его в свой ответ!
В качестве альтернативы, в редком случае, когда это не сработало, вот пуленепробиваемый подход:
# 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.
Надеюсь, это поможет кому-то там :)
Кроме того, пожалуйста, оставьте его в качестве комментария, если он не работает для вас, и укажите вашу операционную систему и эмулятор. Спасибо!
Я бы предположил, что вы создаете setenv script, единственной целью которого является предоставление мест для различных компонентов в вашей системе.
Все остальные сценарии будут затем ссылаться на этот script, чтобы все местоположения были общими для всех скриптов, используя setenv script.
Это очень полезно при запуске cronjob. Вы получаете минимальную среду при запуске cron, но если вы сделаете все cron-скрипты сначала включением setenv script, тогда вы сможете контролировать и синхронизировать среду, в которой вы хотите выполнить cronjobs.
Мы использовали такую технику на нашей обезьяне сборки, которая использовалась для непрерывной интеграции по проекту около 2000 kSLOC.
Ответ на скрипт определенно является правильной техникой, но он должен быть реорганизован так, чтобы ваша переменная installpath находилась в отдельной среде script, где были сделаны все такие объявления.
Затем все исходники скриптов, которые script и должны установить путь к изменению, вам нужно только изменить его в одном месте. Делает больше вещей, надежных и надежных. Боже, я ненавижу это слово! (-:
Кстати. Вы должны действительно ссылаться на переменную с помощью ${installpath} при ее использовании так, как показано в вашем примере:
. ${installpath}/incl.sh
Если фигурные скобки опущены, некоторые оболочки будут пытаться и расширять переменную "installpath/incl.sh"!
Shell Script Loader - это мое решение для этого.
Он предоставляет функцию с именем include(), которую можно много раз вызывать во многих сценариях для ссылки на один Script, но будет загружать только Script один раз. Функция может принимать полные пути или частичные пути (поиск по script осуществляется по пути поиска). Также предоставляется аналогичная функция с именем load(), которая будет безошибочно загружать сценарии.
Он работает для bash, ksh, pd ksh и zsh с оптимизированными сценариями для каждого из них; и другие оболочки, которые в целом совместимы с оригинальной sh, как зола, тире, реликвии sh и т.д., через универсальный Script, который автоматически оптимизирует свои функции в зависимости от возможностей, которые может предоставить оболочка.
[Приведенный пример]
start.sh
Это дополнительный стартер script. Размещение методов запуска здесь является просто удобством и вместо этого может быть помещено в основной Script. Этот Script также не нужен, если скрипты должны быть скомпилированы.
#!/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 ----
То, что лучше всего на них основано, также может быть скомпилировано для создания единственного Script с доступным компилятором.
Здесь используется проект, который использует его: http://sourceforge.net/p/playshell/code/ci/master/tree/. Он может выполняться портативно с или без компиляции скриптов. Компиляция для создания одного Script также может произойти и полезна при установке.
Я также создал более простой прототип для любой консервативной стороны, которая может иметь краткое представление о том, как работает реализация Script: https://sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype.bash. Он небольшой, и каждый может просто включить код в свой основной Script, если он хочет, чтобы их код предназначался для работы с Bash 4.0 или новее, и он также не использует eval
.
Лично поместите все библиотеки в папку lib
и используйте функцию import
для их загрузки.
структура папок
содержание 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"
Теперь у вас есть доступ к функциям внутри utils.sh
и requirements.sh
от script.sh
TODO: написать компоновщик для создания одного файла sh
Я поместил все мои сценарии запуска в каталог .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
... запускается, но не меняет среду по желанию.
Использование источника или $0 не даст вам реальный путь к вашему script. Вы можете использовать идентификатор процесса script для извлечения его реального пути
ls -l /proc/$$/fd |
grep "255 ->" |
sed -e 's/^.\+-> //'
Я использую этот script, и он всегда хорошо меня обслуживал:)
Конечно, каждому свое, но я думаю, что блок ниже довольно солидный. Я считаю, что это означает "лучший" способ найти каталог и "лучший" способ вызвать другой bash script:
scriptdir=`dirname "$BASH_SOURCE"`
source $scriptdir/incl.sh
echo "The main script"
Таким образом, это может быть "лучший" способ включить другие скрипты. Это основано на другом "лучшем" ответе, что сообщает bash script, где он хранится
Это должно работать надежно:
source_relative() {
local dir="${BASH_SOURCE%/*}"
[[ -z "$dir" ]] && dir="$PWD"
source "$dir/$1"
}
source_relative 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"
По словам man hier
подходящее место для сценария включает в себя /usr/local/lib/
/USR/локальные/Библиотека
Файлы, связанные с локально установленными программами.
Лично я предпочитаю /usr/local/lib/bash/includes
для include. Существует библиотека 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
Вы также можете использовать:
PWD=$(pwd)
source "$PWD/inc.sh"