나는 다음과 같은 기능을 가지고 있습니다 ~/.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 exec
target을 대상 함수의 이름으로 사용합니다.
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