从Bash函数返回布尔值

307

我想编写一个Bash函数来检查文件是否具有某些属性,并返回true或false。然后我可以在我的脚本中使用它在"if"语句中。但是我应该返回什么?

function myfun(){ ... return 0; else return 1; fi;}

然后我像这样使用它:

if myfun filename.txt; then ...

当然这行不通。如何实现?


4
去掉“function”关键字,只需要使用“myfun() {...}”就可以了。 - glenn jackman
2
if 语句关注的是 myfun 的零退出状态:如果 myfun0 退出,则执行 then ...;否则,执行 else ... - Eelvex
8
@nhed说:function关键字是Bash独有的语法,会在其他一些shell中导致语法错误。基本上,它要么是不必要的,要么是被禁止的,所以为什么要使用它呢?甚至作为grep的搜索目标都没有用,因为可能根本不存在(可以搜索()代替)。 - Gordon Davisson
6
@GordonDavisson:什么?还有其他的shell?;-) - nhed
请不要使用0和1。请参见https://dev59.com/km035IYBdhLWcg3wbPU5#43840545。 - Bruno Bronosky
11个回答

423

使用0表示真,使用1表示假。

示例:

#!/bin/bash

isdirectory() {
  if [ -d "$1" ]
  then
    # 0 = true
    return 0 
  else
    # 1 = false
    return 1
  fi
}


if isdirectory $1; then echo "is directory"; else echo "nopes"; fi

编辑

根据@amichair的评论,这些也是可能的选项。

isdirectory() {
  if [ -d "$1" ]
  then
    true
  else
    false
  fi
}


isdirectory() {
  [ -d "$1" ]
}

4
不需要这样做——看示例即可。 - Erik
68
为了更好地阅读,您可以使用“ true”命令(它什么也不做并成功完成,即返回0)和“ false”命令(它什么也不做并失败完成,即返回非零值)。此外,如果函数结束时没有显式的return语句,则返回最后执行命令的退出代码,因此在上面的示例中,函数体可以简化为仅使用 [ -d "$1" ] - amichair
26
当你将其视为“错误代码”时,它就有意义了:错误代码0 = 一切顺利 = 0个错误; 错误代码1 = 此调用主要任务失败; 其他情况: 失败!请在手册中查找。 - flying sheep
8
当你考虑编程时,通常情况下只有一种成功的方式,但失败的方式却多种多样,尽管也许不是无穷无尽的,但仍然是数量庞大的,胜利/错误并非是布尔值。我认为这句话“用0表示真,用1表示假”应该改为“用0表示成功,用非零值表示失败”。 - Davos
12
请不要使用0和1。请参阅https://dev59.com/km035IYBdhLWcg3wbPU5#43840545。 - Bruno Bronosky
显示剩余4条评论

298

为什么你应该关注我说的话,尽管有一个更高的赞同回答存在

这并不是说0 = true1 = false。实际上是:零意味着没有失败(成功),而非零意味着失败(类型N的失败)

虽然被选中的回答在技术上是"true",但不要在你的代码中使用return 1**表示false。这会带来一些不幸的副作用。

  1. 经验丰富的开发者会认为你是个业余者(因为下面的原因)。
  2. 经验丰富的开发者不会这样做(因为下面的所有原因)。
  3. 这容易出错。
    • 即使是经验丰富的开发者也会错误地将0和1分别视为false和true(因为上面的原因)。
  4. 它需要(或会鼓励)多余和荒谬的注释。
  5. 它实际上比隐式返回状态更不实用。

学习一些bash

bash手册中提到(重点在我)

return [n]

使一个shell函数停止执行,并将值n返回给调用者。 如果未提供n,则返回值是函数中最后执行的命令的退出状态

因此,我们永远不必使用0和1来表示真和假。它们这样做的事实基本上是琐碎的知识,仅用于调试代码、面试问题和让新手大吃一惊。

bash手册还提到

否则,函数的返回状态是最后执行的命令的退出状态

bash手册还提到

