類似のbash関数を一度に定義する方法


10

私はこれらの機能を持っています~/.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)

回答:


8
$ 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適用に関する通常の注意事項。


つまりfor loop?for loop一般的に宣言されている変数は消えます-同じ理由で同じ関数を期待します。
mikeserv 2014年

何があなたをそう思わせたのですか?bash -c 'for i in 1; do :; done; echo $i'=> 1type関数はループの範囲外に存在することを明らかに示しています。
エイドリアンFrühwirth

1
@mikeserv bashの動的スコープを使用しても、関数全体localのスコープに対してローカルな変数しか取得できませんが、ループ後に変数が「消える」ことはありません。実際、ここには関数が含まれていないため、この場合は変数を定義することもできません。local
エイドリアンFrühwirth14年

右-forループのローカル-それらはローカルにスコープされています。親のループシェルが消えるとすぐに消えます。これはここでは起こりませんか?
mikeserv 2014年

いいえ、先ほど説明したように、シェルスクリプトと私の投稿には「forループのローカル」などの概念はありません。上記のコメントの例では、それを明確に示しています。
AdrianFrühwirth

7
_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は、1つのパラメーターを受け取り、それをbundle execターゲットとそのターゲットとなる関数の名前の両方として事前評価することです。

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 $#は、これらの不要な予防策の1つです。

ファイルは次のように指定されている場合でも、<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

また、a here-documentはコードブロック内でファイルをインラインで記述するための手段に過ぎないため、次のようにします。

<<'HEREDOC'
[$CODE]
HEREDOC

私たちもそうするかもしれません:

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

非常に重要な違いが1つあります。そうしない場合の、その後のシェルは、シェルのためにそれを評価するように:"'\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を2回評価する必要がある場合はhere-document、引用符がに比べてはるかに簡単であるため、シェルを推奨しevalます。

とにかく、上記のコードは3<~/heredoc.file_gem_dec()関数が実行されてでその入力を受け入れるのを待つだけのように、フィードされたファイルとまったく同じです/dev/fd/3

したがって_gem_dec()、最初に行うことは、すべての位置パラメーターを投げることです。次のステップは、2回評価されたシェル展開であり、含ま$expansionsれているものを現在の$1 $2 $3...パラメーターとして解釈したくないためです。だから私:

shift $#

shiftpositional parameters指定した数だけ破棄$1し、残っているものから始めます。私はと呼ばれるのであれば_gem_dec one two threeプロンプト_gem_dec's $1 $2 $3になり、位置パラメータone two threeと、全電流位置のカウント、または$#私はそれから呼び出された場合は3になりますshift 2,の値onetwoされるだろう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ことができます。同じことが、私たちを除いて、ここに真である特別な私たちのファイルディスクリプタのためのファイル、または私たちのところである- そして私達は私達の機能を宣言した-のインラインファイルがパス化されています。そして、それはそれがどのように機能するかです。. sourcecharacter device. /dev/fd/3here-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適切なを。

アスカーの使用にevincedされるように、これは、かかわらず、普遍的習慣ではないcommand時に呼び出します$1

さらに詳しく調べてみると、次のことがわかります。

要求者はguard, rspec, or rake、呼び出されたときに、ファイルが存在するまたは存在しないruby同じ名前の関数を新しくコンパイルするという名前のシェル関数を望んでいます。シェル関数は同じ名前ifの関数を実行する必要があります。Gemfile$PATH if Gemfileruby

私はまた、変更されたため、これが以前働いていないだろう$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の全ての機能では現在、完全に冗長化されています。それでも、同じ過ちを2回犯したくないのでそのままにしておきます。とにかく続けて。

これが質問者に受け入れられず、彼/彼女が_完全に取り除きたい場合、現在の形式では、_underscore私が理解しているように、彼/彼女の要件を満たすために質問者が行う必要があるのは、出力を編集することだけです。

その変更の他に、元の構文ではなく、使用する関数&&||シェルの短絡条件を編集しましたif/then。このようにして、commandステートメントはがにない場合にのみ評価されますただし、この変更では、ステートメントが存在しない場合にステートメントが実行されないようにするためにを追加する必要がありますが、関数は0以外を返しますGemfile$PATHreturn $?bundleGemfileruby $1

最後に、このソリューションは移植可能なシェル構造のみを実装することに注意してください。言い換えれば、これはPOSIX互換性を主張するどのシェルでも同じ結果を生成するはずです。私は、すべてのPOSIX互換システムが処理しなければならないと主張するのは、当然のことながら、ナンセンスであろうがruby bundleディレクティブを、それ呼びかけ少なくともシェルの要請にかかわらず、呼び出し元のシェルがあるかどうかの同じように振る舞うべきshdashまた、上記の意志の作業予想通り(途中-健全少なくとも推定shoptsとにかく)の両方にbash及びzsh


私はあなたのコードをに入れて~/.bashrcを呼び出し. ~/.bashrc、次にこれら3つの関数が実行されます。環境によって動作が異なるので、質問に自分の環境を追加しました。その上、なぜ最後の行_guard ; _rspec ; _rakeが必要なのか理解できませんでした。私はshiftファイル記述子について検索しましたが、これらは私の現在の理解を超えているようです。
ironsand

それらを呼び出し可能であることを示すために、そこに配置しました。申し訳ありません-私は今エコーを入れました。したがって、これまでに説明したように、関数として呼び出すことができます。
mikeserv 2014年

@Tetsu-今はもっと理にかなっていますか?
mikeserv 2014年

私はあなたの答えを3回読みましたが、正直に言って、説明を理解するにはさらに知識が必要だと言っています。私はあなたにとても感謝していますが、私はより多くの経験を積んだときにもう一度読んでいきます。
ironsand 2014年

@徹たぶんもっとはっきりしているかも…?以前に犯した間違いに気づき、今は修正したと思います。よろしければお知らせください。
mikeserv 2014年

2
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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.