스크립트가 소스인지 감지하는 방법


217

나는 exit그것이 공급되고 있다면 그것을 호출하고 싶지 않은 스크립트를 가지고 있습니다 .

나는 확인하는 것을 생각했다 $0 == bash스크립트를 다른 스크립트에서 가져 왔거나 사용자가와 같은 다른 셸에서 소스를 가져 오는 경우 문제가 있는지 했습니다 ksh.

스크립트가 소스인지 감지하는 신뢰할 수있는 방법이 있습니까?


2
나는 비슷한 문제가 있었고 모든 경우에 '종료'를 피함으로써 해결했다. "kill -INT $$"는 두 경우 모두 스크립트를 안전하게 종료합니다.
JESii

1
이 답변을 보셨습니까 ? 허용 된 지 5 년 후에 제공되지만 "배터리 포함"이 있습니다.
raratiru

답변:


73

이것은 Bash와 Korn 사이에서 이식 가능한 것으로 보입니다.

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

이것과 비슷한 줄이나 pathname="$_"(나중에 테스트와 액션이있는) 줄은 스크립트의 첫 줄이나 shebang 다음 줄에 있어야합니다. 대부분의 상황).


10
불행히도 작동하지 않을 수 있습니다. 사용자가 설정 한 경우 BASH_ENV, $_스크립트의 상단에서 마지막 명령이 실행됩니다 BASH_ENV.
Mikel

30
bash를 사용하여 스크립트를 실행하는 경우에도 작동하지 않습니다 (예 : $ bash script.sh) $ _는 ./script.sh 대신 / bin / bash가됩니다. 스크립트가 호출 될 때 예상되는 경우입니다. 이런 식으로 : $ ./script.sh 어떤 경우 든 감지하는 $_것은 문제입니다.
Wirawan Purwanto

2
이러한 호출 방법을 확인하기 위해 추가 테스트가 포함될 수 있습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

8
불행히도, 그건 잘못이야! 참조 내 대답
F. 하우리

8
요약하면 :이 방법은 일반적으로 작동 하지만 강력하지는 않습니다 . (a) bash script(이 솔루션이 소스로 잘못보고하는 셸 실행 파일을 통한 호출 ) 및 (b) (훨씬 적음) echo bash; . script( $_스크립트를 소싱하는 쉘과 일치하는 경우이 솔루션은 다음과 같이 잘못보고합니다. 서브 쉘 ). 쉘 특정 특수 변수 (예 :)$BASH_SOURCE강력한 솔루션을 허용합니다 (강한 POSIX 호환 솔루션이 없음). 그것은 이다 강력한 크로스 - 쉘 테스트를 만드는 법, 성가신이라도 가능.
mklement0

170

Bash 버전이 BASH_SOURCE 배열 변수에 대해 알고 있다면 다음과 같이 시도하십시오.

# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."

11
아마도 $ BASH_SOURCE가 그 목적을 위해 의도 된 가장 깨끗한 방법 일 것입니다.
혼동

4
OP가 지정한 조건 인 ksh에서는 작동하지 않습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

2
${BASH_SOURCE[0]}그냥 대신 사용해야 할 이유가 $BASH_SOURCE있습니까? 그리고 ${0}$0?
hraban

4
BASH_SOURCE소스의 스택 추적을 보유 하는 배열 변수 ( manual 참조 ) ${BASH_SOURCE[0]}는 최신 위치 입니다. 중괄호는 변수 이름의 일부를 bash에 알려주기 위해 여기에서 사용됩니다. $0이 경우에는 필요 하지 않지만 아프지 않습니다. ;)
Konrad

4
@Konrad를 확장 $array하면 ${array[0]}기본적으로 확장 됩니다. 다시, 이유가 있는가 [...]?
Charles Duffy

133

을위한 강력한 솔루션 bash, ksh,zsh 하는 포함 크로스 - 쉘 하나, 플러스 합리적으로 강력한 POSIX 호환 솔루션 :

  • 제공된 버전 번호는 기능이 검증 된 버전 번호입니다. 이러한 솔루션은 훨씬 이전 버전에서도 작동합니다 . 피드백도 환영 합니다.

  • POSIX 기능 만 사용하면 (예 : in dash, /bin/sh우분투에서 작동 ) 스크립트가 소스인지 여부를 확인할 수있는 강력한 방법없습니다 . 최상의 근사값 은 아래를 참조하십시오 .

