私は次の機能を持っています~/.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>
これの最良の部分は引用です。参考にしてください'"
予約見積、予約のみ可能\
引用符が削除されます。シェルを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