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

Posted

技术标签:

【中文标题】在 Bash 脚本中,如果发生某种情况,如何退出整个脚本?【英文标题】:In a Bash script, how can I exit the entire script if a certain condition occurs? 【发布时间】:2010-11-25 14:00:05 【问题描述】:

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

有没有一种方法可以做到这一点,而无需将整个脚本包装在 while 循环中并使用中断? dun dun dun goto 之类的东西?

【问题讨论】:

【参考方案1】:

试试这个说法:

exit 1

用适当的错误代码替换1。另见Exit Codes With Special Meanings。

【讨论】:

@CMCDragonkai,通常任何非零代码都可以。如果你不需要任何特别的东西,你可以一直使用1。如果该脚本打算由另一个脚本运行,您可能需要定义自己的一组具有特定含义的状态代码。例如,1 == 测试失败,2 == 编译失败。如果脚本是其他内容的一部分,您可能需要调整代码以匹配那里使用的做法。例如,当 automake 运行部分测试套件时,代码77 用于标记已跳过的测试。 不,这也会关闭窗口,而不仅仅是退出脚本 @ToniLeigh bash 没有“窗口”的概念,您可能对您的特定设置感到困惑 - 例如终端模拟器 - 可以。 @ToniLeigh 如果它正在关闭“窗口”,您可能会将 exit # 命令放在函数中,而不是脚本中。 (在这种情况下,请改用return #。) @ToniLeigh exit 命令只退出脚本运行所在的 bash 进程。如果您使用 ./script.shbash script.sh 运行脚本,您的“窗口”或 shell 将保持打开状态,因为为脚本创建了一个新的 bash 进程。另一方面,如果您使用 . script.shsource script.sh 运行脚本,它会在您当前的 bash 实例中执行并退出它。【参考方案2】:

使用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

任何失败的命令都会导致整个脚本失败并返回退出状态,您可以使用 $? 检查。如果你的脚本很长或者你正在构建很多东西,如果你到处添加返回状态检查,它会变得非常丑陋。

【讨论】:

使用set -e,您仍然可以一些命令在不停止脚本的情况下退出错误:command 2>&1 || echo $? 如果管道或命令结构返回非零值,set -e 将中止脚本。例如,foo || bar 只有在foobar 都返回非零值时才会失败。通常,如果您在开始时添加 set -e,则编写良好的 bash 脚本将起作用,并且添加作为自动完整性检查:如果出现任何问题,请中止脚本。 如果您将命令一起传递,如果其中任何一个失败,您也可以通过设置 set -o pipefail 选项失败。 实际上没有set -e的惯用代码就是make || exit $? 还有set -u。看看unofficial bash strict modeset -euo pipefail【参考方案3】:

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

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

这些功能是*NIX OS 和shell 风格强大的。将它们放在脚本的开头(bash 或其他),try() 您的语句和代码。

说明

(基于flying sheep 评论)。

yell:将脚本名称和所有参数打印到stderr$0 是脚本的路径; $* 都是参数。 >&2 表示 > 将标准输出重定向到 & 管道 2管道1 本身就是stdoutdieyell 一样,但是以非0 退出状态 退出,这意味着“失败”。 try 使用||(布尔值OR),仅在左侧失败时评估右侧。 $@ 又是所有参数,但 different。

【讨论】:

我对 unix 脚本还很陌生。您能解释一下上述功能是如何执行的吗?我看到了一些我不熟悉的新语法。谢谢。 yell$0 是脚本的路径。 $* 都是参数。 >&2 表示“> 将标准输出重定向到 & 管道 2”。管道 1 本身就是标准输出。所以 yell 用脚本名称作为所有参数的前缀并打印到 stderr。 dieyell 一样,但是以非 0 的退出状态退出,这意味着“失败”。 try 使用布尔值或||,仅在左侧没有失败时才评估右侧。 $@ 又是所有参数,但 different。希望能解释一切 我可以看到我将如何使用yelldie。但是,try 并没有那么多。你能提供一个你使用它的例子吗? 嗯,但是如何在脚本中使用它们呢?我不明白您所说的“try() your statement and code on”是什么意思。 @TheJavaGuy-IvanMilosavljević 将这三行添加到脚本顶部后,您将开始在代码中使用try 函数。例如:try cp ./fake.txt ./copy.txt【参考方案4】:

如果您将使用source 调用脚本,则可以使用return <x>,其中<x> 将是脚本退出状态(使用非零值表示错误或错误)。但是如果你调用一个可执行脚本(即直接使用它的文件名),return 语句将导致一个抱怨(错误消息“return: can only `return' from a function or sourced script”)。

如果改用exit <x>,当使用source 调用脚本时,将导致退出启动脚本的shell,但可执行脚本将按预期终止。

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

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

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

注意:&lt;x&gt; 应该只是一个数字。

【讨论】:

在函数内部有返回/退出的脚本中对我不起作用,即是否可以在不存在 shell 的情况下退出函数内部,但不让函数调用者关心正确的检查函数的返回码? @jan 调用者对返回值做什么(或不做什么)是完全正交的(即,独立于)您从函数返回的方式(... w/o 退出 shell,无论调用什么)。它主要取决于调用方代码,这不是本问答的一部分。您甚至可以根据调用者的需要定制函数的返回值,但是这个答案限制了这个返回值可以是什么...... 这应该是公认的答案。 Q 没有指定它是使用“source script.sh”还是“./script.sh”。人们经常使用两者。如果您使用源,当前接受的答案将不起作用。这个答案适用于这两种情况。【参考方案5】:

我经常包含一个名为 run() 的函数来处理错误。我要进行的每个调用都传递给此函数,因此当失败时整个脚本将退出。与 set -e 解决方案相比,它的优点是当一行失败时脚本不会静默退出,并且可以告诉您问题所在。在下面的例子中,第 3 行没有被执行,因为脚本在调用 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"

【讨论】:

伙计,出于某种原因,我真的很喜欢这个答案。我承认它有点复杂,但它似乎很有用。而且鉴于我不是 bash 专家,这让我相信我的逻辑是错误的,并且这种方法有问题,否则,我觉得其他人会给予它更多的赞美。那么,这个功能有什么问题呢?这里有什么我应该注意的吗? 我不记得我使用 eval 的原因了,该函数与 cmd_output=$($1) 一起工作正常 我刚刚将其作为复杂部署过程的一部分实现,并且效果非常好。谢谢你,这是一个评论和一个upvote。 真正了不起的工作!这是运行良好的最简单、最干净的解决方案。对我来说,我在 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。 如果您接受任意输入,eval 可能会很危险,否则这看起来很不错。【参考方案6】:

您可以利用short-circuit evaluation,而不是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]

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

【讨论】:

如果我使用command -that --fails || exit $?,它可以在没有括号的情况下工作,那么echo $[4/0] 是什么导致我们需要它们? @Anentropic @skalee 括号与优先级无关,而是异常处理。被零除将导致使用代码 1 立即退出 shell。没有括号(即简单的 echo $[4/0] || exit $?bash 将永远不会执行 echo,更不用说遵守||.【参考方案7】:

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

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

这是一个关于如何做的小脚本:

#!/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

这是一个更好的答案,但仍然不完整,我真的不知道如何摆脱 boom 部分。

【讨论】:

【参考方案8】:

您可以通过以下方式通过程序名称关闭您的程序:

软退出做

pkill -9 -x programname # Replace "programmname" by your programme

硬退出做

pkill -15 -x programname # Replace "programmname" by your programme

如果您想知道如何评估关闭程序的条件,您需要自定义您的问题。

【讨论】:

你把 9 和 15 弄混了。信号 9 是 SIGKILL,这是一个“更难”的退出,强制立即终止。信号 15 是 SIGTERM,它礼貌地要求程序终止并让它在需要时进行清理。【参考方案9】:

您可以通过以下方式通过程序的PID关闭您的程序:

软退出做

kill -9 $PID # Replace "programmname" by your programme

硬退出做

kill -15 $PID # Replace "programmname" by your programme

如果您想知道如何评估关闭程序的条件,您需要自定义您的问题。

【讨论】:

以上是关于在 Bash 脚本中,如果发生某种情况,如何退出整个脚本?的主要内容,如果未能解决你的问题,请参考以下文章

Bash 脚本:如何确保脚本在使用“或”(||) 条件调用的函数中出现任何错误时退出?

如果语句过早退出Bash脚本循环

如果出错,如何退出 bash 功能?

Bash:如何在使用微调器时获取命令的退出代码?

返回与 bash 脚本中返回的命令不同的退出代码

如何避免Docker容器启动脚本运行后自动退出