如何在Bash中设置$?或返回代码?

62

我想设置一个返回值使其进入while循环:

#!/bin/bash
while [ $? -eq 1 ]
do
#do something until it returns 0    
done

为了使其正常工作,我需要在开头设置 $? = 1,但这样并没有起作用。

12个回答

161

你可以通过在子shell中执行带参数的exit来设置任意退出代码。

$ (exit 42); echo "$?"
42

那么你可以这样做:

(exit 1)    # or some other value > 0 or use false as others have suggested
while (($?))
do
    # do something until it returns 0    
done

或者你可以模拟一个do while循环

while 
    # do some stuff
    # do some more stuff
    # do something until it returns 0    
do
    continue  # just let the body of the while be a no-op
done

这两种方式都可以确保循环至少运行一次,我认为这正是你的目标。

为了完整起见,exitreturn都接受一个可选参数,这个参数是一个整数(正、负或零),它将返回值设为该整数除以256后的余数。使用 exit 退出当前 shell(或脚本或子 shell*),而使用 return 退出函数。

例如:

$ (exit -2); echo "$?"
254
$ foo () { return 2000; }; foo; echo $?
208

* 即使是由管道创建的子Shell(除非同时禁用作业控制并启用lastpipe),这也是真实的:

$ echo foo | while read -r s; do echo "$s"; exit 333; done; echo "$?"
77

请注意最好使用break命令来退出循环,但它的参数用于指定要退出的循环层数,而不是返回代码。

使用set +mset +o monitorshopt -u -o monitor可以禁用作业控制。要启用lastpipe,请执行shopt -s laspipe。如果两者都执行了,则上面示例中的exit将导致while循环和包含它的 shell 同时退出,并且最终的echo语句将不会被执行。


应该是 $(exit 42); echo "$?" - Elliot Chance
3
@ElliotChance: 我回答中的美元符号表示提示并保持原样。您提出的方法也可以实现,但并非必要。 - Dennis Williamson
请注意,您可以使用范围为0-255的数字,但实际上会对256取模。这意味着您可以执行“exit 300”,并且$?将是“44”。此外,“exit -1”将返回“255”(我经常看到的奇怪习惯)。只是需要注意一下。最好只返回所需的值,因此请坚持使用0-255,以免让自己或他人感到惊讶。 - boweeb
@boweeb "-1 mod X == X-1" 是非常正常的,它基本上是将“0,1, ...,X-1”序列延伸到负数。有关变体和原理,请参见https://en.wikipedia.org/wiki/Modulo_operation。 - toolforger
@toolforger -- 是的...这就是我之前说的。我对数学并不困惑。我困惑的是为什么开发人员要使用一种复杂的方式来达到255。我最好的猜测是,约定意味着255返回代码并没有实际意义(将其写为“-1”可能更容易写入?)。RC 1通常用于通用/未指定错误,但我从未见过任何东西实际上评估$?为255(具体而言)。 - boweeb

28

false 始终返回退出码1。

#!/bin/bash
false
while [ $? -eq 1 ]
do
#do something until it returns 0    
done

15
#!/bin/bash

RC=1

while [ $RC -eq 1 ]
do

  #do something until it returns 0    

  RC=$?
done

6

有些答案依赖于重新编写代码。在某些情况下,这可能是您无法控制的外来代码。

尽管对于这个具体的问题,将$?设置为1就足够了,但如果您需要将$?设置为任何值-唯一有用的答案是Dennis Williamson的答案。

一个更有效的方法是,不产生新的子进程(但也更加冗长),如下所示:

function false() { echo "$$"; return ${1:-1}; }
false 42

注意:echo部分只是为了验证它在当前进程中运行。

