Bashスクリプトでコードブロックを早期に終了する方法(Perlの「最後の」ディレクティブに似ています)

Bashスクリプトでコードブロックを早期に終了する方法(Perlの「最後の」ディレクティブに似ています)

私が本当に好きなPerlの機能の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

タイトルの質問を避け、特定のエラー終了ケースに焦点を当てるために、エラーメッセージと終了全体を関数に入れ、breakingの代わりに直接呼び出します。これにより、デフォルトのコードパスでの追加ブロックとインデントレベルも防ぎます。

#!/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つの項目のみを含むループはfor1回の反復後に自然に終了するため、ここでは論理的です(しかしループ変数の名前を指定する必要があります)。

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

おすすめ記事