如何提前退出 bash 脚本中的代码块(类似于 perl 的 `last` 指令)?

kjo*_*kjo 0 bash zsh perl shell-script control-flow

我真正喜欢的 Perl 的一个特性是它将循环控制关键字泛化到任何花括号分隔的词法块1

例如,可以使用 Perl 的last指令退出任何此类块,如下面的玩具脚本所示:

#!/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";
Run Code Online (Sandbox Code Playgroud)

在这个例子中,花括号分隔的块中的前四个语句意味着代表一系列强制性条件:其中任何一个失败都应该导致打印错误消息(其内容独立于哪个条件失败)并终止脚本的执行。

我想强调的是,对块内所有可能的故障共同响应(在这种情况下,此响应是一个通用错误消息,不区分所有可能的故障点,然后终止)是必不可少的,不可协商的方面这个问题。事实上,我认为是问题的一个方面使得能够从任何地方退出代码块是可取的。

重要提示:为了使上面的示例易于理解,条件人为地简化了。然而,在实践中,每个条件的评估可能需要几行代码! 当您制定答案时,请记住这一点。 该示例的要点是代码块可以从块中的任何位置退出。


我正在寻找一种在 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
Run Code Online (Sandbox Code Playgroud)

...这充其量是令人困惑的,因为它使用了一个while不打算循环的循环结构 ( )。

在 zsh 脚本中,我至少可以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
}
Run Code Online (Sandbox Code Playgroud)

我仍然有兴趣学习在 zsh 脚本中实现早期退出代码块的其他方法。


1这种快速的、凭记忆的描述可能某种程度上过于简单化了……
2事实上,通过这种方法,人们还可以overall_status完全省去变量,$?而是进行测试。

编辑:我在 Perl 示例之后添加了“重要:...”说明。

EDIT2:强调对块内所有故障的共同响应是问题的一个重要方面。

ilk*_*chu 11

回避标题中的问题,并专注于错误退出的特殊情况,我只是将整个错误消息和退出放在一个函数中,然后直接调用它而不是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;
Run Code Online (Sandbox Code Playgroud)

显然,如果您想在带条件的块之后继续执行,这将不起作用。在这种情况下,您需要一个空循环。for只有一项的循环在这里可能是合乎逻辑的,因为它在一次迭代后自然结束(但您需要命名循环变量)。

for __ in once; do
    echo this prints only once
    (( value > 0 )) && echo "$value > 0" || break;
    ...
done
echo continuing from here
Run Code Online (Sandbox Code Playgroud)

一般来说,我还建议为每个错误情况提供不同的错误消息,以便用户知道哪个要求失败了。这可以通过die将消息作为参数来完成。在某些情况下,也可以不管是否失败都进行所有检查,然后才退出。这将使用户同时了解所有问题。

#!/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
Run Code Online (Sandbox Code Playgroud)