원 라이너는 다음과 같습니다-아래 설명; 크로스 셸 버전은 복잡하지만 강력하게 작동해야합니다.

  • bash (3.57 및 4.4.19에서 확인)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh (93u +에서 확인)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
  • zsh (5.0.5에서 확인)- 함수 외부에서 이를 호출해야 합니다.

    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
  • 크로스 쉘 (bash, ksh, zsh)

    ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
     [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
        printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] || 
     [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
  • POSIX 호환 ; 하지 않는 한 줄 기술적 인 이유와 대한 (단일 파이프 라인) 하지 완전히 강력한 (아래 참조)

    sourced=0
    if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
      case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
    elif [ -n "$KSH_VERSION" ]; then
      [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
    elif [ -n "$BASH_VERSION" ]; then
      (return 0 2>/dev/null) && sourced=1 
    else # All other shells: examine $0 for known shell binary filenames
      # Detects `sh` and `dash`; add additional shell filenames as needed.
      case ${0##*/} in sh|dash) sourced=1;; esac
    fi

설명:


세게 때리다

(return 0 2>/dev/null) && sourced=1 || sourced=0

참고 :이 기술은 원래 솔루션보다 더 강력한 것으로 판명 되었으므로 user5754163의 답변 에서 채택되었습니다 . [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

  • Bash는 return스크립트 소스 가있는 경우에만 함수의 문장 및 스크립트의 최상위 범위에서 명령문을 허용 합니다 .

    • 소스return아닌 스크립트 의 최상위 범위에서 사용되는 경우 오류 메시지가 표시되고 종료 코드는로 설정됩니다 1.
  • (return 0 2>/dev/null)서브 쉘return 에서 실행 하고 오류 메시지를 억제합니다. 그 후 종료 코드는 스크립트가 소스인지 ( ) 아닌지 ( )를 나타내며 , 변수 는 변수 를 설정하기 위해 and 연산자 와 함께 사용 됩니다.01&&||sourced

    • return소스 스크립트의 최상위 범위에서 실행 하면 스크립트가 종료 되므로 서브 쉘을 사용해야 합니다.
    • 피연산자 로 명시 적으로 사용하여 명령을보다 강력하게 만든 @Haozhun 의 모자 팁 ; 그는 다음과 같이 지적했다 : bash의 도움 : "N이 생략되면, 리턴 상태는 마지막 명령의 상태이다." 결과적으로, 피연산자없이 그냥 사용 된 이전 버전 은 사용자 쉘의 마지막 명령에 0이 아닌 리턴 값이있는 경우 잘못된 결과를 생성합니다.0returnreturn [N]return

ksh

[[ \
   $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
   "${.sh.file}" \
]] && 
sourced=1 || sourced=0

특수 변수 ${.sh.file}는 다소 유사합니다 $BASH_SOURCE. 참고 ${.sh.file}원인 구문 오류 의 bash, zsh을, 그리고 대시를 확신을 실행하는 조건부 멀티 쉘 스크립트.

bash는 달리, $0그리고 ${.sh.file}보장되지 않음 정확히 으로, 비 - 공급의 경우 동일 $0수 있습니다 상대 경로하면서, ${.sh.file}항상입니다 전체 경로 때문에, $0비교하기 전에 전체 경로로 해결해야합니다.


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXT평가 컨텍스트에 대한 정보를 포함합니다. 함수 외부에서이를 호출하십시오. 전래 스크립트 [의 최고 수준 범위] 내부 $ZSH_EVAL_CONTEXT 으로:file .

경고 : 내부 명령 치환, zsh을 추가 :cmdsubst, 시험 때문에 $ZSH_EVAL_CONTEXT:file:cmdsubst$있다.


POSIX 기능 만 사용

특정 가정을 기꺼이 원한다면 , 스크립트를 실행할 수있는 쉘 의 이진 파일 이름아는 것에 근거하여 스크립트가 소스인지 여부에 대한 합리적이지만 확실한 증거는 아닙니다 . 특히 이는 스크립트가 다른 스크립트에 의해 제공되는 경우이 방법이 실패 함을 의미합니다
.

이 답변 에서 " 원본 호출을 처리하는 방법"섹션에서는 POSIX 기능으로 만 처리 할 수없는 경우 에 대해 자세히 설명합니다.

이것은 의 표준 동작에 의존 $0, zsh인스턴스가 않습니다 들어 하지 나타낸다.

따라서 가장 안전한 방법은 위의 강력한 쉘 특정 방법을 나머지 모든 쉘에 대한 대체 솔루션 과 결합하는 것 입니다.

받는 모자의 팁 스테판 Desneux그의 대답 영감에 대한 (A로 내 크로스 - 쉘 문 식을 변환 sh호환 if문 및 기타 쉘에 대한 핸들러를 추가).

sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
  (return 0 2>/dev/null) && sourced=1 
else # All other shells: examine $0 for known shell binary filenames
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|dash) sourced=1;; esac
fi

[1] user1902689단순한 파일 이름 을 바이너리 에 전달 하여위치한[[ $0 != "$BASH_SOURCE" ]] 스크립트를 실행할 때 위양성을 나타내는 것을 발견했습니다 . 예를 들면, 때문에, 바로 다음이다 , 반면 은 IS 전체 경로 . 당신은 일반적으로 스크립트를 호출하기 위해이 기술을 사용하지 않을 동안 - 당신은 단지 그들을 호출 좋겠 직접 ( 이 -) 과 결합 될 때 도움 을위한 디버깅 .$PATHbashbash my-script$0my-script$BASH_SOURCE$PATHmy-script-x


1
그러한 포괄적 인 답변을위한 kudos.
DrUseful

75

@DennisWilliamson의 답변을 읽은 후 몇 가지 문제가 있습니다. 아래를 참조하십시오.

이 질문은 이 답변에는 또 다른 부분이 있습니다. ... 아래를 참조하십시오.

단순한 방법

[ "$0" = "$BASH_SOURCE" ]

bash가 ;-) 수 있기 때문에 즉시 시도해 봅시다.

source <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

bash <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)

가독성을 위해 source대신 사용 합니다 .( .의 별칭과 같이 source).

. <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

프로세스 소스가 유지 되는 동안 프로세스 번호는 변경되지 않습니다 .

echo $$
29301

$_ == $0비교 를 사용하지 않는 이유

많은 경우를 보장하기 위해 진정한 스크립트 를 작성하기 시작합니다 .

#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell

[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"

이것을 다음이라는 파일로 복사하십시오 testscript.

cat >testscript   
chmod +x testscript

이제 테스트 할 수 있습니다 :

./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)

괜찮아.

. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

괜찮아.

그러나 -x플래그 를 추가하기 전에 스크립트를 테스트하려면 다음을 수행하십시오 .

bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)

