쉘 스크립트를 재귀적으로 얻는 방법은 무엇입니까?

쉘 스크립트를 재귀적으로 얻는 방법은 무엇입니까?

이 질문은 중요하다면 Bash의 맥락에서 가장 적합하지만 언어 간 솔루션을 기대합니다.

내가 겪고 있는 문제는 한 스크립트를 다른 스크립트로 "가져오는" 경우에 발생하지만 이를 재귀적으로 수행하는 것입니다. 문제는 이 일을 무한정 하는 것 같다는 것이다.

구체적인 사례는 다음과 같습니다. 동일한 디렉터리에 다른 bash 스크립트를 로드하기 위해 간단한 bash 스크립트를 만들었습니다. 내 프로젝트에서는 모든 스크립트가 동일한 디렉토리에 있으므로 이제 작동합니다.

따라서 "스크립트 로더"는 다음과 같습니다( _source_script.sh 다음 기능을 정의하는 파일).

_source_script()
{
    # Define a few colors for the possible error messages below.
    RED=$(tput setaf 1)
    NORMAL=$(tput sgr0)

    EXPECTED_DIR="scripts"

    if [ "$#" -ge  "1" ]
    then
        EXPECTED_DIR=$1
    fi

    # Based on: http://stackoverflow.com/a/1371283/3924118
    CURRENT_DIR=${PWD##*/}

    if [ "$CURRENT_DIR" = "$EXPECTED_DIR" ]
    then
        for (( arg = 2; arg <= $#; arg++ ))
        do
            # Based on:
            # - http://askubuntu.com/questions/306851/how-to-import-a-variable-from-a-script
            # - http://unix.stackexchange.com/questions/114300/whats-the-meaning-of-a-dot-before-a-command-in-shell
            # - http://stackoverflow.com/questions/20094271/bash-using-dot-or-source-calling-another-script-what-is-difference

            printf ". ./${!arg}.sh\n"
            # Try to load script ${!arg}.sh
            . ./${!arg}.sh

            # If it was not loaded successfully, exit with status 1.
            if [ $? -ne 0 ]
            then
                printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n"
                exit 1
            fi
        done
    else
        printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
        exit 1
    fi
}

이 "스크립트 로더"를 사용하려면 먼저 다음과 같이 다른 스크립트에서 이를 "가져와야" 합니다.

. ./_source_script.sh

문제는 함수를 통해 다른 스크립트를 포함시키려고 할 때입니다 _source_script. 예를 들어, 스크립트에서 다음을 수행하려고 하면 다음과 같습니다 some_script.sh.

_source_script scripts colors asserts clean_environment

영원히 계속 실행됩니다.

내부에 있음 colors.sh:

#!/usr/bin/env bash

# Colors used when printing.
export GREEN=$(tput setaf 2)
export RED=$(tput setaf 1)
export NORMAL=$(tput sgr0)
export YELLOW=$(tput setaf 3)

내부에 있음 asserts.sh:

...

. ./_source_script.sh
_source_script scripts colors

그 안에 clean_environment.sh나는 또한 다음을 가지고 있습니다:

. ./_source_script.sh
_source_script scripts colors

내 이해에 따르면 이것은 아무것도 로드하지 않거나 루프를 찾는 스크립트를 찾을 때까지 재귀적으로 실행되어야 하지만 그렇지 않습니다.

그래서 내 해결책은 다음과 같습니다 some_script.sh.

_source_script scripts colors 
_source_script scripts asserts
_source_script scripts clean_environment

즉, 개별적으로 실행합니다.

그렇다면 루프에서 여러 스크립트를 "가져올" 수 없는 이유는 무엇입니까?

답변1

코드를 너무 자세히 살펴보지 않고 일반적인 접근 방식에는 C의 "헤더 가드"와 유사한 내용이 포함됩니다.

#ifndef HEADER_H
#define HEADER_H

/* The contents of the header file, with typedefs etc. */

#endif

이 헤더와 관련된 C 전처리기 매크로는 어디에 HEADER_H있습니까? 헤더가 이미 포함된 경우 헤더를 다시 포함하지 않는 데에만 사용됩니다. 나는 일반적 MCMC_H으로 헤더 파일 자체에서 이름이 파생된 매크로를 만듭니다 mcmc.h.

쉘 스크립트에서는 다음과 같이 간단할 수 있습니다.

if [ -z "$script_source_guard" ]; then
script_source_guard=1

# ... do things (define functions etc.)

fi

변수 이름은 script_source_guard이 파일에만 적용되어야 합니다. 마찬가지로 이름은 소스 파일 이름에서 임의로 파생될 수 있습니다. 이름이 지정된 파일에는 myfuncts.shlib이름이 지정된 보호 변수가 있거나 다른 이름이 있을 수 있습니다.MYFUNCTS_MYFUNCTSguard_myfuncts

답변2

이는 범위 문제입니다. 의도적으로 함수는 실행되고 호출될 _source_script때 실제로 자신을 재귀적으로 호출합니다 . 그러나 쉘 스크립트의 변수는 기본적으로 전역입니다. 따라서 (스택에서) 동시에 활성화된 여러 함수 구현은 동일한  .. ./${!arg}.sh_source_script_source_scriptarg

상세히:

언제 asserts.sh또는 clean_environment.sh호출됩니까?

_source_script scripts colors

함수는 = 2 _source_script가 됩니다 $#. 따라서 for (( arg = 2; arg <= $#; arg++ ))루프는 $arg<= 2인 동안 실행되므로 결국 3이 됩니다. . asserts.sh호출 되면 에 있기 $arg때문에 이미 3입니다.asserts$3

_source_script scripts colors asserts clean_environment

명령 이 some_script.sh.$arg놓다 도착하다 3에서는 아무런 작업도 수행하지 않고 원래 상태로 asserts.sh되돌아갑니다 .$arg

그러나 . clean_environment.sh호출 되면 에 있으므로 $arg4입니다.clean_environment$4

_source_script scripts colors asserts clean_environment

주문하다. 따라서 in(호출될 때)이 $arg3으로 설정되면 (in)의 최상위 수준 구현은 루프에서 위치를 잃고 돌아가서 다시 실행됩니다. 몇 번이고, 몇 번이고...clean_environment.sh_source_script_source_scriptsome_script.shfor (( arg = 2; arg <= $#; arg++ )). clean_environment.sh

해결책

물론 해결책은 단순히  local arg함수에 선언을 넣는 것입니다 _source_script.


스크립트에 대한 추가 참고사항:

  • 다음과 같은 상황이 있습니다.

    • 다양한 것(상수, 함수, 별칭 등)을 정의하는 파일이 있습니다.
    • 다른 파일이 있습니다사용위에서 정의한 것들은. (소스) 정의가 포함된 파일
    • 예를 들어 일부 파일은 두 그룹 모두에 속하며  호출자에게 일부 리소스를 제공할 수 있지만 해당 파일에 정의된 값을 얻기 위해 읽기도 asserts.sh 합니다  . 아마도 계층 구조가 있을 것입니다.clean_environment.shcolors.sh

      • some_script.sh 수신 전화 colors.sh
      • some_script.sh 수신 전화 asserts.sh
        • asserts.sh 수신 전화 colors.sh
      • some_script.sh 수신 전화 clean_environment.sh
        • clean_environment.sh 수신 전화 colors.sh

      등.

  • 일부 사용자는 쉘 명령 파일이 여러 번 처리되는 것을 방지하기 위해 "헤더 가드"를 사용할 것을 권장합니다. 이 조언은 귀하에게 유용하고 관련이 있지만상태 (위에서 언급했듯이) 그게 당신과 무슨 관련이 있는지 정말 모르겠습니다.질문.

    - 당신이 그것을 사용해야한다는 점을 제외하고_source_script.sh그 자체.

    위의 계층 구조에서는 다음과 같은 사실을 무시했습니다.파일당수신 전화 _source_script.sh. 이는 다음과 같은 _source_script기능을 의미합니다.런타임 시 재정의됨.  이것은 매우 위험하게 들릴 수 있습니다.

  • 당신은 말한다

    if [ "$#" -ge "1" ]
    then
        EXPECTED_DIR=$1
    fi
    

    직접적으로 말할 수도 있습니다

    if [ "$#" -le  1  ]
    then
        exit 1                                  # or maybe      return 1
    fi
    EXPECTED_DIR=$1
    

    인수가 2개 미만이면 할 일이 없기 때문입니다.

  • printf꼭 필요한 것이 아니라면 형식 문자열(즉, 첫 번째 인수)에 변수를 사용하지 않는 것이 더 안전합니다. 그래서

    printf ". ./${!arg}.sh\n"
    printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n"
    printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
    

    ~해야 한다

    printf ". ./%s.sh\n" "${!arg}"
    printf "%sScript '%s.sh' not loaded successfully. Exiting...%s\n" \
                                                        "$RED" "${!arg}" "$NORMAL"
    printf "No script loaded: %s != %s.\n" "$CURRENT_DIR" "$EXPECTED_DIR"
    
  • 따옴표를 더 자주 사용해야 합니다.

    . ./${!arg}.sh
    

    ~해야 한다

    . "./${!arg}.sh"
    

    그리고 엄밀히 말하면, 진정으로 편집증적인 안전을 $?유지하려면 "$?"

  • "$?"하지만 명시적으로 테스트하는 대신

    if ! . "./${!arg}.sh"
    then
        printf "%sScript '%s.sh' not loaded successfully. Exiting...%s\n" \
                                                        "$RED" "${!arg}" "$NORMAL"
        exit 1
    fi
    

    ?

    참고하시기 바랍니다.$?성공적인 명령은 파일의 마지막 명령의 종료 상태로 설정됩니다 . 따라서 헤더 파일(  .sh사용할 다른 파일을 정의하는 파일)이 정확한 종료 상태를 반환하는지 주의하세요.

답변3

상위 파일을 통해 구매를 연결하려는 경우에도 문제 없습니다. 나는 함수에 소스문을 넣지 않을 것이다. 상위 파일만 가져오면 모든 하위 파일도 가져와야 합니다.

내부 명령에 대한 bash 매뉴얼 페이지에서 source:

[It] 파일 이름에서 명령을 읽고 실행합니다.현재 쉘 환경그리고 filename에서 실행된 마지막 명령의 종료 상태를 반환합니다.

현재 쉘 환경이 계속해서 기능을 실행한다고 보장할 수 없는 경우 구성을 좀 더 신중하게 고려할 수 있습니다.

관련 정보