($?)扩展为最近执行的前台管道的退出状态
哇,等等。管道?让我们再看一遍 bash 手册
管道是由一个或多个命令组成的序列,用控制操作符‘|’或‘|&’分隔。
是的。他们说一个命令就是一个管道。因此,这三个引用都在说同一件事。 $? 告诉你上次发生了什么。
它会冒泡上来。
我的答案
尽管 @Kambus 示范了这样一个简单的函数,根本不需要return。但我认为这对于大多数读者来说过于简单了。
为什么需要return
如果一个函数要返回它最后一个命令的退出状态,为什么还要使用return呢?因为它会导致函数停止执行。

在多种条件下停止执行

01  function i_should(){
02      uname="$(uname -a)"
03
04      [[ "$uname" =~ Darwin ]] && return
05
06      if [[ "$uname" =~ Ubuntu ]]; then
07          release="$(lsb_release -a)"
08          [[ "$release" =~ LTS ]]
09          return
10      fi
11
12      false
13  }
14
15  function do_it(){
16      echo "Hello, old friend."
17  }
18
19  if i_should; then
20    do_it
21  fi

这里的情况是...
第04行是一个明确的返回true,因为&&的右侧只有在左侧为true时才会执行。
第09行返回true或false,与第08行的状态匹配。
第13行返回false,因为第12行的原因。
(是的,这可以被简化,但整个示例是人为的。)
另一种常见的模式...
# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
    echo "some_command failed"
fi

# Do this...
some_command
status=$?
if ! (exit $status); then
    echo "some_command failed"
fi

注意如何设置一个status变量来解释$?的含义。(当然,你知道$?是什么意思,但是比你不那么了解的人可能会在某一天需要通过谷歌来查找。除非你的代码是在进行高频交易,表达一些爱意,设置这个变量。)但真正的要点是,"如果不是退出状态"或者相反地"如果是退出状态"可以大声朗读并解释它们的含义。然而,最后一个可能有点过于雄心勃勃,因为看到exit这个词可能会让你认为它是退出脚本,而实际上它是退出(...) 命令组子shell。
**如果你坚持使用return 1来表示假,我建议你至少使用return 255代替。这会让你未来的自己或其他必须维护你代码的开发人员产生疑问:“为什么是255?”然后他们至少会注意到并更有可能避免“1=true,0=false”的错误。**

14
因为0/1作为返回值是晦涩难懂且容易混淆的,避免使用标准的0/1也是可笑的。整个shell语言本身就是晦涩难懂和容易混淆的。Bash本身在其自己的“true”和“false”命令中使用0/1 = true/false约定。也就是说,“true”关键字实际上会计算为一个0状态码。此外,if-then语句本质上操作的是布尔值而不是成功代码。如果在布尔运算期间发生错误,则不应返回true或false,而应该简单地停止执行。否则,您将得到假阳性(双关语)。 - Beejor
3
“if ! $(exit $status); then”需要更好的解释。它不够直观,我必须思考程序是否会在打印消息之前退出。您的其他回答很好,但这部分翻译让人失望。“if ! $(exit $status); then”的意思是,如果运行命令后退出状态码不为0,则执行下一步操作。其中,“$(exit $status)”表示运行一个命令来获取变量$status的值作为退出状态码。因此,整个语句的含义是,如果上一个命令未成功执行,则执行接下来的操作。 - Craig Hicks
2
@BrunoBronosky 我读了你的回答,我认为我理解了内容管道(stdin->stdout)和返回值中的错误代码之间的区别。然而,我不明白为什么应该避免使用 return 1。假设我有一个 validate 函数,如果验证失败,我认为 return 1 是合理的。毕竟,这是停止脚本执行的原因,如果没有得到适当处理(例如,当使用 set -e 时)。 - JepZ
2
使用 return 1 没有任何问题...特别是如果您不想引用先前的命令。这样做并不意味着您自动成为“业余爱好者”... - Akito
2
“exit”还是“exist”?这很模棱两可,我觉得奇怪居然三年过去了没有人评论。 :-| - zaTricky
显示剩余17条评论

40
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes

1
否定呢? - einpoklum
这个怎么样,@einpoklum? - Mark Reed
@MarkReed:我的意思是,在你的例子中添加“else”情况。 - einpoklum
myfun "路径" || 回显 no - Hrobky

14

使用 -d 选项仅检查目录时要小心!
如果变量 $1 为空,则检查仍将成功。为确保准确性,请同时检查该变量不为空。

#! /bin/bash

is_directory(){

    if [[ -d $1 ]] && [[ -n $1 ]] ; then
        return 0
    else
        return 1
    fi

}