또는 사전 정의 된 변수를 사용하려면

env PATH=/tmp/bintemp:$PATH ./testscript 
proc: 25948[ppid:24890] is own (DW purpose: sourced)

env SOMETHING=PREDEFINED ./testscript 
proc: 25972[ppid:24890] is own (DW purpose: sourced)

더 이상 작동하지 않습니다.

5 번째 줄에서 6 번째 줄로 주석을 옮기면 더 읽기 쉬운 대답이됩니다.

./testscript 
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own

. testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

source testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

bash testscript 
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own

env FILE=/dev/null ./testscript 
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own

더 세게 : 지금...

내가 사용하지 않기 때문에 맨 페이지를 읽은 후에 많은 시도가 있습니다.

#!/bin/ksh

set >/tmp/ksh-$$.log

이것을 다음에 복사하십시오 testfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

두 번 실행하는 것보다 :

./testfile.ksh
. ./testfile.ksh

ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user   2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user   2140 avr 11 13:48 /tmp/ksh-9781.log

echo $$
9725

그리고 봐라:

diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
>   lineno=0
> SHLVL=3

diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
<   level=1
<   lineno=1
< SHLVL=2

소스 실행에 유전 된 변수가 있지만 실제로는 관련이 없습니다 ...

$SECONDS가까운 것을 확인할 수도 0.000있지만 수동 소스 사례 만 보장합니다 ...

부모가 무엇인지 확인하려고 할 수도 있습니다 .

이것을 당신의 testfile.ksh:

ps $PPID

보다:

./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32320 pts/4    Ss     0:00 -ksh

. ./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32319 ?        S      0:00 sshd: user@pts/4

또는 ps ho cmd $PPID, 그러나 한 수준의 하위 세션에서만 작동합니다.

죄송합니다. 아래에서 신뢰할 수있는 방법을 찾지 못했습니다. .


[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]파이프 ( cat script | bash) 를 통해 읽은 스크립트의 경우
hakre

2
참고 .별칭 아니다는 source, 실제로는 다른 방법으로 주위입니다. source somescript.shBash-ism이며 휴대용이 아니며 . somescript.shPOSIX 및 휴대용 IIRC입니다.
dragon788

32

BASH_SOURCE[]대답 (나중에 bash는-3.0)는하지만, 간단한 것 BASH_SOURCE[]입니다 함수 본체 외부 작업에 문서화되어 있지 않습니다 (이것은 현재 man 페이지와 불일치에, 일에 발생).

Wirawan Purwanto가 제안한 가장 강력한 방법 FUNCNAME[1] 은 함수 내 에서 확인 하는 것입니다 .

function mycheck() { declare -p FUNCNAME; }
mycheck

그때:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

이 검사의 출력에 상당 caller값, mainsource발신자의 상황을 구별. 를 사용 FUNCNAME[]하면 caller출력 을 캡처하고 구문 분석 할 수 있습니다 . 그래도 로컬 통화 깊이를 알고 계산해야합니다. 스크립트가 다른 함수 나 스크립트 내에서 제공되는 경우 배열 (스택)이 더 깊어집니다. ( FUNCNAME특별한 bash 배열 변수이며 결코 그렇지 않은 한 호출 스택에 해당하는 연속 인덱스를 가져야합니다 unset.)

function issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(bash-4.2 이상 ${FUNCNAME[-1]}에서는 배열의 마지막 항목 대신 간단한 양식을 사용할 수 있습니다 . 아래 Dennis Williamson의 의견 덕분에 개선되고 단순화되었습니다.)

그러나 명시된대로 귀하의 문제는 " 나는 그것이 소스 인 경우 '종료'를 호출하지 않는 스크립트가 있습니다 "입니다. bash이 상황에 대한 일반적인 관용구는 다음과 같습니다.

return 2>/dev/null || exit

스크립트가 소스 인 return경우 소스 스크립트를 종료하고 호출자에게 리턴합니다.

