다른 스크립트를 포함하는 것이 가장 좋은 방법은 무엇입니까?


353

일반적으로 스크립트를 포함하는 방법은 "source"

예 :

main.sh :

#!/bin/bash

source incl.sh

echo "The main script"

포함 :

echo "The included script"

"./main.sh"실행 결과는 다음과 같습니다.

The included script
The main script

... 이제 다른 위치에서 해당 셸 스크립트를 실행하려고하면 경로에 포함되어 있지 않으면 포함을 찾을 수 없습니다.

스크립트가 포함 스크립트를 찾을 수 있도록하는 좋은 방법은 무엇입니까? 특히 스크립트를 이식성이 있어야하는 경우?


2
이것을보십시오 : stackoverflow.com/questions/59895/…
Lluís

1
귀하의 질문은 매우 유익하고 유익 하여 귀하의 질문을 하기 전에 제 질문에 대한 답변이 제공되었습니다 ! 좋은 작업!
가브리엘 스테이 플스

답변:


229

나는 내 스크립트를 모두 서로 상대적으로 만드는 경향이 있습니다. 그렇게하면 dirname을 사용할 수 있습니다.

#!/bin/sh

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

"$my_dir/other_script.sh"

5
스크립트가 $ PATH를 통해 실행되면 작동하지 않습니다. 다음 which $0도움이 될 것입니다
휴고

41
쉘 스크립트의 위치를 ​​결정하는 확실한 방법은 없습니다. mywiki.wooledge.org/BashFAQ/028
Philipp

12
@Philipp, 해당 항목의 저자는 정확하고 복잡하며 문제가 있습니다. 그러나 몇 가지 핵심 사항이 누락되었습니다. 먼저 저자는 bash 스크립트로 수행 할 작업에 대해 많은 것을 가정합니다. 파이썬 스크립트가 의존성없이 실행될 것으로 기대하지 않습니다. Bash는 다른 언어로는 어려운 일을 빨리 할 수있는 풀 언어입니다. 빌드 시스템이 작동해야 할 때 실용주의 (그리고 스크립트가 의존성을 찾을 수 없다는 좋은 경고)가 이깁니다.
Aaron H.

11
BASH_SOURCE배열과이 배열의 첫 번째 요소가 항상 현재 소스를 가리키는 방법 에 대해 배웠습니다 .
haridsv

6
스크립트가 다른 위치에 있으면 작동하지 않습니다. 예를 들어, /home/me/main.sh는 dirname이 / home / me를 반환하므로 /home/me/test/inc.sh를 호출합니다. BASH_SOURCE를 사용하는 sacii 답변은 더 나은 솔루션입니다. stackoverflow.com/a/12694189/1000011
opticyclic

187

나는 파티에 늦었다는 것을 알고 있지만 스크립트를 시작하고 내장을 독점적으로 사용하는 방식에 관계없이 작동해야합니다.

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

.(점)이 명령에 대한 별칭입니다 source, $PWD작업 디렉터리에 대한 경로입니다, BASH_SOURCE구성원 소스 파일 이름입니다 배열 변수, ${string%substring}$ 문자열의 뒷면에서 하위 문자열 $의 짧은 경기 스트립


7
이 답변은 스레드에서 유일하게 저에게
Justin

3
@sacii 언제 라인이 if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi필요한지 알 수 있습니까? 명령을 실행하기 위해 bash 프롬프트에 붙여 넣을 경우 필요가 있습니다. 그러나 스크립트 파일 컨텍스트 내에서 실행하면 필요하지 않습니다.
Johnny Wong

2
또한 여러 디렉토리에서 예상대로 작동한다는 점도 주목할 가치가 있습니다 source(즉, 다른 디렉토리에 다른 source스크립트 source등이 있으면 여전히 작동합니다).
Ciro Costa

4
이것은해야하지 ${BASH_SOURCE[0]}당신이 최신이라고 원하는대로? 또한,를 사용 DIR=$(dirname ${BASH_SOURCE[0]})하면 if 조건을 제거 할 수 있습니다
kshenoy