#Test
if is_directory $1 ; then
    echo "Directory exist"
else
    echo "Directory does not exist!" 
fi

2
我不确定这如何回答所提出的问题。虽然了解一个空的 $1 可以在为空时返回 true,但它并没有提供任何有关如何从 bash 函数返回 true 或 false 的见解。我建议创建一个新问题“当您对空shell变量进行测试时会发生什么?” 然后将此作为答案发布。 - DRaehal
4
请注意,如果您在$1周围添加适当的引用("$1"),那么您就不需要检查空变量了。[[ -d "$1" ]]会失败,因为""不是一个目录。 - morgents
[[ ... ]] 中,引号没有任何区别。而在 [...] 中,它当然会有所不同,因为如果变量扩展为空并被省略,那么就只剩下了 [ -d ],它只是测试 -d 是否为非空字符串;这当然是正确的。 - Martin Kealey
从更广泛的视角来看,一些Unix将空文件路径视为等同于“.”,而另一些则不会。 - Martin Kealey

13

在你的return之前立即使用truefalse命令,然后return没有参数。 return将自动使用您最后一个命令的值。

return提供参数是不一致的、类型特定的,并且容易出错,如果您不使用1或0的话。正如先前的评论所述,在此处使用1或0并不是正确的处理此函数的方法。

#!/bin/bash

function test_for_cat {
    if [ "$1" = "cat" ];
    then
        true
        return
    else
        false
        return
    fi
}

for i in cat hat;
do
    echo "${i}:"
    if test_for_cat "${i}";
    then
        echo "- True"
    else
        echo "- False"
    fi
done

输出:

$ bash bash_return.sh

cat:
- True
hat:
- False

