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

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

Ответ 1

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

#!/bin/sh

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

"$my_dir/other_script.sh"

Ответ 2

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

Ответ 3

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

scriptPath=$(dirname $0)

является:

scriptPath=${0%/*}

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

Ответ 4

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

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"

Ответ 5

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

Ответ 6

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

Ответ 7

Комбинация ответов на этот вопрос обеспечивает наиболее надежное решение.

Он работал для нас в сценариях промышленного уровня с отличной поддержкой зависимостей и структуры каталогов:

#!/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

Ответ 8

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

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

Ответ 9

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

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

. $installpath/incl.sh

echo "The main script"

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

Ответ 10

1. Neatest

Я изучил почти каждое предложение, и вот самое подходящее, которое сработало для меня:

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.

Надеюсь, это поможет кому-то там :)

Кроме того, пожалуйста, оставьте его в качестве комментария, если он не работает для вас, и укажите вашу операционную систему и эмулятор. Спасибо!

Ответ 11

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

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

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

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

Ответ 12

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

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

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

. ${installpath}/incl.sh

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

Ответ 13

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.

Ответ 14

Лично поместите все библиотеки в папку 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"

Теперь у вас есть доступ к функциям внутри utils.sh и requirements.sh от script.sh

TODO: написать компоновщик для создания одного файла sh

Ответ 15

Я поместил все мои сценарии запуска в каталог .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

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

Ответ 16

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

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

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

Ответ 17

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

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

echo "The main script"

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

Ответ 18

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

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

source_relative incl.sh

Ответ 19

нам просто нужно найти папку, в которой хранятся наши вложенные и 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"

Ответ 20

По словам 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

enter image description here

Ответ 21

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

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