1. 函数介绍

1. 函数介绍

  1. 函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
  2. 它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,只是shell程序的一部分

1.1. 函数和shell程序比较相似,区别在于

  1. Shell程序在子Shell中运行
  2. 而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改

1.2. 定义函数

函数由两部分组成:函数名和函数体

help function

语法一:

f_name (){ ...函数体... }

语法二:

function f_name { ...函数体... }

语法三:

function f_name () { ...函数体... }

1.3. 函数使用

  1. 函数的定义和使用:
    • 可在交互式环境下定义函数
    • 可将函数放在脚本文件中作为它的一部分
    • 可放在只包含函数的单独文件中
  2. 调用:函数只有被调用才会执行
    • 调用:给定函数名
    • 函数名出现的地方,会被自动替换为函数代码
  3. 函数的生命周期:
    • 被调用时创建,返回时终止

1.4. 函数返回值

1.4.1. 函数有两种返回值:

  1. 函数的执行结果返回值:
    • (1) 使用echo等命令进行输出
    • (2) 函数体中调用命令的输出结果
  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. 函数注意点:

  1. 函数就像一个普通命令,声明定义之后(source之后)直接使用即可,看不出和命令的区别。因此命名函数的时候最好以func_funcname的方式进行命名加以区分。

  2. 删除函数和删除变量一样 用unset func_name命令即可

  3. 注意下面这两个命令,它俩显示是已经加载入内存中正在被引用的函数 – declare -F :只看函数名,后面可以跟函数名查询特定的函数,下同

    • declare -f : 看函数的定义内容,也就是/etc/init.d/functions文件中的内容,
      • 如果在当前shell自己source过自己的函数文件,也会把自己source过的函数也显示出来,表示已经可用了
    • declare -Fx :只查看全局函数名
    • declare -fx :查看全局函数的定义内容
  4. 函数定义之后才可以把它声明为全局函数,全局函数可以在当前shell的子shell中运行

    • 其声明的命令为:
      1. export -f 函数名(不加小括号)
      2. declare -fx 函数名(不加小括号)
    • 可以把全局函数的声明写在自己定义的函数文件的最后面,这样当在自己的父进程提前source这个函数文件之后,在子脚本中就无须再一次source函数文件了
    • 查看全局函数命令:
      1. export -f
      2. declare -fx
  5. 可以把自己常用的函数写在一个文件里,然后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 函数举例: shell脚本进阶-函数和信号插图
    综合举例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]
  6. 注意因为函数是在当前shell中运行,因此它的变量会影响当前脚本或者shell中的变量结果(也就是两个变量名字相同会造成变量污染),因此 函数中的变量一般都定义为 local variable=赋值;

    • 三种常见变量
    • 1.普通变量直接赋值无需定义,当前shell中有效;
    • 2.export 全局变量(环境变量) 可以定义的时候赋值,也可以普通变量赋值完之后再定义,子shell和当前shell有效;
    • 3.局部变量local 函数中使用,只在此函数以及此函数调用的子函数中有效;在函数外失效。
  7. 三个返回:

    1. return 返回出函数;
    2. break,continue 返回出循环
    3. exit 返回出shell(脚本)
    • 一个等待:wait 后台命令结束后自动返回前台并打印PS1提示符
    • 注意所有的返回都是返回到执行这个函数,循环,脚本的上一层环境命令处,三个返回命令后面的命令(相对应的就是在这个1函数,2循环,3脚本中)则不再执行了。
  8. { cmd; cdm;} 也可以看作是一个匿名函数(没有名字的函数,只能调用一次),它并不是匿名函数,它是一个命令合集,更详细的关于括号的解释参考其他博客或者看bash手册。

  9. return 后面不加数字的话就是返回上面的最后一个命令的$?值作为这整个函数的返回值,如果加上数字则把这个数字当做这个函数整体的返回值$?.

  10. 函数类似脚本后面也可以跟参数,用法一模一样。注意$*$@的区别 – 当加上双引号引用此参数传递到子函数或者子脚本中时

    • 前者代表一整个字符串作为一个参数
    • 后者代表分别作为一个一个参数,也就是把$@中以空格分开的每一项当做列表中的每一项。
- 不加上双引号没区别,这也是因为不加双引号会把里面的空格作为分隔符。
- 注意如果是脚本中调用函数,则脚本后面的参数调入到函数中时,如若函数后的参数是脚本所用的参数,一定要注意参数用`$加数字方式`的时候,写好顺序。同时如果想要函数对这些参数进行改变,一定注意函数内部的变量不要定义为局部变量才可。
  1. 函数如果写在脚本中要写在最前面以供后面的命令调用,注意shell中的函数没有先声明后定义的用法,只能写在最前面,不像C语言可以先声明后面再定义。
  2. 注意函数递归是先递归到最里层,然后最里层返回一个返回值,然后在从最里层往外返回到最外层。因此最里层一定要写好返回值的条件,不然会永远递归下去消耗系统资源。递归炸弹就从此而来。
  3. 通过阶乘的编写需要注意递归函数的编写: – return返回的值是函数的返回值存在$?中,不能作为函数输出的结果被上一层递归计算 – 用echo 返回出值,然后就可以代表这个函数名字当前层级的值,可以被上一层级来利用它进行计算 – 注意echo 返回值不能写在最后,只能写在递归的判断里面,否则每次递归都会echo 一次,会导致每次递归函数名的返回值是两个(2层) 3个(三层)…. 这样的话就没办法把函数本身当做一个数值来进行计算了。 – 最底层没有返回结果则会永远递归下去,不断的开子进程,folk炸弹就是利用此

2. 信号捕捉trap

  1. trap '触发指令'
    • 进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
  2. trap ''
    • 忽略信号的操作
  3. trap '-'
    • 恢复原信号的操作
  4. trap -p
    • 列出自定义信号操作
  5. trap finish EXIT
    • 当脚本退出时,执行finish函数

2.1. 信号相关注意点:

  1. trap的用处就是在shell(主要体现在脚本)中,让某些命令无法进行操作,比如说kill -9 命令等等。可以保证脚本的各种运行状态
  2. 还有一种情况就是脚本执行一半因为某些特殊情况非正常退出,或者说遇到某些条件判断退出了,则在退出的时候捕获到退出信号EXIT(EXIT信号并未在kill -l中列出),则会执行退出时指定的命令或者函数(需要脚本前面提前定义函数等)(If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell.)
  3. trap的生效在脚本中也是从前到后的,因此trap的信号捕获命令一定要写在脚本的最前端。同时后面的信号可以写对应的数字标号,特殊的0表示shell(脚本)退出信号。
  4. 注意下面trap中写的echo命令会打断sleep,如果想要不打断,则中间的命令部分什么也不写空着最好,这样int命令将没办法执行任何操作了。
  5. 简单介绍可以查看 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
最后修改日期: 2021年7月7日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。