我正在学习决策结构,并且遇到了以下代码:
if [ -f ./myfile ]
then
cat ./myfile
else
cat /home/user/myfile
fi
[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile
两者的行为相同。使用另一种方式有什么优势?
我正在学习决策结构,并且遇到了以下代码:
if [ -f ./myfile ]
then
cat ./myfile
else
cat /home/user/myfile
fi
[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile
两者的行为相同。使用另一种方式有什么优势?
Answers:
不,结构if A; then B; else C; fi
和A && B || C
是不等价的。
使用时if A; then B; else C; fi
,A
始终对命令进行评估和执行(至少尝试执行该命令),然后对命令B
或命令C
进行评估和执行。
用A && B || C
,这是相同的各种命令A
和B
但对于不同C
:命令C
被评估并执行,如果任一 A
发生故障或 B
失败。
在您的示例中,假设您chmod u-r ./myfile
,尽管[ -f ./myfile ]
成功,您仍会cat /home/user/myfile
我的建议:使用A && B
或A || B
全部使用,这仍然易于阅读和理解,没有陷阱。但是,如果您的意思是如果...然后...其他...则使用if A; then B; else C; fi
。
大多数人觉得它更容易理解的if
... ... then
... ... else
... ... fi
形式。
对于a && b || c
,您必须确保b
返回true。这是导致细微错误的原因,也是避免这种样式的一个很好的理由。如果b不返回true,则它们不相同。
$ if true; then false ; else echo boom ; fi
$ true && false || echo boom
boom
对于非常短的没有else子句的测试和操作,缩短的长度很有吸引力,例如
die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }
[ "$#" -eq 2] || die "Needs 2 arguments, input and output"
if [ "$#" -ne 2 ] ; then
die "Needs 2 arguments, input and output"
fi
&&
和||
是short circuiting operators
,只要已知结果,就会跳过进一步不需要的测试。a && b || c
分组为(a && b) || c
。首先a
运行。如果将fails
其定义为不返回退出状态0,则该组(a && b)
是已知的fail
并且b
不需要运行。在||
不知道表达式的结果所以需要执行c
。如果a
成功(返回零),则&&
操作员尚不知道这样的结果,a && b
因此必须运行b
才能找出答案。如果b
成功则a && b
成功,并且||
知道总体结果就是成功,因此不需要运行c
。如果b
失败的话||
仍然不知道表达式的值,所以需要运行c
。
如果前一个命令执行成功,则运算符&&执行下一个命令(返回的退出代码($?)0 =逻辑为真)。
在形式中A && B || C
,对命令(或条件)A进行求值,如果A返回true(成功,退出代码0),则执行命令B。如果甲失败(从而将返回假 - 0以外的退出代码)和/或乙失败(返回假),则命令Ç将被执行。
也&&
算作为AND状况检查和运营商||
的作品像OR状况检查。
根据您想对脚本执行的操作,表单A && B || C
可以像示例一样用于条件检查,也可以用于链接命令,并确保如果先前的命令成功退出代码为0,则可以执行一系列命令。
这就是为什么常见的命令如:的原因
do_something && do_something_else_that_depended_on_something
。
示例:
apt-get update && apt-get upgrade
如果更新失败,则不执行升级(在现实世界中是有道理的...)。
mkdir test && echo "Something" > test/file
echo "Something"
仅当mkdir test
成功且操作返回退出代码0时,才会执行
该部分。
./configure --prefix=/usr && make && sudo make install
通常在编译作业中找到,以将必要的依赖命令链接在一起。
如果您尝试使用if - then - else来实现上述“链”,那么您将需要更多的命令和检查(以及更多的代码编写-更多的错误地方)来完成一个简单的任务。
另外,请记住,用&&和||链接的命令 由shell从左到右读取。您可能需要对命令和条件检查进行分组,以使下一步取决于某些先前命令的成功输出。例如查看此:
root@debian:$ true || true && false;echo $?
1
#read from left to right
#true OR true=true AND false = false = exit code 1=not success
root@debian:$ true || (true && false);echo $?
0
# true OR (true AND false)=true OR false = true = exit code 0 = success
或一个真实的例子:
root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok
root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0
#vars b and c are checked in a group which returns false,
#condition check of var a returns true, thus true OR false yields true = exit code 0
请记住,某些命令根据执行的进程返回不同的退出代码,或者根据其操作返回不同的代码(例如,命令GNU diff
,如果两个文件不同,则返回1,否则,则返回0)。此类命令必须在&&和||中小心对待。。
同样,为了解决所有难题,请注意使用;
operator来连接命令。使用格式时A;B;C
,无论命令A
和的退出代码是什么,所有命令都会依次执行B
。
关于此的许多困惑可能是由于bash文档调用了这些AND和OR列表。虽然在逻辑上类似于和,&&
并||
在方括号内找到,但它们的功能有所不同。
一些例子可以最好地说明这一点。
注意:单方括号和双方括号(
[ ... ]
和[[ ... ]]
)本身就是执行比较并返回退出代码的命令。他们实际上并不需要if
。
cmda && cmdb || cmdc
如果cmda
退出,cmdb
则执行。
如果cmda
退出,cmdb
则不执行,而是执行cmdc
。
cmda; cmdb && cmdc || cmdd
如何cmda
忽略出口。
如果cmdb
退出true,cmdc
则执行。
如果cmdb
退出为false,cmdc
则不执行,而是执行cmdd
。
cmda && cmdb; cmdc
如果cmda
退出,cmdb
则执行,然后执行cmdc
。
如果cmda
退出,cmdb
则不执行,而是执行。cmdc
??为什么要cmdc
执行?
因为对于解释器来说,分号(;
)和换行符意味着完全相同的东西。Bash认为那行代码是...
cmda && cmdb
cmdc
为了达到预期效果,我们必须将cmdb; cmdc
花括号括起来以使它们成为复合命令(group command)。附加的终止分号只是{ ...; }
语法的要求。所以我们得到...
cmda && { cmdb; cmdc; }
如果cmda
退出,cmdb
则执行,然后执行cmdc
。
如果cmda
退出,则不执行cmdb
或cmdc
不执行。
执行继续到下一行。
有条件的命令列表对于尽快从函数中返回,从而避免解释和执行许多不必要的代码,最有用。但是,多重函数返回意味着必须对保持简短的函数保持执着,因此更容易确保覆盖所有可能的条件。
这是一些运行代码的示例...
fnInit () {
:
_fn="$1"
### fnInit "${FUNCNAME}" ...
### first argument MUST be name of the calling function
#
[[ "$2" == "--help-all" ]] && { helpAll ; return 0; }
### pick from list of functions
#
[[ "$2" == "--note-all" ]] && { noteAll ; return 0; }
### pick from notes in METAFILE
#
[[ "$2" == "--version" ]] && { versionShow "${_fn}" "${@:3}"; return 0; }
#
[[ "$2" == "--function" ]] && {
isFnLoaded "$3" && { "${@:3}" ; return 0; }
#
errorShow functionnotfound "Unknown function: $3"
return 0
}
### call any loaded function
#
[[ "$2" == "--help" || "$2" == "-h" ]] && { noteShow "$_fn" "${@:3}"; return 0; }
### fnInit "${FUNCNAME}" --help or -h
#
return 1
}