1. 函数介绍
- 函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
- 它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,只是shell程序的一部分
1.1. 函数和shell程序比较相似,区别在于
- Shell程序在子Shell中运行
- 而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改
1.2. 定义函数
函数由两部分组成:函数名和函数体
help function
语法一:
f_name (){
...函数体...
}
语法二:
function f_name {
...函数体...
}
语法三:
function f_name () {
...函数体...
}
1.3. 函数使用
- 函数的定义和使用:
- 可在交互式环境下定义函数
- 可将函数放在脚本文件中作为它的一部分
- 可放在只包含函数的单独文件中
- 调用:函数只有被调用才会执行
- 调用:给定函数名
- 函数名出现的地方,会被自动替换为函数代码
- 函数的生命周期:
- 被调用时创建,返回时终止
1.4. 函数返回值
1.4.1. 函数有两种返回值:
- 函数的执行结果返回值:
- (1) 使用echo等命令进行输出
- (2) 函数体中调用命令的输出结果
- 函数的退出状态码:
- (1) 默认取决于函数中执行的最后一条命令的退出状态码
- (2) 自定义退出状态码,其格式为:
- return 从函数中返回,用最后状态命令决定返回值
- return 0 无错误返回
- return 1-255 有错误返回
1.5. 环境函数
使子进程也可使用
声明:
export -f function_name
查看:
export -f 或 declare -fx
1.6. 函数递归示例
- 函数递归:
函数直接或间接调用自身
注意递归层数 - 示例:阶乘
- 这里需要注意的就是,bash shell中(当不使用bc命令的时候)利用双小括号计算的数值最大是63位个1(最高位是符号位)
- 因此阶乘函数也是如此,如果计算的数值过大,则结果不正确,因此算阶乘最好用bc计算机工具计算,而不是在bash中直接用行数计算# echo $(( 9223372036854775807-1 )) 9223372036854775806 # echo $(( 9223372036854775807+1 )) -9223372036854775808 # echo $(( 9223372036854775807+2 )) -9223372036854775807 # echo $(( 9223372036854775807+3 )) -9223372036854775806 ### 其中63位1就是9223372036854775807 ,64位1则是-9223372036854775808
1.6.1. 递归函数注意
一定要有返回的处理,不然就会无穷无尽死循环进行调用并开启子进程,导致系统资源用尽卡死
1.6.2. fork炸弹
fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
- 函数实现
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb
- 脚本实现
cat Bomb.sh
#!/bin/bash
./$0|./$0&
func_factorial(){
local input=$1
if func_is_onepositint $input ;then
[ $input -gt 1 ] && echo "$input * `func_factorial $((input-1))`" | \bc || echo 1
else
echo -e "Please input a positive integer"
return 1
fi
}
1.7. 函数注意点:
函数就像一个普通命令,声明定义之后(source之后)直接使用即可,看不出和命令的区别。因此命名函数的时候最好以func_funcname的方式进行命名加以区分。
删除函数和删除变量一样 用
unset func_name命令即可注意下面这两个命令,它俩显示是已经加载入内存中正在被引用的函数 –
declare -F:只看函数名,后面可以跟函数名查询特定的函数,下同declare -f: 看函数的定义内容,也就是/etc/init.d/functions文件中的内容,- 如果在当前shell自己source过自己的函数文件,也会把自己source过的函数也显示出来,表示已经可用了
declare -Fx:只查看全局函数名declare -fx:查看全局函数的定义内容
函数定义之后才可以把它声明为全局函数,全局函数可以在当前shell的子shell中运行
- 其声明的命令为:
export -f 函数名(不加小括号)declare -fx 函数名(不加小括号)
- 可以把全局函数的声明写在自己定义的函数文件的最后面,这样当在自己的父进程提前
source这个函数文件之后,在子脚本中就无须再一次source函数文件了 - 查看全局函数命令:
export -fdeclare -fx
- 其声明的命令为:
可以把自己常用的函数写在一个文件里,然后source它引用。
比如系统中/etc/init.d/functions中的actions()函数就可拿来直接用,先source或者.它即可. 注意脚本中也要source它,这样它才能在开启的子shell中也运行。查看它的内容: # declare -f action action () { local STRING rc; STRING=$1; echo -n "$STRING "; shift; "$@" && success "$STRING" || failure "$STRING"; rc=$?; echo; return $rc } ##### 由此可知它的功能就是 用法:action "描述后面命令的字符串" 命令CMD 结果: - 如果后面的命令CMD执行成功,也就是后面CMD的返回值是0(true),那么action就会把 "描述后面命令的字符串” 打印一遍并在后面显示一个 '[OK]` 的样式,表示后面的 '命令CMD` 执行成功 - 反过来命令执行失败(返回值为假)则显示一个'[false]`的样式 - ##### 这里面需要注意的就是 "$@" :就算`$@`是空值,它的返回结果也是true; 它是用来判断除了$1之外剩下的参数当做命令时的执行结果的,不过要注意的就是如果它为空,返回结果仍然是真 比如直接在命令行测试: # echo "$@" # echo "$@" | cat -A $ # "$@" # echo $? 0 # "$@" && echo true || echo false true- action 函数举例:

