在不关闭shell的情况下返回退出代码

38

我想从一个在另一个脚本中调用,但也可以直接调用的BASH脚本中返回一个退出码。 大致是这样的:

#!/bin/bash
dq2-get $1
if [ $? -ne 0 ]; then
  echo "ERROR: ..."
  # EXIT HERE
fi
# extract, do some stuff
# ...

现在在EXIT HERE这一行中,脚本应该退出并返回退出代码1。问题在于:

  • 我不能使用return,因为当我忘记对脚本进行源调用而不是调用它时,return不会退出,剩下的脚本将被执行并搞乱事情。
  • 我不能使用exit,因为这会关闭shell。
  • 我不能使用好用的技巧kill -SIGINT $$,因为这不允许返回退出代码。

我有没有忽略任何可行的替代方案?


5
不要忘记注明脚本来源是我的建议。 - user2100815
1
软件应该总是以一种不会导致问题的方式编写,即使被误用。 - fuenfundachtzig
1
我不同意尼尔的建议。相反,永远不要引用脚本,而是始终调用它。 - William Pursell
检查 $SHLVL 的值并有条件地退出或不退出。 - MarkHu
6个回答

24

问题的答案(与其他回答涉及的主体不同)是:

在不关闭 shell 的情况下返回一个退出代码

(exit 33)
如果您需要使用-e并仍然避免使用非零退出代码退出shell,则执行以下操作:
(exit 33) && true

true命令永远不会被执行,但用于构建一个不受-e shell标志退出的复合命令。

这样可以设置退出代码而不退出shell(也不退出被源代码调用的脚本)。

对于更复杂的问题,即在执行或源代码化时退出(具有特定的退出代码):

#!/bin/bash
[ "$BASH_SOURCE" == "$0" ] &&
    echo "This file is meant to be sourced, not executed" && 
        exit 30

return 88

如果执行,将设置一个退出代码30(附带错误消息)。
如果被源化,则设置一个退出代码88。 无论执行还是被源化,都不会影响调用shell。


(退出 33)太棒了!! - Adrian Maire
这非常有用。但我遇到的一个问题是,我喜欢使用“set -e”来停止脚本的执行。不幸的是,返回值88会像以前一样导致shell被终止。有没有什么指针可以处理这种情况? - openCivilisation
@openCivilisation 保持 shell 工作的常规解决方案,即使设置了 -e 选项,也是将命令行作为复合命令,最简单的复合命令是 (exit 33) && true。实际上从未执行 true 命令。 - user8017719

12

使用此代码代替exit或return:

[ $PS1 ] && return || exit;

无论是否被引用都能工作。


1
如果使用单括号 [ ] 而不是双括号 [[ ]],它也可以在 sh 中工作。 - FelikZ
1
它按照原样并没有对我起作用(作为脚本运行不起作用),但是以下内容可以:[ -v PS1 ] && return || exit。一个简单的测试方法是:[ -v PS1 ] && (echo returning; return) || (echo exiting; exit) - VasiliNovikov
此外,如果我理解正确的话,当从另一个脚本中进行源代码时,这个解决方案是不起作用的。但是,被接受的答案在那种(复杂)情况下是有效的。 - VasiliNovikov

8

你可以使用 x"${BASH_SOURCE[0]}" == x"$0" 来测试脚本是被引用还是被调用(如果被引用则为false,如果被调用则为true),并相应地使用 returnexit


我试过了,但我感到无聊,因为我无法将其放入“函数”中(因为再次我会错过一个退出的方式) - 我必须在脚本中每个可能出现错误的地方放置一个“if”结构。那么有更好的办法吗? - fuenfundachtzig
你可以将if语句作为信号陷阱的一部分,并用kill -SIGWHATEVER $$替换每个EXIT HERE。 - Shea Levy
x 是 100% 不必要的;它们对 [ "${BASH_SOURCE[0]}" = "$0" ] 的行为没有任何影响。在遵循 POSIX.2 标准的 shell 中,只有在使用带有三个以上参数的 test 调用时(自从其最初的 90 年代早期出版以来),才会出现 x$foo 惯用语的值。如果您需要与古老的 shell 兼容,则 == 在本质上是错误的;唯一向后兼容(甚至基线符合 POSIX 标准)的字符串比较运算符是 = - Charles Duffy
除此之外,这不是一个可靠的测试;$0 可以被使用 exec -a name 的调用者修改。 - Charles Duffy

2
另一个选择是使用函数并将返回值放在其中,然后只需对脚本进行源化(source processStatus.sh)或调用脚本(./processStatus.sh)。例如考虑processStatus.sh脚本,它需要将一个值返回给stopProcess.sh脚本,但也需要单独从命令行调用而不使用source(仅包含相关部分) 例子:
check_process ()
{
  if [ $1 -eq "50" ]
  then
    return 1       
  else
    return 0
  fi       
}

并且

source processStatus.sh $1
RET_VALUE=$?
if [ $RET_VALUE -ne "0" ]
then
  exit 0
fi

50不需要引号,因为无论如何它都解析为相同的两个字符字符串。$1 需要引号,因为它可以根据其内容解析为零个字符串、一个字符串或多个字符串。考虑 check_process "1 -eq 1 -o 39";当您没有引号时,就会运行 if [ 1 -eq 1 -o 39 -eq 50 ],这是真实的,而 [ "1 -eq 1 -o 39" -eq 50 ] 将正确地产生错误。 - Charles Duffy

0

感谢您的提问,我的情况是为了进行一些设置而source一个文件,但如果不满足某些条件,则结束脚本并跳过设置操作。

我曾经遇到过尝试使用exit()实际上导致关闭我的终端的问题,并发现自己在这里:D

在审查特定解决方案的选项后,我选择了类似以下的方法,我还认为Deepaks answer值得审查,如果这种方法适用于您的情况。

if [ -z "$REQUIRED_VAR" ]; then
  echo "please check/set \$REQUIRED_VAR ..."
  echo "skipping logic"
else
  echo "starting logic"
  doStuff()
  echo "completed logic"
fi

0

如果你在脚本开头使用了set -e,那么你可以使用return

如果你只是想检查函数是否没有返回错误,我建议你像这样重写你的代码:

#!/bin/bash

set -e # exit program if encountered errors

dq2-get ()
{
  # define the function here
  # ...
  if [ $1 -eq 0 ]
  then
    return 0
  else
    return 255
  # Note that nothing will execute from this point on,
  # because `return` terminates the function.
}

# ...
# lots of code ...
# ...

# Now, the test:
# This won't exit the program.
if $(dq2-get $1); then
  echo "No errors, everything's fine"
else
  echo "ERROR: ..."
fi
# These commands execute anyway, no matter what
# `dq2-get $1` returns (i.e. {0..255}).
# extract, do some stuff
# ...

现在,上面的代码如果函数dq2-get $1返回错误,程序不会退出。但是,如果单独实现该函数,由于set -e,程序将退出。下面的代码描述了这种情况:

# The function below will stop the program and exit
# if it returns anything other than `0`
# since `set -e` means stop if encountered any errors.
$(dq2-get $1)
# These commands execute ONLY if `dq2-get $1` returns `0`
# extract, do some stuff
# ...

1
if $(some-function some-arg); then is a really bad idea, because if some-function writes anything to stdout that stdout will be executed as a command and the exit status of the command will be used for the if instead of the exit status of some-function. Instead, use if some-function some-arg; then - Charles Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接