1
虽然我会避免将此函数称为“false”(这可能会让读者感到困惑),但使用函数比子shell快得多。一个粗略的微基准测试表明,使用子shell会产生超过100倍的开销。 - dimo414
为什么会让人感到困惑?因为它与传统的false命令相同,只是改进了,使您可以设置首选错误值。 - chukko
因为 false 2 的行为取决于您的函数是否在环境中,所以会有不同的结果。 - dimo414
所有标准的 false 调用者仍将正常工作(因为 false 只需要返回非零值)。唯一真正令人困惑的部分是将其称为 false 0(这可能会引起混淆,最好抛出错误以避免混淆)。因此它不会破坏任何东西 - 它只会提供额外的价值。如果有人感到困惑,他们可以轻松地使用自己的名称(并在函数未定义的情况下破坏他们的脚本)。我更喜欢重载和向后兼容性而不是新名称和破坏兼容性,但你的情况可能有所不同。 - chukko
但它不是向后兼容的 - 意图是返回一个特定的返回代码,而不是false的语义。如果您在不包括此函数的环境中使用false 42,则会得到微妙的意外结果(可能是1返回代码)。如果您使用不同的名称,则如果您的环境未按预期配置,您将收到清晰的错误消息。 - dimo414
当然,向后兼容的重点是原始命令的用例 - 即返回非零错误代码,它确实做到了。这个参数是那个的扩展。你不能期望恰好为1 - 一些Unix版本不返回1。因此,唯一意外的情况是false 0 - 在这种情况下,我同意它应该抛出一个错误。这更多是一个哲学话题,关于你是否喜欢重载。你的经验可能有所不同。如果你不喜欢,就改名字;如果你喜欢,就不要改。选择自己的毒药。没有一个是完美的解决方案。我们不需要在这个问题上达成一致。任何阅读这篇文章的人都有足够的背景信息来自行决定。 - chukko

5

我认为您可以通过运行一个保证失败的命令,在进入 while 循环之前隐式地执行此操作。

当然,典型的命令是 false


5

没有比一个简单的函数更轻巧的东西了:

function set_return() { return ${1:-0}; }

所有其他解决方案,例如(...)[...]false可能包含外部进程调用。


2

虽然这个问题比较老,但是现在有一个更好的答案:

#!/bin/bash
until
    #do something until it returns success
do
    :;
done

如果您正在循环等待某些操作成功,那么只需在“直到”部分执行该操作即可。您可以将完全相同的代码放在“直到”部分中,而不是在“执行/完成”部分中编写代码,然后将其结果传输回while或until。


2

$? 可以包含 0 到 255 的字节值。返回超出此范围的数字将被重新映射到此范围,就像应用了按位与 255。

退出值 - 可用于设置值,但很严格,因为它将终止进程/脚本。

返回值 - 在函数中使用略有传统意义。

[[ ... ]] - 适用于求解布尔表达式。

以下是一个exit的示例:

# Create a subshell, but, exit it with an error code:
$( exit 34 ); echo $? # outputs: 34

Here are examples of return:

# Define a `$?` setter and test it:
set_return() { return $1; }
set_return 0; echo $? # outputs: 0
set_return 123; echo $? # outputs: 123
set_return 1000; echo $? # outputs: 232
set_return -1; echo $? # outputs: 255

这里是一些[ ... ]的示例:
# Define and use a boolean test:
lessthan() { [[ $1 < $2 ]]; }
lessthan 3 8 && echo yes # outputs: yes
lessthan 8 3 && echo yes # outputs: nothing

注意,使用$?作为条件语句时,零(0)表示成功,非零表示失败。


1

这个东西是你在找的吗?

#!/bin/bash
TEMPVAR=1
while [ $TEMPVAR -eq 1 ]
do
  #do something until it returns 0
  #construct the logic which will cause TEMPVAR to be set 0 then check for it in the 
  #if statement 

  if [ yourcodehere ]; then
     $TEMPVAR=0
  fi
done

0

这是我正在使用的

allow_return_code() {
  local LAST_RETURN_CODE=$?
  if [[ $LAST_RETURN_CODE -eq $1 ]]; then
    return 0
  else
    return $LAST_RETURN_CODE
  fi
}

# it converts 2 to 0, 
my-command-returns-2 || allow_return_code 2
echo $?
# 0

# and it preserves the return code other than 2
my-command-returns-8 || allow_return_code 2
echo $?
# 8

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