综合举例1: # action "test describing srtings" true test describing srtings [ OK ] # action "test describing srtings" hostname test describing srtings cet [ OK ] # action "test describing srtings" hostname > /dev/null # action "test describing srtings" `hostname > /dev/null` test describing srtings [ OK ] # action "test describing srtings" `hostname` test describing srtings bash: cet: command not found... [FAILED] # action "test describing srtings" false test describing srtings [FAILED] 举例2: # rc=0 # action test [ "$rc" = 0 ] test [ OK ] # rc=1 # action test [ "$rc" = 0 ] test [FAILED]- action 函数举例:
注意因为函数是在当前shell中运行,因此它的变量会影响当前脚本或者shell中的变量结果(也就是两个变量名字相同会造成变量污染),因此 函数中的变量一般都定义为
local variable=赋值;- 三种常见变量
- 1.普通变量直接赋值无需定义,当前shell中有效;
- 2.
export 全局变量(环境变量) 可以定义的时候赋值,也可以普通变量赋值完之后再定义,子shell和当前shell有效; - 3.
局部变量local函数中使用,只在此函数以及此函数调用的子函数中有效;在函数外失效。
三个返回:
return 返回出函数;break,continue 返回出循环exit 返回出shell(脚本)
- 一个等待:
wait后台命令结束后自动返回前台并打印PS1提示符 - 注意所有的返回都是返回到执行这个函数,循环,脚本的上一层环境命令处,三个返回命令后面的命令(相对应的就是在这个1函数,2循环,3脚本中)则不再执行了。
,它并不是匿名函数,它是一个命令合集,更详细的关于括号的解释参考其他博客或者看bash手册。{ cmd; cdm;}也可以看作是一个匿名函数(没有名字的函数,只能调用一次)return后面不加数字的话就是返回上面的最后一个命令的$?值作为这整个函数的返回值,如果加上数字则把这个数字当做这个函数整体的返回值$?.函数类似脚本后面也可以跟参数,用法一模一样。注意
$*和$@的区别 – 当加上双引号引用此参数传递到子函数或者子脚本中时- 前者代表一整个字符串作为一个参数
- 后者代表分别作为一个一个参数,也就是把
$@中以空格分开的每一项当做列表中的每一项。
- 不加上双引号没区别,这也是因为不加双引号会把里面的空格作为分隔符。
- 注意如果是脚本中调用函数,则脚本后面的参数调入到函数中时,如若函数后的参数是脚本所用的参数,一定要注意参数用`$加数字方式`的时候,写好顺序。同时如果想要函数对这些参数进行改变,一定注意函数内部的变量不要定义为局部变量才可。
- 函数如果写在脚本中要写在最前面以供后面的命令调用,注意shell中的函数没有先声明后定义的用法,只能写在最前面,不像C语言可以先声明后面再定义。
- 注意函数递归是先递归到最里层,然后最里层返回一个返回值,然后在从最里层往外返回到最外层。因此最里层一定要写好返回值的条件,不然会永远递归下去消耗系统资源。递归炸弹就从此而来。
- 通过阶乘的编写需要注意递归函数的编写:
– return返回的值是函数的返回值存在
$?中,不能作为函数输出的结果被上一层递归计算 – 用echo返回出值,然后就可以代表这个函数名字当前层级的值,可以被上一层级来利用它进行计算 – 注意echo返回值不能写在最后,只能写在递归的判断里面,否则每次递归都会echo一次,会导致每次递归函数名的返回值是两个(2层) 3个(三层)…. 这样的话就没办法把函数本身当做一个数值来进行计算了。 – 最底层没有返回结果则会永远递归下去,不断的开子进程,folk炸弹就是利用此
2. 信号捕捉trap
trap '触发指令'- 进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap ''- 忽略信号的操作
trap '-'- 恢复原信号的操作
trap -p- 列出自定义信号操作
trap finish EXIT- 当脚本退出时,执行finish函数
2.1. 信号相关注意点:
- trap的用处就是在shell(主要体现在脚本)中,让某些命令无法进行操作,比如说
kill -9命令等等。可以保证脚本的各种运行状态 - 还有一种情况就是脚本执行一半因为某些特殊情况非正常退出,或者说遇到某些条件判断退出了,则在退出的时候捕获到退出信号EXIT(EXIT信号并未在
kill -l中列出),则会执行退出时指定的命令或者函数(需要脚本前面提前定义函数等)(If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell.) - trap的生效在脚本中也是从前到后的,因此trap的信号捕获命令一定要写在脚本的最前端。同时后面的信号可以写对应的数字标号,特殊的0表示shell(脚本)退出信号。
- 注意下面trap中写的echo命令会打断sleep,如果想要不打断,则中间的命令部分什么也不写空着最好,这样int命令将没办法执行任何操作了。
- 简单介绍可以查看 https://segmentfault.com/a/1190000022092541
2.2. trap示例
#!/bin/bash
trap 'echo “signal:SIGINT"' int
trap -p
for((i=0;i<=10;i++))
do
sleep 1
echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i++))
do
sleep 1
echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i++))
do
sleep 1
echo $i
done
留言