매번 가져올 필요 없이 이 스크립트 파일의 기능을 어떻게 로드할 수 있습니까? "명령을 찾을 수 없습니다"(Bash/스크립팅 기본)

매번 가져올 필요 없이 이 스크립트 파일의 기능을 어떻게 로드할 수 있습니까? "명령을 찾을 수 없습니다"(Bash/스크립팅 기본)

매번 가져올 필요 없이 이 스크립트 파일의 기능을 어떻게 로드할 수 있습니까?

foo실행하려는 스크립트 기능이 포함된 파일을 만들었습니다. PATH에 있습니다 /usr/bin.

문서부자:

#!/bin/bash
echo "Running foo"

function x {
    echo "x"
}

x그러나 터미널에 함수 이름을 입력하면 다음과 같습니다.
x: command not found

입력하면 foo다음
Running foo이 표시됩니다(따라서 파일은 PATH에 있고 실행 가능합니다).

일단 입력하면 실행 기능을 source foo입력할 수 있습니다 xx

나도 알아요, 이건 아주 기본적인 질문이에요. 나는 스크립트를 추상화하여 관리하기 쉽도록 하고 싶습니다(모든 것을 .profile 또는 .bashrc에 덤프하는 것과 비교하여).

답변1

문제는 .../bin디렉토리의 스크립트가 exec다른 쉘 환경에서 편집되었다는 것입니다. 해당 환경은 실행 후 지속되지 않으므로 완료 시 정의가 x() { ... ; }현재 쉘 환경에서 지속되지 않습니다.

때를. ./somescript.sh (또는 및 source와 같은 일부 쉘에서 )bashzsh현재 쉘은 스크립트를 현재 환경으로 읽고 마치 프롬프트에서 발행된 것처럼 내용을 실행합니다. 이는 x() { ... ; }현재 쉘 환경인 대화형 쉘에 정의되어 있습니다.

기본적으로 문제는 다음과 같이 증명될 수 있습니다.

sh <<\HEREDOC_CMD_FILE #runs sh shell with heredoc input file
    { #begins current shell compound expression
        ( #begins subshell compound expression
            x() { echo 'I am function x.' ; } #define function x
            x #x invoked in subshell
        ) #ends subshell compound expression
        x #x invoked in current shell
    } #ends current shell compound expression
#v_end_v
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.
sh: line 6: x: command not found

마찬가지로 ( : subshell )위의 예에 정의된 환경은 완료 후에도 유지되지 않으며 실행하는 스크립트에 정의된 환경도 유지되지 않습니다. 마찬가지로 sh읽어들일 때 HEREDOC_CMD_FILE사용자와 동일한 기능을 수행 source ../file하고 현재 쉘 실행 환경에서 내용을 실행합니다. 비록 해당 환경이 이를 실행한 프롬프트를 발행한 쉘의 하위 쉘 하위이므로 해당 환경에서는 아무것도 수행되지 않습니다. 거기에서도. 그러나 다음과 같이 그렇게 할 수 있습니다.

. /dev/fd/0 <<\HEREDOC_CMD_FILE && x
    x() { echo 'I am function x.' ; }
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.

...이것은 당신이 하고 있는 일과 거의 똑같습니다. source ${script}단, 여기에서는 .dot디스크에 있는 파일의 내용을 가져오는 반면 stdin, 당신은 디스크에 있는 파일의 내용을 가져옵니다.

그러나 실행 스크립트와 실행 스크립트의 주요 차이점 ( : subshell )은 실행 스크립트의 실행 환경이 어떻게 생겼는지입니다. 스크립트를 실행하면새로운운영 환경 - 따라서 명령줄에서 명시적으로 내보내거나 선언하지 않는 한 현재 셸에서 선언된 모든 변수는 해당 환경으로 전송되지 않습니다. 이 동작은 다음과 같이 설명할 수 있습니다.

{   x() { printf "$FMT" "$0" "$var2" x ; } #describe env
    export FMT='argv0: %s\t\tvar2: %s\t\tI am function %s.\n' \
        var1=val1 #var1 and FMT are explicitly exported
    var2=shell_val2 #var2 is not
    cat >./script && #read out stdin to >./script
        chmod +x ./script && #then make ./script executable
        var3=val3 ./script #then define var3 for ./script's env and execute
} <<\SCRIPT ; x ; y #send heredoc to block's stdin then call {x,y}()
#!/usr/bin/sh
#read out by cat to >./script then executed in a separate environment
y() { printf "$FMT" "$0" "$var2" y ; } #describe env
echo "${var1:-#var1 is unset or null}" #$var1 if not unset or null else :-this} 
echo "${var2:-#var2 is unset or null}"
echo "${var3:-#var3 is unset or null}"
export var2=script_val2
x ; y #run x() ; y() in script
#v_end_v                                                                                                                                                                                          
SCRIPT