스크립트가 실행 중이면 return오류 (리디렉션 된)를 반환 exit하고 스크립트를 정상적으로 종료합니다. 모두 returnexit필요한 경우, 종료 코드를 취할 수 있습니다.

슬프게도, 이것은 ksh(적어도 내가 가지고있는 AT & T 파생 버전 에서는) 작동하지 않으며 함수 또는 도트 소스 스크립트 외부에서 호출 된 경우 return와 동일 하게 처리 됩니다 exit.

업데이트 : 최신 버전에서 할 수있는 일은 함수 호출 깊이로 설정된 ksh특수 변수를 확인하는 것 .sh.level입니다. 호출 된 스크립트의 경우 처음에 설정이 해제되고 도트 소스 스크립트의 경우 1로 설정됩니다.

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

이것은 bash 버전만큼 강력하지는 않으므로 issourced()테스트중인 파일을 최상위 레벨이나 알려진 함수 깊이에서 호출해야합니다 .

(또한 bashhub 에서이 코드 에 관심 있을 수 있습니다. ksh 기능 은 규율 함수와 일부 디버그 트랩 트릭을 사용하여 bash FUNCNAME배열 을 에뮬레이트합니다 .)

여기 정식 답변 : http://mywiki.wooledge.org/BashFAQ/109$-쉘 상태에 대한 또 다른 지표 (불완전하지만)를 제공합니다.


노트:

  • "main"및 "source"( builtin을 재정의 함 ) 라는 bash 함수를 작성할 수 있습니다. 이러한 이름이 표시 될 수 FUNCNAME[]있지만 해당 배열의 마지막 항목 만 테스트되는 한 모호성이 없습니다.
  • 에 대한 좋은 답변이 없습니다 pdksh. 내가 찾을 수있는 가장 가까운 것은에 적용됩니다 pdksh. 스크립트의 각 소싱은 새 파일 설명자를 엽니 다 (원본 스크립트의 경우 10으로 시작). 거의 확실하게 당신이 의지하고 싶은 것이 아닙니다 ...

${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}스택에서 마지막 (아래쪽) 항목을 얻는 방법은 무엇입니까? 그런 다음 "main"(OP 제외)에 대한 테스트가 가장 신뢰할 수있었습니다.
Adrian Günter

나는이 있으면 PROMPT_COMMAND세트를, 그 쇼까지의 마지막 인덱스로 FUNCNAME배열 나는 실행하는 경우 source sourcetest.sh. 확인을 거꾸로 돌리면 ( main마지막 색인으로 보임)보다 강력 해 보입니다 is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }.
dimo414

1
맨 페이지 상태 FUNCNAME는 기능에서만 사용할 수 있습니다. 내 테스트에 따르면 declare -p FUNCNAME, bash다르게 동작합니다. v4.3은 함수 외부에 오류를 제공하고 v4.4는을 제공합니다 declare -a FUNCNAME. 주 스크립트에서 (!) 는 아무것도 반환 하지 않으면 서 반환 main됩니다 . 그리고 : 외부 함수를 사용하는 "ab"스크립트가 너무 많아서 이것이 변경되거나 변경 될 수 있을지 의심 스럽습니다. ${FUNCNAME[0]}$FUNCNAME$BASH_SOURCE
티노

24

편집자 주 :이 답변의 솔루션은 강력하지만 효과가 bash있습니다. 로 간소화 할 수 있습니다
(return 2>/dev/null).

TL; DR

return명령문 을 실행하십시오 . 스크립트가 소스가 아닌 경우 오류가 발생합니다. 해당 오류를 파악하고 필요에 따라 진행할 수 있습니다.

이것을 파일에 넣고 test.sh라고 부릅니다.

#!/usr/bin/env sh

# Try to execute a `return` statement,
# but do it in a sub-shell and catch the results.
# If this script isn't sourced, that will raise an error.
$(return >/dev/null 2>&1)

# What exit code did that give?
if [ "$?" -eq "0" ]
then
    echo "This script is sourced."
else
    echo "This script is not sourced."
fi

직접 실행하십시오.

shell-prompt> sh test.sh
output: This script is not sourced.

그것을 소스 :

shell-prompt> source test.sh
output: This script is sourced.

나에게 이것은 zsh와 bash에서 작동합니다.

설명

return당신은 함수의 외부 또는 스크립트가 공급되지 않는 경우 그것을 실행하려고하면 문에서 오류가 발생합니다. 쉘 프롬프트에서 이것을 시도하십시오 :

shell-prompt> return
output: ...can only `return` from a function or sourced script

해당 오류 메시지를 볼 필요가 없으므로 출력을 dev / null로 리디렉션 할 수 있습니다.

shell-prompt> return >/dev/null 2>&1

이제 종료 코드를 확인하십시오. 0은 확인 (오류가 발생하지 않음)을 의미하고 1은 오류가 발생했음을 의미합니다.

shell-prompt> echo $?
output: 1

