同様の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>

これの最良の部分は引用です。参考にしてください'"予約見積、予約のみ可能\引用符が削除されます。シェルを2回評価する必要がある場合は、おそらくこの理由が原因です。$expansionおすすめしたいhere-documentなぜなら、引用符はたくさん比較するeval

とにかく、上記のコードは入力ファイルのように動作します。3<~/heredoc.fileただ待っています。_gem_dec()関数が起動し、入力を受け入れます。/dev/fd/3

だから私たちが始めたとき_gem_dec()私が最初にしたことは投げることでした。みんな次のステップはシェル拡張を2回評価することで、位置引数を含めることは望ましくありません。$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}はいただシェルは、2つの方法のいずれかで呼び出しを解釈できます。$1または_${1}その後、使用commandこの機能は現在完全に重複しています。それでも同じミスを2回連続したくないので守りました。

問い合わせ者がこれを受け入れることができず、リクエストをキャンセルしたい場合_次に、現在の形式で編集します。_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

関連情報