也许可以改进这段代码,使其在使用 if test_for_cat 'snow leopard' ; then echo "It's a feline" ; else echo "It's something else" ; fi 调用时不会产生奇怪的错误。 - Martin Kealey
@MartinKealey,我修复了引用引起的错误。但这是另一个例子,说明为什么除非你绝对必须符合POSIX sh标准,否则应始终使用[[而不是[。请参见:https://mywiki.wooledge.org/BashFAQ/031 - Bruno Bronosky

10

为了代码可读性,我认为返回true/false应该:

  • 在一行上
  • 是一个命令
  • 易于记忆
  • 提到关键字return后跟另一个关键字(truefalse)

我的解决方案是如下所示:return $(true)return $(false)

is_directory()
{
    if [ -d "${1}" ]; then
        return $(true)
    else
        return $(false)
    fi
}

1
然而,这样做的缺点是你会生成一个完整的子进程(在许多解释器中),只是为了返回一个原始值。与任何其他“真正”的语言相比,这种效率低下是惊人的!我知道在shell脚本中很难避免这种情况,但这是可能的。事实上,我开发了一个库,可以避免这种疯狂的情况,一般情况下可以显著加速一些“重型”脚本。虽然我的“反向”答案不太易读,但如果我关心处理细节,我仍然会使用它而不是这个漂亮的答案。 - BuvinJ
1
如果你正在构建一个库并想要支持“重型”脚本,那么在某个地方声明TRUE=0; FALSE=1,然后使用return $TRUEreturn $FALSE会是更好的答案。在大多数情况下,代码可读性比轻微的性能提升更重要,因此使用bash脚本。如果需要性能和优化,则bash不是答案。 - RandomName
请查看Bruno Bronosky的答案,涉及返回0或1的问题... - BuvinJ
我不喜欢使用 $(true)$(false),因为它违反了另一个重要的风格规则:_始终引用扩展_。总有一天,有人会查看 ShellCheck 的结果,并通过将 return $(true) 更改为 return "$(true)" 来“修复”此代码。这不是语法错误,但是由于空字符串不是数字,因此将导致 return 报错 需要数字参数 - Martin Kealey
我自己采用了 return $TRUE 的方法,因为它不会创建子shell。因此,由于我非常重视代码的可读性,我发誓永远不会使用 return "$TRUE" 只是为了满足一些在线工具的要求。 - RandomName
显示剩余9条评论

6

我遇到了一个问题(可能还没有明确提到?),让我感到困惑。那就是,不是如何“返回”布尔值,而是如何正确地评估它!

我试图这样写if [ myfunc ]; then ... ,但那是错误的。你不能使用括号!if myfunc; then ... 才是正确的方法。

正如 @Bruno 和其他人所重申的,truefalse都是命令,而不是值!这对于理解shell脚本中的布尔值非常重要。

在这篇文章中,我解释并演示了如何使用布尔变量:https://dev59.com/j3A85IYBdhLWcg3wEPVL#55174008。我强烈建议查看一下,因为它与这个主题非常相关。

在这里,我将提供一些从函数中返回和“评估”布尔值的示例:

这段代码:

test(){ false; }                                               
if test; then echo "it is"; fi                                 

不会产生回显输出。(例如false 返回 false)

test(){ true; }                                                
if test; then echo "it is"; fi                                 

产生:

it is                                                        

(i.e. true 返回 true)

另外,

test(){ x=1; }                                                
if test; then echo "it is"; fi                                 

生成:

it is                                                                           

因为0(即true)被隐式返回,导致了这个问题。现在,这就是让我困扰的东西...
test(){ true; }                                                
if [ test ]; then echo "it is"; fi                             

生成:

it is                                                                           

并且

test(){ false; }                                                
if [ test ]; then echo "it is"; fi                             

ALSO产生:

it is                                                                           

在这里使用方括号产生了一个错误的Positive!(我推断"外部"命令的结果为0。)

我的主要观点是:不要像对典型的相等检查一样,使用括号来评估布尔函数(或变量),例如if [ x -eq 1 ]; then... !


1
如果test函数包含了一个printf,你就会注意到从[ test ]中缺少输出。规则非常简单:[不是if结构固有语法的一部分;相反,它接受一系列命令,而[是一个(奇怪命名的)命令。顺便说一下,将函数命名为test是个坏主意,因为test是一个内置函数(等同于[)。 - Martin Kealey

5

在跟进 @Bruno Bronosky 和 @mrteatime 之后,我提供了一种建议,那就是你只需将布尔返回值“反过来”写即可。我的意思是:

foo()
{
    if [ "$1" == "bar" ]; then
        true; return
    else
        false; return
    fi;
}

那样就可以消除每个返回语句中繁琐的两行要求。

一眼看上去漂亮、整洁且易于理解 - david valentino

4

我发现测试函数输出的最短形式就是:

do_something() {
    [[ -e $1 ]] # e.g. test file exists
}

do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }

1
这里有一些与测试用例相关的良好示例。
对于基本功能,可以在方法的最后一行调用true/false,或使用true; return提前退出。请看以下内容:
function is_true() { true; }

if is_true; then echo 'true is true'; fi
if ! is_true; then exit; else echo '! true is ! true'; fi

function is_false() { false; }

if ! is_false; then echo 'false is false'; fi
if is_false; then exit; else echo '! false is ! false'; fi

如果不能立即返回,请将返回值存储在变量中。在设置变量时使用(true; echo $?)。这也适用于嵌套函数(请参见下一节)。
function from_var() {
        local input=$1
        local my_var
        if ((input == 1)); then
                my_var=$(true; echo $?)
        else
                my_var=$(false; echo $?)
        fi
        echo 'ignore this line'
        (exit $my_var)
}

if from_var 1; then echo "return true is true"; else exit; fi
if from_var 0; then exit; else echo "return false is false"; fi

如果您需要存储返回布尔值的函数调用的结果,请使用相同的技术,但将调用的输出导向到/dev/null,否则结果可能也会包含来自echo或其他命令的字符串。请注意,在if语句中的(exit $rval)可让您正确解释返回值。其他方法如if (($rval))if [ $rval ]将无法按预期工作。
# Return a truthy result
rval=$(from_var 1 >/dev/null; echo $?)
if (exit $rval); then echo "return true as variable is true"; else exit; fi

# Return a falsy result
rval=$(from_var 0 >/dev/null; echo $?)
if (exit $rval); then exit; else echo "return false as variable is false"; fi

这段代码的完整输出结果如下:
true is true
! true is ! true
false is false
! false is ! false
ignore this line
return true is true
ignore this line
return false is false
return true as variable is true
return false as variable is false

如果您不想使用> /dev/null在函数内部抑制输出,则可以重写为先调用该函数。

from_var 0; rval="$?"

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