또한 return서브 쉘 내부 에서 명령문 을 실행하려고합니다 . 때 return문을 실행합니다. . . 잘. . . 보고. 하위 셸에서 실행하면 스크립트에서 반환되지 않고 해당 하위 셸에서 반환됩니다. 서브 쉘에서 실행하려면 다음과 같이 랩핑하십시오 $(...).

shell-prompt> $(return >/dev/null 2>$1)

이제 서브 쉘 내부에서 오류가 발생하여 서브 쉘의 종료 코드가 1이어야합니다.

shell-prompt> echo $?
output: 1

0.5.8-2.1ubuntu2$ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced.
Phil Rutschman에서

3
POSIX는 return최상위 수준에서 수행 할 작업 ( pubs.opengroup.org/onlinepubs/9699919799/utilities/… )을 지정하지 않습니다 . dash쉘 취급합니다 return으로 최상위 수준 exit. 다른 쉘 은 최상위 레벨에서 허용 bash하거나 zsh허용하지 않으며 return, 이는 이와 같은 기술입니다.
user5754163

$서브 쉘 앞 을 제거하면 sh에서 작동합니다 . 즉, - (return >/dev/null 2>&1)대신에 사용 $(return >/dev/null 2>&1)하지만 bash에서 작동이 중지됩니다.
시조

@Eponymous : dash이 솔루션이 작동하지 않는 곳 sh은 예를 들어 Ubuntu에서 와 같이 작동 하므로이 솔루션은 일반적으로 와 작동 하지 않습니다 sh. 솔루션은 Bash 3.2.57 및 4.4.5에서 $이전 과 함께 또는없이 (...)( 나 에게 좋은 이유는 없지만) 저에게 효과적 $입니다.
mklement0

2
returnsource명령이 잘못 종료 된 직후 스크립트 를 호출하면 explcit 반환 값이 없어집니다 . 개선 편집 제안.
DimG

12

FWIW, 다른 답변을 모두 읽은 후 다음 해결책을 찾았습니다.

업데이트 : 실제로 누군가가 내 대답 에 영향을 준 다른 답변 에서 수정 된 오류를 발견했습니다 . 여기 업데이트도 개선 된 것 같습니다 (호기심이 많은 경우 편집 참조).

이것은 모든 스크립트에서 작동 하지만 , 다른 쉘에서 시작될#!/bin/bash 수있을뿐만 아니라 main함수 외부에 유지되는 설정과 같은 일부 정보를 배우기 위해 제공 될 수도 있습니다 .

아래 의견에 따르면이 답변은 모든 bash변형에 대해 작동하지는 않습니다 . 또한에 /bin/sh기반한 시스템에는 해당되지 않습니다 bash. IE bashMacOS의 v3.x 에서는 실패합니다 . (현재 나는 이것을 해결하는 방법을 모른다.)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

마지막 2 줄 대신 다음 BASH_SOURCE쉘을 사용 set -e하여 다른 쉘에 설정하지 않고 작업 할 수 있습니다 main.

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

이 스크립트 레시피에는 다음과 같은 속성이 있습니다.

  • bash정상적인 방법으로 실행되면 main호출됩니다. bash -x script여기 script에는 경로가 포함되지 않은 통화는 포함되지 않습니다 ( 아래 참조).

  • 에 의해 공급하는 경우 bash, main호출 스크립트가 같은 이름을 가지고 일 경우에만 호출됩니다. (예를 들어 자체 소스 또는 bash -c 'someotherscript "$@"' main-script args..위치 를 통해main-scripttest 로 표시되는 경우 $BASH_SOURCE).

  • eval이외의 다른 셸 에서 소스 / 실행 / 읽기 / 처리 된 경우 bash,main 호출되지 않습니다 ( BASH_SOURCE항상 다릅니다 $0).

  • main 경우 호출되지 않습니다 bash$0빈 문자열로 설정하지 않는 한 stdin에서 스크립트를 읽는 .( exec -a '' /bin/bash ) <script

  • 다른 스크립트 내에서 bashwith eval ( eval "`cat script`" 모든 따옴표가 중요합니다! )로 평가되면을 호출합니다 main. 경우 eval직접 명령 줄에서 실행이 스크립트는 stdin에서 읽어 이전의 경우와 유사하다. ( BASH_SOURCE공백이지만 $0일반적으로 /bin/bash완전히 다른 것을 강요하지 않는 경우)

  • 경우 main호출되지 않습니다, 그것은 반환하지 않습니다 true($?=0 ).

  • 이것은 예기치 않은 동작에 의존하지 않습니다 (이전에는 문서화되지 않았지만 unset변경할 수없는 문서는 없습니다 BASH_SOURCE).

    • BASH_SOURCEbash 예약 배열 입니다. 그러나 허용BASH_SOURCE=".$0" 변경을 하면 매우 위험한 웜 캔이 열리므로 이것이 효과가 없어야한다고 기대합니다 (미래의 일부 버전에서는 추악한 경고가 표시되는 경우 제외 bash).
    • BASH_SOURCE외부 에서 작동하는 문서는 없습니다 . 그러나 그 반대 (기능에서만 작동하는)는 문서화되어 있지 않습니다. 관찰 결과, 작동하지만 ( bashv4.3 및 v4.4로 테스트 bash되었지만 더 이상 v3.x가 없습니다) 너무 많은 스크립트가 중단됩니다$BASH_SOURCE 관찰 된대로 작동 중지 됩니다. 따라서 내 기대는 BASH_SOURCE미래 버전의 그대로 유지됩니다.bash 에서도 .
    • 반면에 (좋은 발견은, BTW!) 고려 ( return 0 )주는, 0소스 경우 1공급하지 않을 경우. 이것은 조금 나를 위해뿐만 아니라 예상치 못한 제공 하고, POSIX는 것을 말한다 (이 수치에 따라) return서브 쉘이 정의되지 않은 동작입니다에서합니다 (그리고 return여기에 서브 쉘에서 명확하게). 아마도이 기능은 결국 더 이상 변경 될 수 없을 정도로 널리 사용 되기는하지만, AFAICS는 향후 bash버전에서 실수로 반환 동작이 변경 될 가능성이 훨씬 높습니다 .
  • 불행히도 bash -x script 1 2 3실행되지 않습니다 main. ( 경로가없는 script 1 2 3곳 비교 script). 해결 방법으로 다음을 사용할 수 있습니다.

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • 그것은 bash script 1 2 3실행되지 않는 main기능이 고려 될 수있다.
  • 참고 것을 ( exec -a none script )호출 main( bash그것의 통과하지 않습니다$0 사용하기 위해 필요한이 들어, 스크립트에 -c마지막 점 참조).

