유사한 bash 함수를 한 번에 정의하는 방법

유사한 bash 함수를 한 번에 정의하는 방법

나는 다음과 같은 기능을 가지고 있습니다 ~/.bashrc:

function guard() {
    if [ -e 'Gemfile' ]; then
    bundle exec guard "$@"
    else
    command guard "$@"
    fi
}
function rspec() {
    if [ -e 'Gemfile' ]; then
    bundle exec rspec "$@"
    else
    command rspec "$@"
    fi
}
function rake() {
    if [ -e 'Gemfile' ]; then
        bundle exec rake "$@"
    else
        command rake "$@"
    fi
}

보시다시피 기능은 매우 유사합니다. 이 3가지 함수를 한번에 정의하고 싶습니다. 만드는 방법이 있나요?

환경

bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)

답변1

$ cat t.sh
#!/bin/bash

for func in guard rspec rake; do
        eval "
        ${func}() {
                local foo=(command ${func})
                [ -e 'Gemfile' ] && foo=(bundle exec ${func})
                \"\${foo[@]}\" \"\$@\"
        }
        "
done

type guard rspec rake

.

$ ./t.sh
guard is a function
guard ()
{
    local foo=(command guard);
    [ -e 'Gemfile' ] && foo=(bundle exec guard);
    "${foo[@]}" "$@"
}
rspec is a function
rspec ()
{
    local foo=(command rspec);
    [ -e 'Gemfile' ] && foo=(bundle exec rspec);
    "${foo[@]}" "$@"
}
rake is a function
rake ()
{
    local foo=(command rake);
    [ -e 'Gemfile' ] && foo=(bundle exec rake);
    "${foo[@]}" "$@"
}

신청에 관한 일반적인 고려 사항 eval.

답변2

_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."

위의 내용은. source /dev/fd/3그것은 공급된다_gem_dec()사전 평가된 함수로 호출될 때마다here-document. _gem_dec's유일한 작업은 매개변수를 수신하고 이를 다음과 같이 사전 평가하는 것입니다.bundle exectarget을 대상 함수의 이름으로 사용합니다.

NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.

하지만 위의 경우에는 위험이 없다고 생각합니다.

위의 코드 블록을 복사하면.bashrc파일, 쉘 기능뿐만 아니라_guard(), _rspec()그리고_rake()로그인 시 선언했지만_gem_dec()함수는 쉘 프롬프트에서 언제든지 실행될 수도 있습니다.(또는 다른 수단)그래서다음과 같이 언제든지 새로운 템플릿 함수를 선언할 수 있습니다.

_gem_dec $new_templated_function_name

이것이 그렇지 않을 것이라는 것을 나에게 보여준 @Andrew에게 감사드립니다.for loop.

하지만 어떻게?

나는 사용한다3위에 보관할 파일 설명자stdin, stdout, and stderr, or <&0 >&1 >&2습관적으로 켜진 것입니다. 하지만 여기에 구현한 다른 기본 예방 조치도 마찬가지입니다. 생성된 함수는 너무 간단하기 때문에 실제로는 필요하지 않습니다. 그러나 이는 좋은 습관입니다. 부르다shift $#이는 또 다른 불필요한 예방 조치입니다.

그럼에도 불구하고 파일이 다음과 같이 지정되면<input또는>output그리고[optional num]<file또는 [optional num]>file리디렉션커널은 이것을 다음을 통해 액세스할 수 있는 파일 설명자로 읽습니다.character device특수 파일이 들어있습니다/dev/fd/[0-9]*.만약에[optional num]지정자가 생략된 경우0<file입력이 다음과 같다고 가정합니다.1>file출력용. 생각해 보세요:

l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6

( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2

( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6

( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3

( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6

그리고 때문에here-document다음과 같이 코드 블록에서 파일을 인라인으로 설명하는 방법입니다.

<<'HEREDOC'
[$CODE]
HEREDOC

우리는 이렇게 할 수도 있습니다:

echo '[$CODE]' >/dev/fd/0

매우 중요차이점. 그렇지 않으면"'\quote'"이것<<"'\LIMITER"'중 하나here-document그런 다음 쉘은 쉘을 평가합니다.$expansion좋다:

echo "[$CODE]" >/dev/fd/0

그래서_gem_dec(),이것3<<-FUNC here-document입력으로 평가될 때 파일, 해당 경우와 동일3<~/some.file 와는 별개로우리가 떠났으니까FUNC리미터따옴표가 없으면 먼저 평가됩니다.$expansion.중요한 것은 그것이 입력이라는 것입니다. 즉, 다음에만 존재한다는 뜻입니다._gem_dec(),그러나 그것은 또한 전에 평가될 것입니다_gem_dec()쉘이 이를 읽고 평가해야 하기 때문에 함수가 실행됩니다.$expansions앞으로입력으로 전달하십시오.

우리가 해보자guard,예를 들어:

_gem_dec guard

따라서 먼저 쉘은 입력을 처리해야 하며 이는 다음을 읽는다는 것을 의미합니다.

3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC

파일 설명자 3으로 이동하여 해당 쉘 확장을 평가합니다. 이 시점에서 실행하면:

cat /dev/fd/3

또는:

cat <&3

모두 동등한 명령이므로 *가 표시됩니다.

_guard() { [ ! -e 'Gemfile' ] && { 
    command guard "$@" ; return $?
    } || bundle exec guard "$@"
}

...함수의 코드가 실행되기 전입니다. 이것은 기능적이다<input,결국. 더 많은 예를 보려면 다른 질문에 대한 내 답변을 참조하세요.여기.

-dash(* 엄밀히 말하면 완전히 맞는 말은 아닙니다. 이전에 행간을 사용했기 때문에 위의 내용은 모두 왼쪽 정렬됩니다. 하지만 애초에 가독성을 높이기 위해 here-doc limiter사용한 것이므로 이전 내용을 제거하지는 않겠습니다. 당신의 독서...)-dash<tab-insert><tab-inserts>

이것에 대한 가장 좋은 부분은 인용문입니다. 참고하세요'"예약 견적, 예약만 가능\따옴표가 제거됩니다. 쉘을 두 번 평가해야 한다면 아마도 이런 이유 때문일 것입니다.$expansion나는 추천하고 싶다here-document왜냐하면 따옴표는많은비교하다eval.

어쨌든 이제 위 코드는 입력 파일처럼 동작합니다.3<~/heredoc.file그냥 기다리고 있어_gem_dec()함수가 시작되고 입력을 받아들입니다./dev/fd/3.

그래서 우리가 시작할 때_gem_dec()내가 가장 먼저 한 일은 던지는 일이었다.모두다음 단계는 쉘 확장을 두 번 평가하는 것이므로 위치 인수가 포함되는 것을 원하지 않습니다.$expansions내 현재의 것으로 해석됩니다.$1 $2 $3...매개변수. 그래서 나는:

shift $#

shift최대한 버린다positional parameters당신이 지정하는대로$1나머지와 함께. 그래서 내가 전화하면_gem_dec one two three프롬프트를 따르십시오_gem_dec's $1 $2 $3위치 매개변수는 다음과 같습니다.one two three현재 직위의 총 수, 또는$#3입니다. 내가 그때 전화하면shift 2,가치one그리고two~ 할 것이다shift편집하다멀리, 가치$1될 것입니다three그리고$#으로 확장될 예정입니다.1.그래서shift $#그냥 모두 버리세요. 이렇게 하는 것은 철저한 예방 조치이며 한동안 이런 일을 하고 나서 몸에 익은 습관일 뿐입니다. 이것은(subshell)명확성을 위해 조금 확장해 보겠습니다.

( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3

( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1

( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0

그럼에도 불구하고 다음 단계는 마법이 일어나는 곳입니다. 만약 너라면. ~/some.sh쉘 프롬프트에서 선언된 모든 함수 및 환경 변수~/some.sh그런 다음 쉘 프롬프트에서 호출할 수 있습니다. 우리 빼고는 상황은 똑같아. source 이것character device특수 파일에 대한 파일 설명자 또는. /dev/fd/3- 이건 우리꺼야here-document인라인 파일의 경로가 지정되었으며 함수를 선언했습니다. 이것이 작동하는 방식입니다.

_guard

지금 원하는 대로 하세요_guard함수가 수행해야 하는 작업입니다.

부록:

위치를 저장하는 좋은 방법:

f() { . /dev/fd/3
} 3<<-ARGS
    args='${args:-"$@"}'
ARGS

편집하다:

처음 이 질문에 대답했을 때 나는 쉘 선언 문제에 더 집중했습니다.function()현재 셸 내에서 지속되는 다른 함수를 선언하는 기능$ENV다리미질요청자가 지속성 기능을 사용하여 무엇을 할 것인지에 대해 내가 아는 것보다 더 많습니다. 그때 제가 원래 제공했던 솔루션이3<<-FUNC다음 형식을 취합니다.

3<<-FUNC
    _${1}() { 
        if [ -e 'Gemfile' ]; then
            bundle exec $1 "\$@"
        else 
            command _${1} "\$@"
    }
FUNC

선언 함수의 이름을 구체적으로 변경했기 때문에 질문자가 의도한 대로 작동하지 않을 수 있습니다.$1도착하다_${1}그렇게 부르면_gem_dec guard예를 들어, 다음과 같은 결과가 발생합니다._gem_dec라는 이름의 파일을 선언하십시오._guard그냥보다는guard.

노트:이 동작은 나에게 습관의 문제입니다. 저는 일반적으로 쉘 함수가 다음을 수행해야 한다고 가정합니다.오직그들만의_namespace그들의 침입을 피하기 위해namespace껍데기commands적절한.

그러나 이는 질문자가 사용한 것처럼 일반적인 관행이 아닙니다.command부르다$1.

추가 조사를 통해 나는 다음과 같은 사실을 믿게 되었습니다.

질문자는 쉘 함수의 이름을 지정하고 싶어합니다.guard, rspec, or rake호출되면 다시 컴파일됩니다.ruby같은 이름의 함수if문서Gemfile존재하다$PATH 또는 if Gemfile존재하지 않으면 쉘 함수를 실행해야 합니다ruby같은 이름의 함수입니다.

나도 변경했기 때문에 이전에는 작동하지 않았습니다.$1소환된command읽다:

command _${1}

이로 인해 실행이 발생하지 않습니다.ruby쉘 함수가 컴파일하는 함수는 다음과 같습니다.

bundle exec $1

네가 볼 수 있기를 바라(내가 한 일처럼)질문자는 단지command전체 간접 지정namespace왜냐하면command실행 파일 호출을 선호합니다$PATH같은 이름의 쉘 함수를 통해.

내 분석이 맞다면(질문하시는 분이 확인해주셨으면 좋겠습니다)그런 다음 이:

_${1}() { [ ! -e 'Gemfile' ] && { 
    command $1 "\$@" ; return \$?
    } || bundle exec $1 "\$@"
}

호출을 제외하고 이러한 조건을 더 잘 충족해야 합니다.guard프롬프트에 따라오직실행 파일을 실행해 보세요$PATH명명 된guard통화하는 동안_guard프롬프트에서 확인합니다Gemfile's존재하고 그에 따라 컴파일됩니다.또는구현하다guard실행 가능 날짜$PATH.그래서namespace적어도 내 생각에는 보호를 받고 있으므로 질문자의 의도는 여전히 달성됩니다.

실제로 쉘 함수를 가정해 보겠습니다._${1}()및 실행 파일${PATH}/${1}오직쉘은 두 가지 방법 중 하나로 호출을 해석할 수 있습니다.$1또는_${1}그런 다음 사용command이 기능은 이제 완전히 중복됩니다. 그래도 같은 실수를 두번 연속 하고 싶지 않아서 지켰습니다.

문의자가 이를 수락할 수 없고 요청을 취소하려는 경우_그런 다음 현재 형식으로 편집합니다._underscore내가 이해하는 바로는 질문자가 자신의 요청을 만족시키기 위해 해야 할 일이 전부여야 합니다.

이 변경사항 외에도 사용할 기능도 편집했습니다.&&그리고/또는||껍데기단락원래 상태보다는if/then통사론. 따라서command명령문은 평가만 수행됩니다.별말씀을요만약에Gemfile여기가 아니야$PATH.이 수정 사항은 실제로 추가되어야 합니다.return $?그러나 보장하기 위해bundle이벤트에서 문이 실행되지 않음Gemfile존재하지 않지만ruby $1함수는 나누기를 반환합니다.0.

마지막으로 이 솔루션은 이식 가능한 셸 구성만 구현한다는 점을 지적하고 싶습니다. 즉, 이는 POSIX 호환성을 주장하는 모든 셸에서 동일한 결과를 생성해야 합니다. 물론, 모든 POSIX 호환 시스템이 다음 사항을 처리해야 한다고 주장한다면ruby bundle지시문을 호출하는 쉘 명령은 호출하는 쉘이 명령인지 여부에 관계없이 동일하게 동작해야 합니다.sh또는 dash.위의 내용도 예상대로 작동합니다.(적어도 절반의 정신이 있다고 가정 shopts)동시에bash그리고 zsh.

답변3

function threeinone () {
    local var="$1"
    if [ $# -ne 1 ]; then
        return 1
    fi
    if ! [ "$1" = "guard" -o "$1" = "rspec" -o "$1" = "rake" ]; then
        return 1
    fi
    shift
    if [ -e 'Gemfile' ]; then
        bundle exec "$var" "$@"
    else
        command "$var" "$@"
    fi
}

threeinone guard
threeinone rspec
threeinone rake

관련 정보