소스 쉘 스크립트의 경로 결정


80

소스 쉘 스크립트가 자체 경로를 찾는 방법이 있습니까? tcsh를 사용하는 동료가 있지만 주로 bash에 관심이 있습니다.

소싱으로 인해 현재 쉘에서 명령이 실행되므로 $0소스 스크립트가 아닌 현재 쉘의 호출 이므로 여전히 운 이 좋지 않을 것입니다. 내 최선의 생각은 현재하는 source $script $script것이므로 첫 번째 위치 매개 변수에 필요한 정보가 포함됩니다. 더 나은 방법이 있습니까?

분명히하기 위해 스크립트를 실행하지 않고 소싱 하고 있습니다.

source foo.bash

관련 질문이 4200 개 이상 있습니다. stackoverflow.com/q/59895/52074
Trevor Boyd Smith

답변:


65

에서는 tcsh, $_스크립트의 시작 부분에 파일이 공급 된 경우 위치를 포함 할 것이고, $0그것이 실행 된 경우를 포함한다.

#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
    echo "run $0"
endif

배쉬에서 :

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"

방금 tcsh에서 이것을 사용할 기회가 있었고 shebang 없이는 작동하지 않는 것으로 나타났습니다. 당신이 그것을 소싱하고 실행하지 않으면 행동이 바뀌는 것이 조금 이상해 보입니다 ...
Cascabel

스크립트가 비 대화식으로 소스 된 경우 (예 : cshrc에서) tcsh 버전도 작동하지 않는 것 같습니다. 이 경우 정보를 얻는 방법을 찾지 못하는 것 같습니다. 이견있는 사람?
Cascabel

소싱은 shebang없이 저에게 효과적입니다. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. 비 대화식으로 소스를 만드는 한 소스 파일은 원래 질문에서 언급 한 것처럼 실제로 파일의 일부인 것처럼 부모 파일에 포함됩니다. 귀하의 위치 매개 변수 해결 방법이 아마도 가장 좋은 방법이라고 생각합니다. 그러나 일반적인 질문은 "왜 그렇게 하시겠습니까?"이고 답장에 대한 일반적인 대답은 "하지 말고 대신 수행 하십시오 "입니다. "this"는 종종 저장됩니다 ...
Dennis Williamson

2
@clacke : 내가 찾을 것을 나는, 4.1.9을 포함, 4.2.37에 2.05b에서 해당 테스트 배쉬의 모든 버전 .source이 점에서 동일했다. 참고 $_에 액세스해야하는 첫번째 파일의 문은, 그렇지 않으면 이전 명령의 마지막 인수를 포함합니다. 나는 자신의 참조를 위해 shebang을 포함하고 싶기 때문에 구문을 강조 표시하는 편집기를 사용하기 위해 어떤 쉘을 사용 해야하는지 알 수 있습니다.
Dennis Williamson

1
하하. 분명히 나는 ​​먼저하고 source나서하고 테스트했다 .. 유능한 것에 대해 사과드립니다. 그들은 실제로 동일합니다. 어쨌든 $BASH_SOURCE작동합니다.
clacke

30

$BASH_SOURCE변수를 사용할 수 있다고 생각 합니다. 실행 된 경로를 반환합니다.

pbm@tauri ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ ./a.sh
./a.sh
pbm@tauri ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ source ./a.sh
./a.sh

따라서 다음 단계에서는 경로가 상대적인지 여부를 확인해야합니다. 상대가 아닌 경우 모든 것이 정상입니다. 이 경우 우리와 경로를 확인할 수 있습니다 pwd로 연결하여 /$BASH_SOURCE.


2
그리고 노트 source에서 검색 $PATH지정된 이름이 포함되지 않은 경우 /. 검색 순서는 쉘 옵션에 따라 다릅니다. 자세한 내용은 설명서를 참조하십시오.
Gilles

1
그렇다면 어떤 mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"것이 효과가 있습니까?
Kevin Cantu

빠르고 유용한 답변입니다. Dennis도 tcsh 답변을 제공하는 녹색 확인 표시를 받았습니다. @Gilles : 그렇습니다, 나는 문서에서 그것을 찾았습니다. 유감스럽게도 유스 케이스에는 거의 걱정할 필요가 없습니다.
Cascabel

17

이 솔루션은 tcsh가 아닌 bash에만 적용됩니다. ${BASH_SOURCE[0]}함수 내에서 경로를 찾으 려면 일반적으로 제공되는 답변 이 작동하지 않습니다.

