结构
function func_name(){
statement
...
return code
}
架子和
js
的看起来一样,不过深究一下,差异还是特别的多。
position |
description |
---|---|
function |
函数声明,可省略 |
func_name |
函数名称,不可省略 |
() |
函数标记,不可带参 |
statement |
shell 语法 |
return |
返回状态 |
param |
参数和shell 一样,使用$n 进行提取 |
使用
$?
提取return
的值
状态码和返回值
状态码的探究
- 斐波那契引发的思考
写个斐波那契感受一下
#!/bin/bash
function fb(){
if [ $1 -eq 1] || [ $1 -eq 2 ];then
return 1
else
fb $(($1 - 1))
last1=$?
fb $(($1 - 2))
last2=$?
return $(($last1 + $last2))
fi
}
fb $1
echo $?
你会发现,5以上的结果都错了。换个方式
#!/bin/bash
function fb(){
if [ $1 -eq 2 ] || [ $1 -eq 1 ];then
echo 1
else
last1=`fb $(($1 - 1))`
last2=`fb $(($1 - 2))`
echo $((last1 + last2))
fi
}
fb $1
这下结果正确了,可以说,我们的逻辑没有问题,问题出在哪呢?
- 累加和的矫正
#!/bin/bash
function sum(){
if [ $1 -eq 1 ];then
return 1
else
sum $(($1 - 1))
last_sum=$?
return $(($1 + $last_sum))
fi
}
sum $1
echo $?
可以看到,在一定范围内,我们的计算结果是正确的。
查阅一下,思考一下。
$?
获取的是最后一条命令的返回状态。状态码范围是
[0,255]
,由此,我们的sum
计算结果超出255
就注定了溢出被截断。而
fb
中,递归嵌套太多了,其中的堆栈逻辑我也不好确认。打印计算步骤之后,发现最后一步总是
+1。
也就是说,
$?
所谓的最后一步,并不是我们感官那样的最后一步。不停的堆栈嵌套,加上
fb
中的重复计算,我们最终是丢失了我们的逻辑。不过
sum
证明了,在没有重复堆栈的过程下,即使是大量的堆栈,还是会如我们所想。
return
和exit
的统一
函数和
shell
有太多的相似,让我不得不去分解一下这两者的区别。毕竟参数,语法都是一致的,那么两种返回有什么不同呢。
#!/bin/bash
case $1 in
return)
return 1
;;
exit)
exit 2
;;
*)
;;
esac
首先,有额外的收获。
在
case
中的value
字段,有天生的屏蔽作用。除了存在通配符的语法之外,所谓关键字都只是字符而已。
虽说如此,不过尽量避免吧,误导可不好。
运行脚本,并$?
查看状态,你会发现,return
和exit
的值并没有什么区别。
回顾起一句话:函数没有返回值就会把最后一条指令执行结果进行返回
。
#!/bin/bash
function show(){
ls godme.sh
}
show
godme.sh
这个文件是不存在的。
你会发现,$?
是127,这个又是从哪里来的。
状态码的层级
自不量力,强行理解一波
- 命令
命令就是可执行的东东,大致场景就在于编写和调用两个地方。
如此划分,ls
之类的算作命令,函数
也是命令,脚本
也是一种命令。
可执行的都算作是命令,可调用,也可以自己编写。
- 返回
每个命令呢,都有返回值,但是仅仅是自身的返回值。
其他被调用的命令的返回值,不能够决定当前命令的返回值。
不过缺省默认最后一条被调用命令返回值作为此命令的返回值。
- 流程
说实话,就是一个中断返回的优先级问题。
类型 | 作用域 | 效果 |
---|---|---|
exit |
shell |
中断脚本并返回 |
return |
function |
中断函数并返回 |
other |
shell/function |
中断自身并返回 |
#!/bin/bash
function test(){
exit 4
}
test
exit 3
这个脚本执行之后,返回的必定是4
,因为脚本已经中断并返回了。
#!/bin/bash
function test(){
return 4
}
test
exit 3
这个呢,中断返回的是3
,脚本的事情,和函数的return
有什么关系呢。
#!/bin/bash
function test(){
return 4
}
test
恩,缺省默认,返回值当然是4
啦。
简单说来,进行编程的时候,我们只涉及了逻辑的中断返回,并不管理程序的生命周期。
但是在
shell
中,shell
作为可直接调用的程序
,本身就可以中断。
function
做为内部的组织结构,本身就是一段程序,本身就具有中断返回的功能。加上
shell
和function
的层级关系,导致了exit
可以在function
中使用,才有这么奇妙而畸形的组合。更加说明了
代码块
这个概念,避免重复编写,而直接引用的好处。不过,外部逻辑影响了内部逻辑,的确是个大问题,如果是非当前脚本的函数中的
exit
导致脚本中断。这问题可不小。
不过目前学的浅,不知道脚本能否调用外部函数。
但是在编写函数的时候,还是要注意别使用
exit
,有需求的话请再三确认。
返回值的概念
前面虽然一直说是返回值
,只是为了稳住return
的概念,实际上应该叫做状态码
。
那么,真正的返回值到底是什么呢。
说实话,我目前也不太清楚,但是$?
的确是太过于繁琐了。
我个人更倾向于使用****当做返回值,也就是直接用
echo``进行输出计算。
#!/bin/bash
function sum(){
if [ $1 -eq 1 ];then
echo 1
else
echo $(($1 + `sum $(($1 - 1))`))
fi
}
sum $1
两者的统一
高级语言中,我们经常会遇见一个问题:如果是XX结果,我们需要NN。
不过尴尬的是,我们的返回值只能有一个。
当然
java
当中可以用数组,python
中用元组。最方便的就是用
c++
了,一个指针引用,内部修改就能够传达到外部。这听起来像是
if...else
,不过,贴切点来说应该是try...catch
。针对不同的执行结果,我们采取不同的处理办法。
if...else
针对的是逻辑结构,更宽泛一些。
try...catch
针对的是执行结果,更有针对性一些。
类型 | 说明 |
---|---|
返回码 | 具体场景 |
返回值 | 具体结果 |
如果一开始就拿到了
exception
,不就是if...else
了么。
- 遍历字符串
为了演示一下功用,必须写一个脚本试试,首先先介绍一个字符串遍历的办法。
#!/bin/bash
str=$1
for index in `seq ${#str}`;do
echo ${str:$index-1:1}
done
seq
:这个没忘记吧,可以回顾一下#
:东西除了可以是注释,语法当中它可以用来计算长度${}
:包括一个表达式而已${str:start:length}
:在字符串str
的start
开始截取length
个字符
- 脚本演示
得到一个字符串,计算其中数值的和,拼接其他非数值字符串,并区分大小写和特殊字符
#!/bin/bash
function getChar(){
echo $1
case $1 in
[0-9])
return 1
;;
[a-z])
return 2
;;
[A-Z])
return 3
;;
*)
return 4
;;
esac
}
str=$1
sum=0
lowercase=""
uppercase=""
others=""
for index in `seq ${#str}`;do
value=`getChar ${str:$index-1:1}`
status=$?
case $status in
1)
((sum+=value))
;;
2)
lowercase=$lowercase$value
;;
3)
uppercase=$uppercase$value
;;
4)
others=$others$value
;;
esac
done
echo -e "sum\t\t: $sum"
echo -e "lowercase\t: $lowercase"
echo -e "uppercase\t: $uppercase"
echo -e "others\t\t: $others"
通过代码来看,总是感觉不够明晰。
但是,
退出状态
这一概念,的确是明白的显露出来的。至于通过状态选择分支,和通过条件选择分支,真就是一个模子。
我也无法说的更清楚一些。
脚本
- 连加
补上之前欠下的,多参数连加
#!/bin/bash
function sum(){
if [ $# -le 0 ];then
echo "need param"
fi
result=0
while [ $# -gt 0 ];do
((result+=$1))
shift
done
echo $result
}
sum $*
递归的也可以
#!/bin/bash
function sum(){
if [ $# -eq 2 ];then
echo $(($2+$1))
else
head=$1
shift
echo $((head+`sum $*`))
fi
}
sum $*
数组遍历的话,后面学到再说,要不知识体系有点混乱了,东一枪西一枪的。
小结
- 递归
命令都可以递归使用,之前没有函数的时候,我们递归的是脚本自身。
现在又function
了,就别继续骚操作
了。
- 状态码
区分好exit
和return
的使用范围,别不小心把脚本搞挂了。
- 返回值
返回的话推荐echo
进行值显示,有好办法的请告诉我,目前认识不多,请见谅。
return
的话,尽快纠正,他是状态码
返回,只能是[0,255]
,范围有限,容易出错。
case
case
中的value
自带转义,仅限于关键字,不过慎用。
case $param in
,头上选择是变量
而不是变量名
,别不小心弄错了,还找不到错误。
(嗯,没错,就是我,浪费了好多时间)
- 字符遍历
${str:index:length}
,截取字符串的办法,后续估计统一学习,现在先积累一些。
str
变量名,index
数值,length
数值,别记错了。
${$str}
取字符串长度。
- 返回
虽然有状态码和返回值,概念不清晰的建议双管齐下。
先通过状态码
判断场景,然后通过返回值
进行操作。
要不错误状态下的echo
提示语句参与了计算出错了怎么办。
可能看起来比较繁琐,但的确是个好办法,更是一个好习惯。