1
이 스 니펫을 CC0 과 같은 속성이 필요없는 라이센스로 사용할 수있게 하시겠습니까, 아니면 공개 도메인으로 릴리스 하시겠습니까? 이 그대로의 표현을 사용하고 싶지만 모든 스크립트의 맨 위에 속성을 표시하는 것은 성가신 일입니다!
BeeOnRope 2

52

대안 :

scriptPath=$(dirname $0)

입니다 :

scriptPath=${0%/*}

.. 내장 명령이 아닌 dirname에 의존하지 않는 이점 (에뮬레이터에서 항상 사용 가능한 것은 아님)


2
basePath=$(dirname $0)포함하는 스크립트 파일이 소싱되면 빈 값을 제공했습니다.
prayagupd

41

동일한 디렉토리에 있으면 다음을 사용할 수 있습니다 dirname $0.

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"

2
두 가지 함정 : 1) $0is ./t.sh및 dirname returns .; 2) cd bin반품 후 .가 정확하지 않습니다. $BASH_SOURCE더 나은 없습니다.
18446744073709551615

또 다른 함정 : 디렉토리 이름에 공백으로 시도하십시오.
허버트 Grzeskowiak

source "$(dirname $0)/incl.sh"이러한 경우에 작동
dsm

27

이 작업을 수행하는 가장 좋은 방법은 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하면 작동합니다.


훌륭하고 간단한 솔루션이며 생각할 수있는 많은 스크립트 호출에서 나에게 유명합니다. 감사.
Brian Cline

따옴표가없는 문제 외에도 $0직접 사용하지 않고 심볼릭 링크를 해결하려는 실제 사용 사례가 있습니까?
l0b0

1
@ l0b0 : 스크립트가 상상 /home/you/script.sh당신은 할 수 cd /home와 거기에서 스크립트를 실행 ./you/script.sh이 경우 dirname $0반환 ./you등 다른 스크립트가 실패합니다
dr.scre

1
그러나 큰 제안은`MY_DIR=$(dirname $(readlink -f $0)); source $MY_DIR/incl.sh
Frederick Ollinger

21

이 질문에 대한 답변을 조합하여 가장 강력한 솔루션을 제공합니다.

의존성과 디렉토리 구조를 크게 지원하는 프로덕션 급 스크립트에서 우리를 위해 일했습니다.

#! / bin / bash

# 현재 스크립트의 전체 경로
THIS =`readlink -f "$ {BASH_SOURCE [0]}"2> / dev / null || echo $ 0`

# 현재 스크립트가있는 디렉토리
DIR =`이름 "$ {THIS}"`

# '점'은 '소스', 즉 '포함'을 의미합니다.
. "$ DIR / compile.sh"

이 방법은 다음을 모두 지원합니다.

  • 경로에 공백
  • 링크 (경유 readlink)
  • ${BASH_SOURCE[0]} 보다 강하다 $0

1
본은 = readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0 당신의 readlink를가 비지 박스의 V1.01 경우
알렉스 로슈

@AlexxRoche 감사합니다! 모든 Linux에서 작동합니까?
Brian Haak

1
나는 그렇게 기대할 것입니다. Debian Sid 3.16 및 QNAP의 armv5tel 3.4.6 Linux에서 작동하는 것 같습니다.
Alexx Roche

2
나는이 한 줄에 넣었습니다.DIR=$(dirname $(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0)) # https://stackoverflow.com/a/34208365/
Acumenus

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

1
"$ 0"이라는 이름이 같은 것을 달성해야 할 때 "cd ..."에 투표를 한 것으로 의심됩니다.
Aaron H.

7
이 코드는 현재 디렉토리에서 스크립트가 실행될 때에도 절대 경로를 반환합니다. $ (dirname "$ 0")만으로도 "."만 반환합니다.
Max

1
그리고 ./incl.sh같은 경로로 확인 cd+ pwd. 디렉토리를 변경하면 어떤 이점이 있습니까?
l0b0 12

디렉토리의 앞뒤로 변경해야 할 때와 같이 스크립트의 절대 경로가 필요할 때가 있습니다.
Laryx Decidua

15

스크립트가 소스 인 경우에도 작동합니다.

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

"[0]"의 목적이 무엇인지 설명해 주시겠습니까?
Ray

1
@Ray BASH_SOURCE는 콜 스택에 해당하는 경로의 배열입니다. 첫 번째 요소는 현재 실행중인 스크립트 인 스택에서 가장 최근의 스크립트에 해당합니다. 실제로, $BASH_SOURCE변수로 불리는 것은 기본적으로 첫 번째 요소로 확장되므로 [0]여기에서는 필요하지 않습니다. 자세한 내용은 이 링크 를 참조하십시오.
Jonathan H

10

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

이것은 실제로이 페이지의 다른 답변에서 온 것이지만 내 답변에도 추가하고 있습니다!

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

https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner 에서 실제로 볼 수 있습니다 taskrunner.

이것이 누군가를 도울 수 있기를 바랍니다 :)

또한 작동하지 않는 경우 주석으로 남겨두고 운영 체제 및 에뮬레이터를 언급하십시오. 감사!


7

다른 스크립트의 위치를 ​​지정해야합니다. 다른 방법은 없습니다. 스크립트 상단에 구성 가능한 변수를 권장합니다.

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

. $installpath/incl.sh

echo "The main script"

또는 사용자가 PROG_HOME 등의 프로그램 홈 위치를 나타내는 환경 변수를 유지하도록 요구할 수 있습니다. 이는 /etc/profile.d/에 해당 정보가 포함 된 스크립트를 작성하여 사용자에게 자동으로 제공 될 수 있습니다.이 정보는 사용자가 로그인 할 때마다 제공됩니다.


1
특이성에 대한 열망에 감사하지만 include 스크립트가 다른 패키지의 일부가 아닌 한 전체 경로가 필요한 이유를 알 수 없습니다. 특정 상대 경로 (예 : 스크립트가 실행되는 동일한 디렉토리)와 특정 전체 경로에서 보안 차이가로드되지 않습니다. 왜 주변에 방법이 없다고 말합니까?
Aaron H.

4
스크립트가 실행되는 디렉토리가 스크립트에 포함하려는 스크립트가있는 위치 일 필요는 없습니다. 스크립트가 설치된 위치에 스크립트를로드하려고하며 런타임 위치를 알 수있는 확실한 방법이 없습니다. 고정 된 위치를 사용하지 않는 것도 잘못된 (예 : 해커 제공) 스크립트를 포함하고 실행하는 좋은 방법입니다.
Steve Baker

6

시스템 전체의 다양한 구성 요소에 대한 위치를 제공하는 것이 유일한 목적인 setenv 스크립트를 작성하는 것이 좋습니다.

그런 다음 다른 모든 스크립트는이 스크립트를 소싱하여 setenv 스크립트를 사용하는 모든 스크립트에서 모든 위치가 공통되도록합니다.

이것은 cronjob을 실행할 때 매우 유용합니다. cron을 실행할 때는 최소한의 환경이 필요하지만 모든 cron 스크립트가 먼저 setenv 스크립트를 포함하게하면 cronjob이 실행될 환경을 제어하고 동기화 할 수 있습니다.

우리는 약 2,000 kSLOC의 프로젝트에서 지속적인 통합을 위해 사용 된 빌드 몽키에서 이러한 기술을 사용했습니다.


3

Steve의 답변은 확실히 올바른 기술이지만 installpath 변수가 모든 선언이 이루어지는 별도의 환경 스크립트에 있도록 리팩터링해야합니다.

그런 다음 모든 스크립트가 해당 스크립트를 소싱하고 installpath 변경을 수행해야하며 한 위치에서만 변경하면됩니다. 더, 더, 미래를 보장합니다. 신 나는 그 단어를 싫어한다! (-:

BTW 다음 예제와 같이 $ {installpath}를 사용하여 변수를 참조해야합니다.

. ${installpath}/incl.sh

중괄호가 생략되면 일부 쉘은 "installpath / incl.sh"변수를 확장하려고 시도합니다!


3

쉘 스크립트 로더 가 내 솔루션입니다.

단일 스크립트를 참조하기 위해 여러 스크립트에서 여러 번 호출 할 수 있지만 스크립트를 한 번만로드하는 include ()라는 함수를 제공합니다. 이 함수는 전체 경로 또는 부분 경로를 허용 할 수 있습니다 (스크립트는 검색 경로에서 검색 됨). 무조건 스크립트를로드하는 load ()라는 비슷한 함수도 제공됩니다.

그것은 작동 배쉬 , KSH , PD KSHzsh을 가진 그들 각각에 대해 최적화 된 스크립트; ash , dash , heirloom sh 등과 같이 원래 sh와 일반적으로 호환되는 다른 쉘 은 쉘이 제공 할 수있는 기능에 따라 기능을 자동으로 최적화하는 범용 스크립트를 통해 제공됩니다.

[향상된 예]

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

# ...

금연 건강 증진 협회

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.


3
eval종속성을로드하기위한 100 줄 이상의 ed 코드를 포함하는 12 킬로바이트의 Bash 스크립트 . Ouch
l0b0

1
eval바닥 근처 의 세 블록 중 하나 가 항상 실행됩니다. 따라서 필요한지 여부에 관계없이 반드시을 사용하고 eval있습니다.
l0b0

3
해당 평가 호출은 안전하며 Bash 4.0 이상인 경우 사용되지 않습니다. 나는 당신 eval이 순수한 악 이라고 생각 하고 대신 그것을 잘 활용하는 방법을 모른다고 생각했던 그 옛날 스크립터 중 하나입니다 .
konsolebox

1
"사용되지 않음"이 무슨 뜻인지 모르겠지만 실행됩니다. 그리고 내 일의 일부로 몇 년 동안 쉘 스크립팅을 한 후에, 나는 그 어느 때보 다 더 확신 eval합니다.
l0b0

1
파일을 플래그 지정하는 두 가지 이식 가능하고 간단한 방법은 즉각 inode 번호로 참조하거나 NUL로 구분 된 경로를 파일에 넣는 것입니다.
l0b0

2

모든 라이브러리를 개인적으로 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.shrequirements.sh에서를script.sh

TODO : 링커를 작성하여 단일 sh파일 작성


이것은 또한 스크립트가있는 디렉토리 외부에서 스크립트를 실행하는 문제를 해결합니까?
Aaron H.

@AaronH. 아니요. 대규모 프로젝트에 종속성을 포함시키는 체계적인 방법입니다.
Xaqron

1

source 또는 $ 0을 사용해도 스크립트의 실제 경로는 제공되지 않습니다. 스크립트의 프로세스 ID를 사용하여 실제 경로를 검색 할 수 있습니다

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

나는이 스크립트를 사용하고 있으며 항상 나에게 잘 봉사했다 :)


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

... 실행되지만 원하는대로 환경을 변경하지는 않습니다.


1

물론, 그들 각자에게, 그러나 아래 블록은 꽤 견고하다고 생각합니다. 나는 이것이 디렉토리를 찾는 "최상의"방법과 다른 bash 스크립트를 호출하는 "최상의"방법을 포함한다고 믿는다 :

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

echo "The main script"

따라서 이것은 다른 스크립트를 포함시키는 "최상의"방법 일 수 있습니다. 이것은 bash 스크립트가 어디에 저장되어 있는지 알려주는 또 다른 "최상의"답변을 기반으로합니다.


1

이것은 안정적으로 작동해야합니다.

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

source_relative incl.sh

0

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"

잘못된 인용, 불필요한 사용 echo, 그리고 잘못된 사용 sedg옵션을 선택합니다. -1.
l0b0

: 어쨌든,이 스크립트 작업 할 일을 얻기 위해 SCRIPT_DIR=$(echo "$0" | sed "s/${SCRIPT_NAME}//")다음과source "${SCRIPT_DIR}incl.sh"
Krzysiek

0

man hier스크립트 에 적합한 장소는 다음과 같습니다./usr/local/lib/

/ usr / local / lib

로컬로 설치된 프로그램과 관련된 파일

개인적으로 나는 /usr/local/lib/bash/includes포함을 선호 합니다. 이 bash는 헬퍼 그 방법에 libs와 등을위한 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는 상태 출력을 포함합니다


-5

다음을 사용할 수도 있습니다.

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

9
스크립트가있는 동일한 디렉토리에 있다고 가정합니다. 다른 곳에 있으면 작동하지 않습니다.
Luc M
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.