파일이 소스인지 스크립트로 실행되는지에 관계 없이이 줄이 항상 작동하는 것으로 나타났습니다.

echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

readlink위의 경로에서 심볼릭 링크를 사용하려면 재귀 적으로 또는 비재 귀적으로 사용하십시오.

다음은 시도해보고 다른 제안 된 솔루션과 비교하는 스크립트입니다. source test1/test2/test_script.sh또는 로 호출하십시오 bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

one-liner가 작동하는 이유는 BASH_SOURCE환경 변수와 관련 변수를 사용하여 설명합니다 FUNCNAME.

BASH_SOURCE

멤버가 FUNCNAME 배열 변수의 해당 쉘 함수 이름이 정의 된 소스 파일 이름 인 배열 변수입니다. 쉘 함수 $ {FUNCNAME [$ i]}은 $ {BASH_SOURCE [$ i]} 파일에 정의되어 있으며 $ {BASH_SOURCE [$ i + 1]}에서 호출되었습니다.

기능 명

현재 실행 호출 스택에있는 모든 쉘 함수의 이름을 포함하는 배열 변수입니다. 인덱스가 0 인 요소는 현재 실행중인 쉘 함수의 이름입니다. 맨 아래 요소 (인덱스가 가장 높은 요소)는 "main"입니다. 이 변수는 쉘 기능이 실행될 때만 존재합니다. FUNCNAME에 대한 지정은 적용되지 않으며 오류 상태를 리턴합니다. FUNCNAME을 설정하지 않으면 나중에 다시 설정하더라도 특수 속성이 손실됩니다.

이 변수는 BASH_LINENO 및 BASH_SOURCE와 함께 사용할 수 있습니다. FUNCNAME의 각 요소에는 호출 스택을 설명하기 위해 BASH_LINENO 및 BASH_SOURCE에 해당 요소가 있습니다. 예를 들어 $ {FUNCNAME [$ i]}은 (는) 행 번호 $ {BASH_LINENO [$ i]}의 $ {BASH_SOURCE [$ i + 1]} 파일에서 호출되었습니다. 발신자 기본 제공은이 정보를 사용하여 현재 호출 스택을 표시합니다.

[출처 : 배쉬 매뉴얼]


이 솔루션은 bash에서 나를 위해 일했지만 선택한 답변은 간헐적으로 만 작동했습니다. 나는 왜 그것이 다른 사람들이 아닌 때로는 효과가 있었는지 알지 못했습니다.
Jim2B

17

철저하고 검색을 위해 여기에있는 일이 있습니다. 이것은 커뮤니티 위키이므로 다른 쉘의 해당 항목을 자유롭게 추가하십시오 (분명히 $ BASH_SOURCE는 다를 것입니다).

test.sh :

#! /bin/sh
called=$_
echo $called
echo $_
echo $0
echo $BASH_SOURCE

test2.sh :

#! /bin/sh
source ./test.sh

세게 때리다:

$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh

대시

$./test2.sh
./test2.sh
./test2.sh
./test2.sh

$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh

$

Zsh

$ ./test2.sh
./test.sh
./test.sh
./test.sh

$ zsh test.sh

echo
test.sh

$

1
이해가 안됩니다 : 왜 called=$_; echo $called; echo $_? 이게 $_두 번 인쇄되지 않습니까?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

5
@CiroSantilli : 항상 그런 것은 아니지만 특수 매개 변수 에 대한 Bash 매뉴얼 을 읽으십시오 $_. "쉘 시작시 환경 또는 인수 목록에 전달 될 때 실행되는 쉘 또는 쉘 스크립트를 호출하는 데 사용되는 절대 경로 이름으로 설정하십시오. 이후 마지막으로 확장됩니다. 확장 후 이전 명령에 대한 인수. 또한 해당 명령으로 내 보낸 환경에서 실행 및 배치 된 각 명령을 호출하는 데 사용되는 전체 경로 이름으로 설정합니다. 메일을 확인할 때이 매개 변수는 메일 파일의 이름을 보유합니다. "
Adam Rosenfield

이 문제는 소스 파일에 헤더 #! /bin/sh가있어 소스에 쓸모가 없습니다. 그러면 새로운 인스턴스가 시작되고 /bin/sh변수가 설정되고 해당 인스턴스가 종료되어 호출 인스턴스가 변경되지 않습니다.
JamesThomasMoon1979