따라서 일부 경우를 제외하고 main스크립트가 일반적인 방식으로 실행될 때만 호출됩니다. 일반적으로 이것은 원하는 것입니다. 특히 이해하기 어려운 코드가 없기 때문에 특히 그렇습니다.

파이썬 코드와 매우 유사합니다.

if __name__ == '__main__': main()

main스크립트를 가져오고로드하고 강제로 실행할 수 있으므로 일부 경우를 제외하고의 호출을 방지 합니다.__name__='__main__'

이것이 도전을 해결하는 좋은 일반적인 방법이라고 생각하는 이유

여러 쉘에서 제공 할 수있는 것이 있으면 호환 가능해야합니다. 그러나 (다른 답변을 읽으십시오), ing 을 감지하는 휴대용 방법이 없기 source때문에 규칙을 변경해야합니다 .

스크립트를 다음과 같이 실행해야합니다. /bin/bash 이를 정확하게 수행 할 수 있습니다.

이렇게 하면 모든 경우가 해결되지만 다음과 같은 경우에는 스크립트를 직접 실행할 수 없습니다.

  • /bin/bash 설치되어 있지 않거나 작동하지 않습니다 (즉, 부팅 환경에서)
  • 당신이 같은 쉘에 파이프하면 curl https://example.com/script | $SHELL
  • (참고 : bash최근에 사용한 경우에만 해당됩니다 .이 레시피는 특정 변형에 대해 실패한 것으로보고되었으므로 귀하의 사례에 적합한 지 확인하십시오.)

