Как лучше включить другие скрипты?

252

Как обычно вы включаете 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 должен быть переносимым?

Теги:

18 ответов

181
Лучший ответ

Я стараюсь, чтобы мои скрипты были относительно друг другу. Таким образом, я могу использовать dirname:

#!/bin/sh

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

"$my_dir/other_script.sh"
  • 3
    Это не будет работать, если скрипт выполняется через $ PATH. тогда which $0 будет полезен
  • 34
    Не существует надежного способа определения местоположения сценария оболочки, см. Mywiki.wooledge.org/BashFAQ/028
Показать ещё 5 комментариев
135

Я знаю, что опаздываю на вечеринку, но это должно работать независимо от того, как вы начинаете 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

  • 2
    dir=$(dirname $o) может быть переписан для использования вашей реализации ${BASH_SOURCE%/*} , что проще. Но я бы источник как . $dir/incl.sh и . $dir/main.sh
  • 0
    Я обновил код, чтобы реализовать ваше предложение.
Показать ещё 10 комментариев
48

Альтернатива:

scriptPath=$(dirname $0)

является:

scriptPath=${0%/*}

.. Преимущество заключается в отсутствии зависимости от dirname, которая не является встроенной командой (и не всегда доступна в эмуляторах)

  • 2
    basePath=$(dirname $0) дал мне пустое значение, когда исходный файл скрипта получен.
33

Если он находится в том же каталоге, вы можете использовать dirname $0:

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"
  • 2
    Две ловушки: 1) $0 - это ./t.sh и dirname возвращается . ; 2) после cd bin вернул . не является правильным. $BASH_SOURCE не лучше.
  • 0
    другая ловушка: попробуйте это с пробелами в имени каталога.
Показать ещё 1 комментарий
22

Я думаю, что лучший способ сделать это - использовать способ Криса Борана, НО вы должны вычислить 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, она работает.

  • 0
    Хорошее и простое решение, и отлично работает для меня во всех возможных вариантах вызова сценариев. Благодарю.
  • 0
    Помимо проблем с отсутствующими кавычками, существует ли какой-либо фактический вариант использования, в котором вы хотите разрешить символические ссылки, а не использовать $0 напрямую?
Показать ещё 2 комментария
17
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"
  • 1
    Я подозреваю, что вы проголосовали за "cd ...", когда dirname "$ 0" должно выполнить то же самое ...
  • 7
    Этот код будет возвращать абсолютный путь, даже если скрипт выполняется из текущего каталога. Только $ (dirname "$ 0") вернет просто "."
Показать ещё 1 комментарий
10

Это работает, даже если найден script:

source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"
  • 0
    Не могли бы вы объяснить, какова цель "[0]"?
  • 1
    @Ray BASH_SOURCE - это массив путей, соответствующих стеку вызовов. Первый элемент соответствует самому последнему сценарию в стеке, который является текущим выполняемым сценарием. На самом деле, $BASH_SOURCE вызываемый как переменная, по умолчанию расширяется до своего первого элемента, поэтому здесь [0] не требуется. Смотрите эту ссылку для деталей.
7

Все эти решения не работают для меня.

Пожалуйста, используйте более надежный метод:

#!/bin/bash

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

# This directory path
DIR=`dirname "${THIS}"`

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

Он поддерживает:

  • Пробелы в пути
  • Ссылки (через readlink)
  • ${BASH_SOURCE[0]} более надежный, чем $0
  • 1
    THIS = readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0 если ваша ссылка для чтения - BusyBox v1.01
  • 0
    @AlexxRoche спасибо! Будет ли это работать на всех Linux?
Показать ещё 2 комментария
7

Вам нужно указать расположение других скриптов, нет другого пути. Я бы рекомендовал настраиваемую переменную в верхней части вашего script:

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

. $installpath/incl.sh

echo "The main script"

В качестве альтернативы вы можете настаивать на том, чтобы пользователь поддерживал переменную окружения, указывающую, где находится ваша домашняя страница программы, например PROG_HOME или что-то подобное. Это может быть предоставлено пользователю автоматически, создав script с этой информацией в файле /etc/profile.d/, который будет использоваться каждый раз, когда пользователь войдет в систему.

  • 1
    Я ценю стремление к конкретности, но не могу понять, почему требуется указывать полный путь, если сценарии включения не являются частью другого пакета. Я не вижу загрузки разницы в безопасности по определенному относительному пути (то есть тому же каталогу, где выполняется скрипт) по сравнению с конкретным полным путем. Почему вы говорите, что нет пути?
  • 4
    Поскольку каталог, в котором выполняется ваш скрипт, не обязательно находится в том месте, где находятся скрипты, которые вы хотите включить в ваш скрипт. Вы хотите загрузить сценарии, в которых они установлены, и нет надежного способа определить, где они находятся во время выполнения. Не использовать фиксированное местоположение - это также хороший способ включить неверный (т.е. предоставленный хакером) скрипт и запустить его.
5

Я бы предположил, что вы создаете setenv script, единственной целью которого является предоставление мест для различных компонентов в вашей системе.

Все остальные сценарии будут затем ссылаться на этот script, чтобы все местоположения были общими для всех скриптов, используя setenv script.

Это очень полезно при запуске cronjob. Вы получаете минимальную среду при запуске cron, но если вы сделаете все cron-скрипты сначала включением setenv script, тогда вы сможете контролировать и синхронизировать среду, в которой вы хотите выполнить cronjobs.

Мы использовали такую ​​технику на нашей обезьяне сборки, которая использовалась для непрерывной интеграции по проекту около 2000 kSLOC.

3

Ответ на скрипт определенно является правильной техникой, но он должен быть реорганизован так, чтобы ваша переменная installpath находилась в отдельной среде script, где были сделаны все такие объявления.

Затем все исходники скриптов, которые script и должны установить путь к изменению, вам нужно только изменить его в одном месте. Делает больше вещей, надежных и надежных. Боже, я ненавижу это слово! (-:

Кстати. Вы должны действительно ссылаться на переменную с помощью ${installpath} при ее использовании так, как показано в вашем примере:

. ${installpath}/incl.sh

Если фигурные скобки опущены, некоторые оболочки будут пытаться и расширять переменную "installpath/incl.sh"!

1

Это должно работать надежно:

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

source_relative incl.sh
1

Конечно, каждому свое, но я думаю, что блок ниже довольно солидный. Я считаю, что это означает "лучший" способ найти каталог и "лучший" способ вызвать другой bash script:

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

echo "The main script"

Таким образом, это может быть "лучший" способ включить другие скрипты. Это основано на другом "лучшем" ответе, что сообщает bash script, где он хранится

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

... запускается, но не меняет среду по желанию.

0

нам просто нужно найти папку, в которой хранятся наши вложенные и 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"
  • 0
    Неправильное цитирование, ненужное использование echo и некорректное использование опции sed g . -1.
  • 0
    В любом случае, чтобы получить этот сценарий, выполните: SCRIPT_DIR=$(echo "$0" | sed "s/${SCRIPT_NAME}//") и затем source "${SCRIPT_DIR}incl.sh"
0

Использование источника или $0 не даст вам реальный путь к вашему script. Вы можете использовать идентификатор процесса script для извлечения его реального пути

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

Я использую этот script, и он всегда хорошо меня обслуживал:)

  • 0
    художественное произведение...
0

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.

  • 3
    12 килобайт скрипта Bash, содержащего более 100 строк eval кода для загрузки зависимостей. ой
  • 0
    @ l0b0 Если вы имеете в виду сценарии, представленные там, eval'ed-код применяется только в качестве справки для общей формы. Баш и другие современные снаряды не нуждаются в этом. Я полагаю, вы читали универсальный, т.е. loader.sh ?
Показать ещё 7 комментариев
-1

Вы также можете использовать:

PWD=$(pwd)
source "$PWD/inc.sh"
  • 9
    Вы предполагаете, что находитесь в том же каталоге, где расположены сценарии. Это не сработает, если вы находитесь где-то еще.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню