
私が本当に好きなPerlの機能の1つは、ループ制御キーワードの一般化です。どの中括弧で区切られた語彙塊1.
たとえば、last
次のおもちゃスクリプトに示すように、Perlのコマンドを使用してそのブロックを終了できます。
#!/usr/bin/env perl
use strict;
my $value = $ARGV[0] || 0;
my $overall_status = 'failed';
{
$value > 0 and print "$value > 0\n" or last;
$value > 1 and print "$value > 1\n" or last;
$value > 2 and print "$value > 2\n" or last;
$value > 3 and print "$value > 3\n" or last;
$overall_status = 'ok';
}
die 'EARLY EXIT' unless $overall_status eq 'ok';
print "ok\n";
この例では、中括弧で区切られたブロックの最初の4つのステートメントは、次のことを示します。一連の前提条件:失敗どのどちらもエラーメッセージを出力し(内容は失敗条件とは無関係です)、スクリプトの実行を終了する必要があります。
私が強調したいのはブロック内で発生する可能性のあるすべてのエラーに対する共通の応答(この場合、この応答は、シャットダウン後に発生する可能性があるすべての障害点を区別しない一般的なエラーメッセージです。)問題の重要で交渉できない側面です。。実際、コードブロックのどこでも終了できるようにすることが問題の一面だと思います。
重要:上記の例を理解しやすくするために、条件を人為的に単純化しました。しかし実際には各条件を評価するには、複数行のコードが必要な場合があります。 答えを定式化するときにこの点に留意してください。 この例のポイントは、コードブロックがブロック内のどこでも終了できることです。
Bashスクリプトで同様の効果を得るための良い方法を探しています。
私はこれより良いことを考えることはできません:
#!/bin/bash
overall_status=failed
value=$1
while true; do
(( value > 0 )) && echo "$value > 0" || break;
(( value > 1 )) && echo "$value > 1" || break;
(( value > 2 )) && echo "$value > 2" || break;
(( value > 3 )) && echo "$value > 3" || break;
overall_status=ok
break
done
if [[ "$overall_status" != ok ]]; then
echo 'EARLY EXIT' >&2
exit 1
fi
while
...ループを意図しないループ構文()を使用しているので混乱しています。
zshスクリプトで少なくとも奇妙に見えるループを1回限りのwhile
匿名関数2に置き換えることができます。
() {
(( value > 0 )) && echo "$value > 0" || return 1;
(( value > 1 )) && echo "$value > 1" || return 1;
(( value > 2 )) && echo "$value > 2" || return 1;
(( value > 3 )) && echo "$value > 3" || return 1;
overall_status=ok
}
私はまだzshスクリプトでコードブロックの早期終了を実装する他の方法を学ぶことに興味があります。
1この高速メモリベースの説明は少し過度に単純化することができます...
2実際には、このアプローチを使用してoverall_status
変数を完全に省略してテストすることもできます$?
。
編集する:Perlの例の後に「重要:...」メモを追加しました。
編集2:ブロック内のすべての欠陥に対する共通の対応を強調することは、問題の重要な側面です。
答え1
タイトルの質問を避け、特定のエラー終了ケースに焦点を当てるために、エラーメッセージと終了全体を関数に入れ、break
ingの代わりに直接呼び出します。これにより、デフォルトのコードパスでの追加ブロックとインデントレベルも防ぎます。
#!/bin/bash
die() {
echo 'error: mandatory conditions not met!' >&2
exit 1
}
value=$1
(( value > 0 )) && echo "$value > 0" || die;
(( value > 1 )) && echo "$value > 1" || die;
(( value > 2 )) && echo "$value > 2" || die;
(( value > 3 )) && echo "$value > 3" || die;
明らかに、条件付きブロックの後に実行を続行したい場合は機能しません。この場合、空のループが必要です。 1つの項目のみを含むループはfor
1回の反復後に自然に終了するため、ここでは論理的です(しかしループ変数の名前を指定する必要があります)。
for __ in once; do
echo this prints only once
(( value > 0 )) && echo "$value > 0" || break;
...
done
echo continuing from here
一般に、ユーザーが理解できるように、各エラー条件ごとに異なるエラーメッセージを提供することをお勧めします。どの要求が失敗しました。これはdie
、メッセージをパラメータとして渡すことによって実現できます。場合によっては、終了する前に検査の1つが失敗したかどうかにかかわらず、すべての検査を実行することも可能です。これにより、ユーザーはすべての問題を同時に認識できます。
#!/bin/bash
errexit=
error() {
echo "error: $1" >&2
errexit=1 # global
}
value=$1
(( value > 0 )) && echo "$value > 0" || error 'value <= 0';
(( value > 1 )) && echo "$value > 1" || error 'value <= 1';
(( value > 2 )) && echo "$value > 2" || error 'value <= 2';
(( value > 3 )) && echo "$value > 3" || error 'value <= 3';
[ "$errexit" ] && exit 1
答え2
私はBashであなたが望むものを達成する方法を知りません(あなたが識別したオプションに加えて)。
あなたが望むことを私がやろうとしているなら、おそらく次のようにします。
#!/bin/bash
overall_status=failed
value=$1
if (( value > 0 )); then echo "value > 0"
elif (( value > 1 )); then echo "value > 1"
elif (( value > 2 )); then echo "value > 2"
elif (( value > 3 )); then echo "value > 3"
else overall_status=ok
fi
if [[ "$overall_status" != ok ]]; then
echo 'EARLY EXIT' >&2
exit 1
fi
答え3
コマンドグループを早く終了する簡単な方法は次のとおりです。そしてリスト&&
orステートメントへの短い選択肢である最初の失敗時に中断するアイテムを使用します。例のダミーwhileループを次のように置き換えます。if
case
(( value > 0 )) && echo "$value > 0" &&
(( value > 1 )) && echo "$value > 1" &&
(( value > 2 )) && echo "$value > 2" &&
(( value > 3 )) && echo "$value > 3" &&
overall_status=ok
または、zsh匿名関数と同様に名前付きbash関数がないのはなぜですか?
#!/bin/bash
arg=$1
function f() {
value=$1
(( value > 0 )) && echo "$value > 0" || return 1
(( value > 1 )) && echo "$value > 1" || return 1
(( value > 2 )) && echo "$value > 2" || return 1
(( value > 3 )) && echo "$value > 3" || return 1
return 0
}
if f arg; then
exit 0
else
echo 'EARLY EXIT' >&2
exit 1
fi
答え4
あなたも同じ方法で行います。
Perlでは、{...}
ブロックは一度だけ実行されるループです。したがって、シェルで同じ操作、つまり一度だけ実行されるループを実行します。break
代わりに使用してくださいlast
:
for _ in "$_"; do
foo || break
bar || break
baz || break
{
quux
moo
} || break
done
$value > 0 and print "$value > 0\n" or last;
foo && bar || quux
これはシェルと同じ理由で悪いことです。失敗するとprint
どうなりますか?シェルとは異なり、Perlには三項演算子があります?:
。それを書く:
$value > 0 ? print "$value > 0\n" : last;
if(...){...}else{...}
または、簡潔すぎると思われる場合は、明示的な表現を使用してください。
さらに、これは#!/usr/bin/env perl
問題を要求しています。/usr/bin/perl
ディストリビューションで確認できるバージョンだけでなく、ユーザーパスにある可能性のあるすべてのPerlバージョンに対してスクリプトをテストする必要があるのはなぜですか。