그러나 나는 당신이 그것을 필요로하는 실제 이유와 정확히 똑같은 스크립트를 병렬로 제공하는 능력에 대해 생각할 수 없습니다! 일반적 main으로 손으로 감쌀 수 있습니다 . 그렇게 :

  • $SHELL -c '. script && main'
  • { curl https://example.com/script && echo && echo main; } | $SHELL
  • $SHELL -c 'eval "`curl https://example.com/script`" && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $SHELL

노트

  • 이 답변은 다른 모든 답변의 도움 없이는 불가능했을 것입니다! 잘못된 것조차도-처음에 이것을 게시하게했습니다.

  • 업데이트 : https://stackoverflow.com/a/28776166/490291에 있는 새로운 발견으로 인해 편집되었습니다.


ksh 및 bash-4.3에 대해 테스트되었습니다. 좋은. 다른 답변이 이미 투표권을 수집하는 데 수년이 걸린다면 귀하의 답변이 어려운 삶을 사는 것은 매우 안타깝습니다.
hagello

이 답변에 감사드립니다. IF 문으로 더 길고 '읽기 어려운'테스트를 통해 감사합니다. 적어도 두 가지 상황을 모두 처리하여 적어도 무소음 오류를 발생시키는 것이 좋기 때문입니다. 필자의 경우 소스를 사용하거나 소스를 사용하지 않을 때 오류를 알리는 스크립트가 필요합니다.
Tim Richardson

@Tino :에 맥 OS :에 관해서는 "뿐만 아니라 다른 조개에 의해 공급 될 수있다" /bin/sh효율적이다 bash에 할당, POSIX 모드에서 BASH_SOURCE 휴식 스크립트를. 다른 셸 ( dash, ksh, zsh), 파일 인수로 전달하여 스크립트를 호출 쉘 실행에 직접 오작동 (예를 들어, zsh <your-script>스크립트가 잘못이 있다고 생각하게됩니다 소스 ). (당신은 이미 모든 쉘에서 코드 파이핑 이 오작동 한다고 언급했습니다 .)
mklement0

@Tino : 제쳐두고 : . <your-script>(소싱)은 모든 POSIX 유사 쉘에서 작동 하지만 원칙적으로 스크립트가 POSIX 기능 만 사용하도록 작성되어 하나의 쉘에 특정한 기능이 실행을 방해하지 않도록하는 것이 의미가 있습니다. 다른 껍질에서; 따라서 Bash shebang 행 () 대신 사용하는 #!/bin/sh것은 혼란 스럽습니다. 적어도 눈에 띄는 주석이 없습니다. 반대로 스크립트가 Bash에서만 실행되도록 의도 된 경우 (휴대식이 아닌 기능을 고려하지 않았더라도) 비 Bash 셸에서 실행 을 거부하는 것이 좋습니다 .
mklement0

1
@ mklement0 다시 한 번 감사드립니다. 문제가 있다는 메모를 추가했습니다. 다른 독자들을 위해 : bash v3.x와 함께 제공 될 때는 실행하지 말아야 main하지만이 경우에는 그렇게합니다! 그리고에 의해 공급 될 때 /bin/sh, bash --posix이 경우에도 동일하게 발생하며, 그것은 또한 명백한 잘못입니다.
Tino

6

이것은 나중에 스크립트에서 작동하며 _ 변수에 의존하지 않습니다.

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

또는

[ $(basename $0) = $Prog ] && exit

1
이 답변은 여기에서 몇 가지 POSIX 호환 중 하나라고 생각합니다. 명백한 단점은 파일 이름을 알아야하며 두 스크립트가 동일한 파일 이름을 가진 경우 작동하지 않습니다.
JepZ

5

BASH 전용 답변을 드리겠습니다. Korn shell, 죄송합니다. 스크립트 이름이 include2.sh; 그런 다음 호출 된 함수 를 작성 하십시오 . 내 데모 버전은 다음과 같습니다 .include2.sham_I_sourcedinclude2.sh

am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "$1" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
    fi
    return 0
  else
    if [ "$1" = -v ]; then
      echo "I am not being sourced, my script/shell name was $0"
    fi
    return 1
  fi
}

if am_I_sourced -v; then
  echo "Do something with sourced script"
else
  echo "Do something with executed script"
fi

이제 여러 가지 방법으로 실행 해보십시오.

~/toys/bash $ chmod a+x include2.sh

~/toys/bash $ ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ bash ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script

따라서 이것은 예외없이 작동하며 취성 $_재료를 사용하지 않습니다 . 이 트릭은 BASH의 내부 검사 기능, 즉 내장 변수 FUNCNAMEBASH_SOURCE; bash 매뉴얼 페이지에서 해당 설명서를 참조하십시오.

두 가지 경고 만 :

1) 호출 은 소스 스크립트 에서 발생 am_I_called 해야 하지만 함수 내에서 수행 해서는 안됩니다. 네 ... 확인할 수있었습니다${FUNCNAME[1]}${FUNCNAME[2]} 있지만 인생을 더 힘들게 만듭니다.

2) 포함되는 파일의 이름을 찾으려면 소스 스크립트에 함수가 am_I_called 있어야 합니다.


1
설명 :이 기능을 사용하려면 BASH 버전 3 이상이 필요합니다. BASH 2에서 FUNCNAME은 배열 대신 스칼라 변수입니다. 또한 BASH 2에는 BASH_SOURCE 배열 변수가 없습니다.
Wirawan Purwanto

4

Dennis의 매우 유용한 답변 에 약간의 수정을 제안하고 약간 더 휴대하기 쉽도록하기를 바랍니다.

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

