for NAME [in WORDS ... ] ; do COMMANDS; done
forループの動作をオーバーライドしたいと思います。つまり、構文の意味を変更するときに、既存のBASHスクリプトの構文が同じままであることを望みます。たとえば、私はこのような人にCOMMANDS
なりたいです。coproc ( COMMANDS )
for i in $(seq 1 5);do sleep $[ 1 + $RANDOM % 5 ]; echo $i;done
~になる
for i in $(seq 1 5);do coproc (sleep $[ 1 + $RANDOM % 5 ]; echo $i);done
ベストアンサー1
TCLはできるそのようなもの。bash
TCLではありません;-)
同時のcoprocのため、Stéphanのきれいなzsh
解決策も参照してください。zsh
alias do='do coproc {' done='}; done'
それではなくに慣れる動作しますbash
が、2つの問題があります。bash
単一のcoprocのみが正しくサポートされています(下記参照)。しなければならない複数のコルーチンがある場合は、コルーチンの名前を指定し、bash
現在の名前に拡張子を適用せず(coproc $FOO
バグなど)、静的な名前のみを受け入れます(解決方法はeval
)。
しかし、それに基づいて実際には必要がないと仮定すると、coproc
次のようにすることができます。
shopt -s expand_aliases # enable aliases in a script
alias do='do (' done=')& done' # "do (" ... body... ")& done"
これは、do
各プリンシパルを背景サブシェルに「上昇」させる。ループの一番下で同期が必要な場合、a はwait
トリックを実行するか、重要なスクリプトに対してより選択的に処理します。
shopt -s expand_aliases
declare -a _pids
alias do='do (' done=')& _pids+=($!); done'
for i in $(seq 1 5);do sleep $[ 1 + $RANDOM % 5 ]; echo $i;done
wait ${_pids[*]}
_pids
配列の新しい各サブシェルを追跡します。
(を使用して do { ... } &
も機能しますが、追加のシェルプロセスが開始されます。)
参考にしてくださいおそらく、次のいずれもプロダクションコードには表示されません。
必要なものの一部を実行できますが(エレガントではなく強力ではありませんが)、主な問題は次のとおりです。
- 共同プロセス並列化だけでなく、他のプロセスとの同期相互作用のために設計されています。 (1つの効果は、stdinとstdoutがパイプに変わり、サブシェルとして異なる違いがある可能性があることです。
$$
) - さらに、bashのみ(最大4.4)単一の共同プロセスサポート一度。
- 体がバックグラウンドで実行されている場合は、ループ制御を中断して効果的に休憩を取ることができます
break
(しかし、これはここの簡単な例には影響しません)。exit
同時コプロセスの不完全な実装を含むbashの内部を大幅に変更せずにbashにこれを実行させる方法はありません。コマンドは、構文レベルの構造に暗黙的( )
または明示的にグループ化する必要があります。 bashスクリプトを前処理できますが、これには強力なパーサーが必要です(現在のパーサーには約6500行のC / bison入力があります)。{ }
for/do/done
(純粋な読者は今目を向けたいと思うかもしれません。)
エイリアスと関数を使用して「オーバーライド」できます。do
この場合、多くの複雑な問題(エスケープ、引用、拡張など)が発生する可能性があります。
function _do()
{
( eval "$@" ) &
}
shopt -s expand_aliases # allow alias expansion in a script
alias do="do _do"
# single command only
for i in {1..5}; do sleep $((1+$RANDOM%5)); done
ただし、複数のコマンドを渡すには、次をエスケープおよび/または引用する必要があります。
for i in {1..5}; do "sleep $((1+$RANDOM%5)); echo $i" ; done
この場合、alias
トリックを放棄して直接呼び出すことも、_do
ループを変更することもできます。
(穏やかな読者は今悲鳴を上げ、建物から逃げたいと思うかもしれません。)
これは配列を使用するエラーの可能性がわずかに少ないバージョンですが、コードをさらに修正する必要があります。
function _do()
{
local _cmd
printf -v _cmd "%s;" "$@"
( eval "$_cmd" ) &
}
cmd=( 'sleep $((1+$RANDOM%5))' )
cmd+=( 'echo $i' )
for i in {1..5}; do "${cmd[@]}" ; done
(今、このまともな読者は気絶したり、それ以上のダメージを受けないと思われます。)
結局、それが可能だからではなく、それが可能だからだ。良い考え:
function _for() {
local _cmd _line
printf -v _cmd '%s ' "$@"
printf -v _cmd "for %s\n" "$_cmd" # reconstruct "for ..."
read _line # "do"
_cmd+=$'do (\n'
while read _line; do
_cmd+="$_line"; _cmd+=$'\n' # reconstruct loop body
done
_cmd+=$') &\ndone' # finished
unalias for # see note below
eval "$_cmd"
alias for='_for <<-"done"'
}
shopt -s expand_aliases
alias for='_for <<-"done"'
for i in $(seq 1 5)
do
sleep $((1+$RANDOM%5))
echo $i
done
必要な唯一の変更は、do
/がdone
独自の行に表示され、done
ゼロ以上のハードタブにインデントする必要があることです。
上記は次のとおりです。
- ハイジャッキングは
for
本文をdone
。 - 関数は
for
引数に基づいてループを書き換えてから、HEREDOCから本文の残りの部分を読み込みます。 - 再構成されたボディには「(…)&」が挿入されて追加され
done
ました。 - 呼び出し、エイリアスを
eval
キャンセルするには(プレフィックスはエイリアスに適用されます。)for
\
注文する、キーワードの場合はunalias
復元が必要です)
繰り返しますが、これはあまり強力ではありません。ループのリダイレクトによってdone > /dev/null
問題が発生する可能性があり、ネストによって問題が発生する可能性があり、追加の参照が必要な操作によってfor
問題が発生する可能性があります。for (( ;; ))
上書きするために同じトリックを使用することはできませんdo
。ただし、うまくいくselect/do/done
と簡単になりますが、do
そうしようとすると構文エラー(リダイレクトを含む)が発生します。