2
@ JamesThomasMoon1979 : 무슨 소리 야? 로 시작 아무것도 #쉘 스크립트에서 주석이다.  #!(shebang)은 실행 되는 스크립트의 첫 번째 줄로만 특별한 의미를 갖습니다 . 소스   파일의 첫 줄은 단지 주석 일뿐입니다.
스콧

13

이것은 bash, dash, ksh 및 zsh에서 나를 위해 일했습니다.

if test -n "$BASH" ; then script=$BASH_SOURCE
elif test -n "$TMOUT"; then script=${.sh.file}
elif test -n "$ZSH_NAME" ; then script=${(%):-%x}
elif test ${0##*/} = dash; then x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
else script=$0
fi

echo $script

이 쉘에 대한 출력 :

BASH source: ./myscript
ZSH source: ./myscript
KSH source: /home/pbrannan/git/theme/src/theme/web/myscript
DASH source: /home/pbrannan/git/theme/src/theme/web/myscript
BASH: ./myscript
ZSH: ./myscript
KSH: /home/pbrannan/git/theme/src/theme/web/myscript
DASH: ./myscript

csh / tcsh에서 작동 시키려고했지만 너무 어렵습니다. POSIX를 고수하고 있습니다.


1

나는 커뮤니티 위키 답변 (숀 J. 고프의)에 약간 혼란 스러웠으므로 물건을 정리하는 스크립트를 작성했습니다. 에 대해 $_, 나는 이것을 발견했다 : 환경 변수로서의 사용법_명령에 전달되었다 . 환경 변수이므로 값을 잘못 테스트하기 쉽습니다.

아래는 스크립트이며 출력입니다. 그들은 또한 이 요지에있다 .

test-shell-default-variables.sh

#!/bin/bash

# test-shell-default-variables.sh

# Usage examples (you might want to `sudo apt install zsh ksh`):
#
#  ./test-shell-default-variables.sh dash bash
#  ./test-shell-default-variables.sh dash bash zsh ksh
#  ./test-shell-default-variables.sh dash bash zsh ksh | less -R

# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.


# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.

# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.

echolor() {
    echo -e "\e[1;36m$@\e[0m"
}

tell_file() {
    echo File \`"$1"\` is:
    echo \`\`\`
    cat "$1"
    echo \`\`\`
    echo
}

SHELL_ARRAY=("$@")

test_command() {
    for shell in "${SHELL_ARRAY[@]}"
    do
        prepare "$shell"
        cmd="$(eval echo $1)"
        # echo "cmd: $cmd"
        printf '%-4s: ' "$shell"
        { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
        teardown
    done
    echo
}

prepare () {
    shell="$1"
    PATH="$PWD/$shell/sh:$PATH"
}

teardown() {
    PATH="${PATH#*:}"
}


###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
    mkdir "$shell"
    ln -sT "/bin/$shell" "$shell/sh"
done

echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"

tell_file sourcer.sh

###
### run
###
test_expression() {
    local expr="$1"

    # prepare
    echo "echo $expr" > printer.sh
    tell_file printer.sh

    # run
    cmd='$shell ./printer.sh'
    echolor "\`$cmd\` (simple invocation) ($expr):"
    test_command "$cmd"

    # cmd='sh ./printer.sh'
    # echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./sourcer.sh'
    echolor "\`$cmd\` (via sourcing) ($expr):"
    test_command "$cmd"

    # cmd='sh ./sourcer.sh'
    # echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./linked.sh'
    echolor "\`$cmd\` (via symlink) ($expr):"
    test_command "$cmd"

    # cmd='sh ./linked.sh'
    # echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    echolor "------------------------------------------"
    echo
}

test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'

###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
    rm "$shell/sh"
    rm -d "$shell"
done

rm sourcer.sh
rm linked.sh
rm printer.sh

출력 ./test-shell-default-variables.sh {da,ba,z,k}sh

File `sourcer.sh` is:
```
. ./printer.sh
```

File `printer.sh` is:
```
echo $BASH_SOURCE
```

`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash: 
bash: ./linked.sh
zsh : 
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $0
```

`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh

`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh

`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh

------------------------------------------

File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```

`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $_
```

`$shell ./printer.sh` (simple invocation) ($_):
dash: 
bash: bash
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($_):
dash: 
bash: bash
zsh : ./printer.sh
ksh : 

`$shell ./linked.sh` (via symlink) ($_):
dash: 
bash: bash
zsh : 
ksh : 

------------------------------------------

우리는 무엇을 배웠습니까?

$BASH_SOURCE

  • $BASH_SOURCE bash와 bash에서만 작동합니다.
  • 유일한 차이점 $0은 현재 파일이 다른 파일에 의해 제공되었을 때입니다. 이 경우 $BASH_PROFILE소스 파일 이름이 아닌 소스 파일 이름이 포함됩니다.

$0

  • zsh을에서는 $0같은 값 갖는다 $BASH_SOURCE떠들썩한 파티에서를.

$_

  • $_ 대시와 ksh는 그대로 유지됩니다.
  • bash 및 zsh $_에서 마지막 호출의 마지막 인수로 붕괴합니다.
  • bash $_는 "bash"로 초기화 됩니다.
  • zsh는 $_그대로 유지됩니다. (소싱 할 때 "마지막 인수"규칙의 결과 일뿐입니다).

심볼릭 링크

  • 스크립트가 심볼릭 링크를 통해 호출되면 변수에는 링크 대상에 대한 참조가 포함되며 이름 만 포함됩니다.

ksh

  • 이러한 테스트와 관련하여 ksh는 대시처럼 동작합니다.

  • bash 또는 zsh가 sh테스트와 관련하여 이라는 symlink를 통해 호출되면 대시처럼 작동합니다.

0

bash 쉘의 경우 @Dennis Williamson의 답변이 가장 도움이되었지만의 경우에는 작동하지 않았습니다 sudo. 이것은 :

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi

0

if 문을 사용하지 않고 bash 및 zsh 호환 스크립트를 만들려면 간단히 쓸 수 있습니다 ${BASH_SOURCE[0]:-${(%):-%x}}. 결과 값은 BASH_SOURCE[0]정의 ${(%):-%x}}될 때 와 BASH_SOURCE [0]이 정의되지 않은 경우에 가져옵니다.


0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (분명히 bash 용)


$BASH_SOURCE 테스트 사례

주어진 파일 /tmp/source1.sh

echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'\
     "($(readlink -e -- "${BASH_SOURCE}"))"

source 다른 방식으로 파일

source ...에서 /tmp

$> cd /tmp

$> source source1.sh
$BASH_SOURCE (source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source ./source1.sh
$BASH_SOURCE (./source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source /tmp/source1.sh
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source ...에서 /

cd /
$> source /tmp/source1.sh
$0 (bash)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source다른 상대 경로 /tmp/a에서/var

$> cd /tmp/a

$> source ../source1.sh
$BASH_SOURCE (../source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> cd /var

$> source ../tmp/source1.sh
$BASH_SOURCE (../tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

...에 관해서 $0

모든 경우에 스크립트에 추가 된 명령이있는 경우

echo '$0 '"(${0})"

다음 source스크립트는 항상 인쇄

$0 (bash)

그러나 , 스크립트가 된 경우 실행

$> bash /tmp/source1.sh

그런 다음 $0string value /tmp/source1.sh입니다.

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

0

이 답변lsoftcsh에서 중첩 소스 파일을 처리 할 수있는 유일한 방법 인 grep 마술이 어떻게 그리고 약간 인지 설명합니다 .

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh

-2
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

어쩌면 이것은 심볼릭 링크 또는 소스 파일에서는 작동하지 않지만 일반 파일에서는 작동합니다. 이리저리 참조로 찍은. @kenorb dirname, readlink, BASH_SOURCE가 없습니다.


1
질문 에서 소스 스크립트가 아닌 $0현재 실행중인 스크립트 에 대한 정보 를 얻습니다 .
스콧

-3

실제로 "dirname $ 0"은 스크립트 경로를 제공하지만 약간 해석해야합니다.

$ cat bash0
#!/bin/bash
echo \$0=$0
dirname $0
$ bash0    # "." appears in PATH right now.
$0=./bash0
.
$ ./bash0
$0=./bash0
.
$ $PWD/bash0
$0=/home/00/bediger/src/ksh/bash0
/home/00/bediger/src/ksh
$ $PWD/../ksh/bash0
$0=/home/00/bediger/src/ksh/../ksh/bash0
/home/00/bediger/src/ksh/../ksh
$ ../ksh/bash0
$0=../ksh/bash0
../ksh

"."처리를 준비해야합니다. 일반적인 상황에서 디렉토리 이름으로. "."일 때 ksh에 약간 다르게 동작하는 내장 dirname을 기억하면서 조금 실험 해 보았습니다. PATH에 나타납니다.


4
이것은 실행 된 스크립트가 아닌 소스 스크립트입니다. $0대화식 쉘을위한 "bash"가 포함되어 있으며, 이는 모든 소스 스크립트에서 볼 수 있습니다.
Cascabel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.