###OUTPUT###
val1
#var2 is unset or null
val3
./script: line 8: x: command not found
argv0: ./script         var2: script_val2               I am function y.
argv0: sh               var2: shell_val2                I am function x.
sh: line 18: y: command not found

이 동작은 프롬프트에서 실행하거나 현재 셸의 자식으로 실행하는 것과 다릅니다. 즉 ( : subshells ), 위에서 언급한 것처럼 실행 중인 자식은 명시적으로 편집해야 하는 반면 부모의 환경을 자동으로 상속한다는 점입니다 export.

var1=val1 ; export var2=val2
x() { echo 'I am function x.' ; }
( 
    printf '%s\n' "$var1" "$var2" 
    x
)

###OUTPUT###
val1
val2
I am function x.

.dot마지막으로 주목하고 싶은 점은 현재 셸에서 하는 것처럼 실행된 스크립트에 함수 정의가 포함된 파일을 가져오지 않고 상위 셸에서 실행된 스크립트로 함수를 내보내는 이식 가능한 방법이 없다는 것입니다 . 기본적으로 변수를 사용하는 것처럼 이식 source할 수 없습니다 . 그러나 실행 중인 스크립트 에 export function귀하가 있을 수 있다고 생각합니다 . 물론 하위 환경(실행 중이든 아니든)은 하위 환경과 함께 사라집니다.export fn_def='fn() { : fn body ; }'eval "$fn_def"

따라서 스크립트에 정의된 함수를 원하지만 스크립트 자체를 가져오고 싶지 않은 경우 함수를 일부 명령 및 해당 출력으로 읽어야 합니다 eval.

eval "$(cat ./script)"

하지만 본질적으로 똑같습니다 . ./script. 단지 덜 효율적일 뿐입니다. 그냥 받아도 됩니다.

이를 수행하는 가장 좋은 방법은 스크립트 자체에서 함수 정의를 제거하고 자체 파일에 넣은 다음 필요할 때 스크립트와 현재 셸 모두에서 가져오는 것입니다. 이와 같이:

{
    echo 'x() { echo "I am function x and my argv0 is ${0}." ; }' >./fn_x                                                                                                                                                         
    echo '. ./fn_x ; x' >./script                                                                                                                                                                                                 
    chmod +x ./script && ./script                                                                                                                                                                                                 
    . ./fn_x ; x                                                                                                                                                                                                                  
}
###OUTPUT###
I am function x and my argv0 is ./script.
I am function x and my argv0 is sh.

대화형 셸에서 항상 사용할 수 있도록 하려면 . /path/to/fn_xENV파일에 를 추가하세요. 예를 들어 에 있는 함수가 포함된 스크립트의 경우 bash다음 행을 다음에 추가합니다.foo/usr/bin~/.bashrc

. /usr/bin/foo

시작하는 동안 어떤 이유로 인해 해당 위치에서 스크립트를 사용할 수 없는 경우 쉘은 ENV예상대로 파일의 나머지 부분을 읽고 가져오지만 stderr함수 정의 파일을 가져오는 데 문제가 있음을 알려주는 진단 메시지를 인쇄합니다.

답변2

마이크 셀프의 답변"비하인드 스토리"에 대한 자세한 내용은 좋지만 정확한 제목 질문에 대한 간단하고 유용한 답변이 포함되어 있지 않기 때문에 여기에 또 다른 답변이 필요하다고 생각합니다.

매번 가져올 필요 없이 이 스크립트 파일의 기능을 어떻게 로드할 수 있습니까?

대답은 다음과 같습니다. 실행하는 모든 셸에서 사용할 수 있도록 .bashrc사용자로부터 가져오는 것입니다 ..bash_profile

예를 들어, 내 안에는 다음이 있습니다 .bash_profile.

if [ -d ~/.bash_functions ]; then
  for file in ~/.bash_functions/*.sh; do
    . "$file"
  done
fi

관련 정보