One feature of Perl that I really like is its generalization of looping control keywords to any curly-brace-delimited lexical block1.
For example, one can use Perl's last
directive to exit any such block, as the following toy script illustrates:
#!/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";
In this example, the first four statements in the curly-brace-delimited block are meant to stand in for a sequence of mandatory conditions: the failure of any of them should result in the printing of an error message (whose content is independent of which condition failed) and the termination of the script's execution.
I want to emphasize that the common response to all possible failures within the block (in this case, this response being a generic error message that does not distinguish among all the possible points of failure followed by termination) is an essential, non-negotiable aspect of this problem. In fact, I would argue that it is the aspect of the problem that makes being able to exit the code block from anywhere along it desirable.
IMPORTANT: In order to keep the example above easy to understand, the conditions are artificially simple. In practice, however, the evaluation of each condition may take up several lines of code! Please keep this in mind when you formulate your answer. The essential point of the example is that the code block can be exited from anywhere in the block.
I am looking for a good way to achieve this similar effect in a bash script.
I can't come up with anything better than this:
#!/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
...which is confusing at best, because it is using a looping construct (while
) where no looping is intended.
In a zsh script I can at least replace the strange-looking while
loop with a single-use anonymous function2:
() {
(( 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
}
I'd still be interested in learning of other ways to implement an early exit from a code block in a zsh script.
1 It is likely that this quick, from-memory description oversimplifies somewhat...
2 In fact, with this approach, one could also dispense with the overall_status
variable altogether, and test $?
instead.
EDIT: I added the "IMPORTANT: ..." clarification after the Perl example.
EDIT2: Emphasized the common response to all the failures within the block is an essential aspect of the problem.