在Bash脚本中,如果发生某种情况,如何退出整个脚本?


717

我正在Bash中编写脚本来测试一些代码。但是,如果编译代码首先失败,则运行测试似乎很愚蠢,在这种情况下,我将中止测试。

有没有一种方法可以在不将整个脚本包装在while循环内并使用break的情况下做到这一点?像dun dun dun goto之类的东西?

Answers:


808

尝试以下语句:

exit 1

替换1为适当的错误代码。另请参阅具有特殊含义的退出代码


4
@CMCDragonkai,通常任何非零代码都可以使用。如果您不需要任何特别的东西,则可以1始终使用。如果该脚本打算由另一个脚本运行,则您可能需要定义自己的一组具有特定含义的状态代码。例如,1==测试失败,2==编译失败。如果脚本是其他内容的一部分,则可能需要调整代码以匹配此处使用的实践。例如,当部分测试套件由automake运行时,该代码77用于标记已跳过的测试。
2014年

21
不,这也将关闭窗口,而不仅仅是退出脚本
Toni Leigh

7
@ToniLeigh bash没有“窗口”的概念,您可能对自己的特定设置(例如终端仿真器)的功能感到困惑。
Michael Foukarakis

3
@ToniLeigh如果要关闭“窗口”,则可能是将exit #命令放在函数而不是脚本中。(在这种情况下,请使用return #。)
杰米

1
@Sevenearths 0表示已成功,因此exit 0退出脚本并返回0(告诉其他可能使用此脚本结果的脚本成功)
VictorGalisson

689

使用set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

脚本将在失败的第一行之后终止(返回非零退出代码)。在这种情况下,command-that-fails2将不会运行。

如果要检查每个命令的返回状态,则脚本将如下所示:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

使用set -e它将看起来像:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

任何失败的命令都将导致整个脚本失败并返回退出状态,您可以使用$检查该退出状态。如果您的脚本很长,或者您正在构建很多东西,那么在各处添加返回状态检查将变得非常难看。


10
通过,set -e您仍然可以使某些命令错误退出而无需停止脚本:command 2>&1 || echo $?
Adobe

6
set -e如果管道或命令结构返回非零值,将中止脚本。例如foo || bar将失败只有两个foobar返回非零值。通常,编写良好的bash脚本将在您set -e开始添加时起作用,并且该添加将作为自动的健全性检查起作用:如果出现任何问题,请中止该脚本。
Mikko Rantalainen

6
如果将命令管道在一起,则通过设置set -o pipefail选项也可能导致命令失败。
杰克·比辛格

18
实际上,没有的惯用代码set -e将是公正的make || exit $?
2013年

4
你也有set -u。看一看非官方的bash严格模式set -euo pipefail
Pablo A

235

SysOps的一个人曾经教过我三指爪技术:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

这些功能是* NIX OS和shell健壮的。将它们放在脚本的开头(bash或其他方式),try()语句和代码的开头。

说明

(根据 飞羊的评论)。

  • yell:将脚本名称和所有参数打印到stderr
    • $0 是脚本的路径;
    • $* 都是论点。
    • >&2表示>将stdout重定向到&pipe21stdout本身。
  • die的功能与相同yell,但退出状态非零退出状态,表示“失败”。
  • try使用||(boolean OR),它仅在左侧失败时评估右侧。
    • $@又是所有论点,但又不同

3
我对UNIX脚本还很陌生。您能解释一下上述功能如何执行吗?我看到了一些我不熟悉的新语法。谢谢。
kaizenCoder

15
yell$0是脚本的路径。$*都是论点。>&2表示“ >将标准输出重定向到&管道2”。管道1本身就是标准输出。因此yell在所有参数之前加上脚本名称,并输出到stderr。die的作用与yell相同,但退出状态为非0退出,表示“失败”。try使用boolean或||,仅在左侧没有失败时才评估右侧。$@又是所有论点,但又有所不同。希望能解释一切
飞羊