[[(일부 항문 보유 IMHO) 데비안 POSIX 호환 쉘에서 인식되지 않기 때문 입니다 dash. 또한, 다시 셸에서 공백을 포함하는 파일 이름으로부터 보호하기 위해 따옴표가 필요할 수 있습니다.


2

$_꽤 부서지기 쉽습니다. 스크립트에서 가장 먼저 확인해야합니다. 그렇더라도 쉘 이름 (공급 된 경우) 또는 스크립트 이름 (실행 된 경우)을 포함 할 수는 없습니다.

예를 들어, 사용자가를 설정 BASH_ENV한 경우 스크립트 맨 위에는에서 $_실행 된 마지막 명령의 이름이 포함됩니다.BASH_ENV 됩니다.

내가 찾은 가장 좋은 방법은 $0다음과 같이 사용하는 것입니다.

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

불행히도이 방법은 zsh에서 즉시 작동하지 않습니다. functionargzero 이름에서 제안하는 것보다 많은 옵션을 수행하고 기본적으로 켜져 .

이 문제를 해결하기 위해을 넣었 unsetopt functionargzero습니다 .zshenv.


1

mklement0 컴팩트 한 표정을 따랐 습니다 .

그것은 깔끔하지만 ksh의 경우 다음과 같이 호출하면 실패 할 수 있음을 알았습니다.

/bin/ksh -c ./myscript.sh

(그것은 소스라고 생각하고 서브 쉘을 실행하기 때문이 아닙니다) 그러나 표현식은 이것을 감지하도록 작동합니다.

/bin/ksh ./myscript.sh

또한 표현식이 컴팩트하더라도 구문이 모든 쉘과 호환되지는 않습니다.

그래서 나는 bash, zsh, dash 및 ksh에서 작동하는 다음 코드로 끝났습니다.

SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
elif [ -n "$KSH_VERSION" ]; then
    [[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1
elif [ -n "$BASH_VERSION" ]; then
    [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
    case $0 in *dash*) SOURCED=1 ;; esac
fi

이국적인 쉘 지원을 추가하십시오 :)


에서 ksh 93+u, ksh ./myscript.sh(내 문) 나를 위해 잘 작동합니다 - 당신이 사용하고있는 버전?
mklement0

POSIX 기능만을 사용하여 스크립트가 소스인지 여부 를 확실하게 결정할 방법이 없다고 생각 합니다. 시도는 Linux ( /proc/$$/cmdline)를 가정 하고 dash( sh예를 들어 우분투 에서도 작동)에만 집중합니다 . 특정 가정을 기꺼이 내릴 수 있다면 $0휴대하기 쉬운 합리적이지만 불완전한 테스트를 검사 할 수 있습니다 .
mklement0

++ 기본 접근 방식-나는 내 대답에 대한 부록에서 sh/ 지원의 가장 좋은 휴대용 근사치라고 생각하는 것에 맞게 자유를 채택했습니다 dash.
mklement0

0

ksh와 bash 에서이 작업을 수행 할 수있는 휴대용 방법이 없다고 생각합니다. bash에서는 caller출력 을 사용하여 그것을 감지 할 수 있지만 ksh에 동등한 것이 있다고 생각하지 않습니다.


$0작동 bash, ksh93pdksh. ksh88테스트 할 필요가 없습니다 .
Mikel

0

bash.version> = 3 인 [mac, linux]에서 작동하는 하나의 라이너가 필요했으며 이러한 답변 중 어느 것도 법안에 맞지 않습니다.

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"

1
bash솔루션은 (당신이 간단하게 할 수 잘 작동 $BASH_SOURCE),하지만 ksh솔루션은 강력한되지 않습니다 : 스크립트에 의해 공급되는 경우 다른 스크립트 , 당신은 가양를 얻을 수 있습니다.
mklement0

0

요점으로 : "$ 0" 변수 가 쉘 이름과 같은지 평가해야합니다 .


이처럼 :

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi


쉘을 통해 :

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

소스를 통해 :

$ source check_source.sh
First Parameter: bash

The script was sourced.



스크립트가 소스인지 여부를 100 % 이식 가능하게 감지하는 것은 매우 어렵습니다 .

내 경험에 대해서 (Shellscripting 7 년) (와 환경 변수에 의존하지 않는 유일한 안전한 방법 의 PID 가 뭔가 있다는 사실에 의한 안전하지 등등, 변수 ), 당신은해야한다 :

  • 당신의 경우에서 가능성을 확장
  • 원하는 경우 스위치 / 케이스를 사용하십시오.

두 옵션 모두 자동 확장 할 수 없지만 더 안전한 방법입니다.



예를 들면 다음과 같습니다.

SSH 세션을 통해 스크립트를 소싱 할 때 "$ 0" 변수 ( source를 사용할 때 )가 반환 한 값 은 -bash 입니다.

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi

또는

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
    echo "The script was sourced via SSH session."
else
    echo "The script WAS NOT sourced."
fi

2
이것은 명백한 잘못이므로 공감됩니다 : /bin/bash -c '. ./check_source.sh'gives The script WAS NOT sourced.. 같은 버그 : ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
Tino

2
귀하의 공감대가 전체 시나리오를 변경하고 Tino에 큰 기여를했습니다. 감사!
ivanleoncz

0

나는 확인으로 끝났다 [[ $_ == "$(type -p "$0")" ]]

if [[ $_ == "$(type -p "$0")" ]]; then
    echo I am invoked from a sub shell
else
    echo I am invoked from a source command
fi

curl ... | bash -s -- ARGS원격 스크립트를 즉석에서 실행 하는 데 사용하면 실제 스크립트 파일을 실행할 때 $ 0이 bash정상적으로 대신 /bin/bash사용됩니다.type -p "$0" 배쉬의 전체 경로를 표시합니다.

테스트:

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE

wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE

0

이것은 "보편적 인"크로스 쉘 지원에 관한 다른 답변들로부터 분리 된 것입니다. 이것은 약간 다르지만 https://stackoverflow.com/a/2942183/3220983 과 매우 유사합니다 . 이것의 약점은 클라이언트 스크립트가 사용 방법을 존중해야한다는 것입니다 (즉, 변수를 먼저 내보내는 것). 장점은 간단하고 "어디서나"작동해야한다는 것입니다. 잘라 내기 및 붙여 넣기 즐거움을위한 템플릿은 다음과 같습니다.

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

참고 : export이 메커니즘을 하위 프로세스로 확장 할 수 있는지 확인하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.