1
我将其修改为die() { yell "$1"; exit $2; }使您可以通过传递消息并退出代码die "divide by zero" 115
Mark Lakata

3
我可以看到如何使用yelldie。但是,try没有那么多。您能提供一个使用示例吗?
kshenoy

2
嗯,但是如何在脚本中使用它们呢?我不明白“ try()您的语句和代码所依据的”的含义。
TheJavaGuy-IvanMilosavljević

33

如果将使用调用脚本source,则可以使用return <x>where <x>将是脚本退出状态(对于错误或false,请使用非零值)。但是,如果您调用可执行脚本(即直接使用其文件名),则return语句将导致抱怨(错误消息“ return:只能从函数或源脚本中返回”)。

如果exit <x>使用代替,则使用调用脚本时source,它将导致退出启动脚本的外壳,但是可执行脚本将按预期终止。

要在同一脚本中处理任何一种情况,都可以使用

return <x> 2> /dev/null || exit <x>

这将处理任何合适的调用。假设您将在脚本的顶层使用此语句。我建议不要直接从函数中退出脚本。

注意:<x>应该只是一个数字。


在函数内部具有返回/退出的脚本中对我不起作用,即是否可以在不存在外壳的情况下退出函数内部,但无需使函数调用者注意对函数返回代码的正确检查?

@jan调用者对返回值执行(或不执行)的操作是完全正交的(即独立于)您从函数返回的方式(无论调用如何,都无需退出外壳)。它主要取决于调用者代码,这不是本问答的一部分。您甚至可以根据调用者的需求定制函数的返回值,但是此答案并不限制此返回值可以是什么。
kavadias 18/12/13

11

我经常包括一个名为run()的函数来处理错误。我要进行的每个调用都传递给此函数,因此在遇到故障时会退出整个脚本。与set -e解决方案相比,此方法的优势在于,当行失败时,脚本不会以静默方式退出,并且可以告诉您问题出在哪里。在以下示例中,第三行未执行,因为脚本在调用false时退出。

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

1
男人,由于某种原因,我真的很喜欢这个答案。我知道它有点复杂,但似乎很有用。考虑到我不是bash专家,这使我相信我的逻辑是错误的,并且这种方法有问题,否则,我觉得其他人会给予更多的赞赏。那么,此功能有什么问题呢?我在这里应该注意什么吗?

我不记得使用eval的原因,该函数在cmd_output = $($ 1)时可以正常工作
Joseph Sheedy

我只是将其作为复杂的部署过程的一部分实施,效果很好。谢谢,这里有评论和赞誉。
Fuzzygroup

确实很棒的工作!这是最简单有效的解决方案。对我来说,我在FOR循环中的命令之前添加了此内容,因为FOR循环不会使用set -e option。然后命令,因为它是一个带有参数,我用单引号,以避免bash的问题,像这样:runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'。注意我将函数的名称更改为runTry。
Tony-Caffe

1
eval如果您接受任意输入,则可能会很危险,但否则看起来非常不错。
dragon788

5

除了if构造,您还可以利用短路评估

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

请注意一对括号,这是因为交替运算符的优先级所必需的。$?是一个特殊变量,用于退出最近调用的命令的代码。


1
如果我command -that --fails || exit $?没有括号就可以使用,那又是什么echo $[4/0]导致我们需要它们呢?
Anentropic

2
@Anentropic @skalee括号与优先级无关,但与异常处理无关。除以零会导致立即退出具有代码1的shell。如果没有括号(即简单的echo $[4/0] || exit $?),bash将永远不会执行echo,更不用说遵守了||
bobbogo

1

我有同样的问题,但不能问,因为它将是重复的。

当脚本有点复杂时,使用exit接受的答案不起作用。如果使用后台进程检查条件,则退出仅退出该进程,因为它在子外壳程序中运行。要杀死脚本,必须明确杀死它(至少这是我所知道的唯一方法)。

这是一个有关如何执行此操作的小脚本:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

这是一个更好的答案,但仍然不完整-我真的不知道该如何摆脱吊杆零件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.