Shell 函数的知识与实践

8. Shell 函数的知识与实践

8.1 Shell 函数介绍

在讲解Shell 函数之前,先来回顾Linux系统中 alias 的作用。

bash 复制代码
[shizhan@shell ~]$ ls -l --color=auto /home
total 4
drwx------. 3 devops  devops    78 Nov 29 13:56 devops
drwx------. 7 shizhan   shizhan   4096 Dec  6 11:50 shizhan
drwx------. 6 student student  169 Nov 29 13:56 student
[shizhan@shell ~]$ alias ll='ls -l --color=auto'
[shizhan@shell ~]$ ll /home/
total 4
drwx------. 3 devops  devops    78 Nov 29 13:56 devops
drwx------. 7 shizhan   shizhan   4096 Dec  6 11:50 shizhan
drwx------. 6 student student  169 Nov 29 13:56 student

函数也有类似于别名的作用,例如可简化程序的代码量,让程序更易读、易改、易用。

简单地说,函数的作用就是将程序里多次被调用的相同代码组合起来(函数体),并为其取一个名字(即函数名),其他所有想重复调用这部分代码的地方都只需要调用这个名字就可以了。当需要修改这部分重复代码时,只需要改变函数体内的一份代码即可实现对所有调用的修改,也可以把函数独立地写到文件里,当需要调用函数时,再加载进来使用。

使用 Shell 函数的优势整理如下:

  • 把相同的程序段定义成函数,可以减少整个程序的代码量,提升开发效率。
  • 增加程序的可读性、易读性,提升管理效率。
  • 可以实现程序功能模块化,使得程序具备通用性(可移植性)。

对于Shell来说,Linux系统里的近2000个命令可以说都是Shell的函数,所以,Shell的函数也是很多的,这一点需要读者注意。

8.2 Shell 函数的语法

下面是Shell 函数的常见语法格式。

标准写法

bash 复制代码
function 函数名 () {
  指令...
  return n
}

简化写法1:不写()

bash 复制代码
function 函数名 {
  指令...
  return n
}

简化写法2:不写function

bash 复制代码
函数名 () {
  指令...
  return n
}

8.3 Shell 函数的执行

Shell的函数分为最基本的函数和可以传参的函数两种,其执行方式分别说明如下。

  1. 执行不带参数的函数时,直接输人函数名即可(注意不带小括号)。

    格式如下:函数名

    有关执行函数的重要说明:

    • 执行Shell 函数时,函数名前的function和函数后的小括号都不要带。
    • 函数的定义必须在要执行的程序前面定义或加载。
    • Shell执行系统中各种程序的执行顺序为:系统别名->函数->系统命令->可执行文件
    • 函数执行时,会和调用它的脚本共用变量,也可以为函数设定局部变量及特殊位置参数。
    • 在Shell 函数里面,return命令的功能与exit类似,return的作用是退出函数,而exit是退出脚本文件。
    • return语句会返回一个退出值(即返回值)给调用函数的当前程序,而exit会返回一个退出值(即返回值)给执行程序的当前Shell。
    • 如果将函数存放在独立的文件中,被脚本加载使用时,需要使用source或来加载。
    • 在函数内一般使用local定义局部变量,这些变量离开函数后就会消失。
  2. 带参数的函数执行方法,格式如下:

    复制代码
    函数名 参数1 参数2

    函数后接参数的说明:

    • Shell 的位置参数(1、2...、2...、2...、#、∗、\*、∗、?及@)都可以作为函数的参数来使用。
    • 此时父脚本的参数临时地被函数参数所掩盖或隐藏。
    • $0 比较特殊,它仍然是父脚本的名称。
    • 当函数执行完成时,原来的命令行脚本的参数即可恢复。
    • 函数的参数变量是在函数体里面定义的。

8.4 Shell 函数的基础实践

示例1:hello函数

bash 复制代码
[shizhan@shell ~]$ cat fun1.sh 
#!/bin/bash
function hello () {
  echo "Hello World ! "
}
hello
[shizhan@shell ~]$ bash fun1.sh 
Hello World !

# 函数必须先定义,后调用
[shizhan@shell ~]$ cat fun2.sh 
#!/bin/bash
hello
function hello () {
  echo "Hello World !"
}
[shizhan@shell ~]$ bash fun2.sh
fun2.sh: line 2: hello: command not found

示例2:调用外部函数

bash 复制代码
[shizhan@shell ~]$ cat >> mylib << 'EOF'
function hello () {
  echo "Hello World !"
}
EOF

[shizhan@shell ~]$ cat fun3.sh 
#!/bin/bash
if [ -r mylib ];then
  source mylib
  hello
else
  echo mylib is not exist 
  exit 1
fi

[shizhan@shell ~]$ bash fun3.sh 
Hello World !

示例3:带参数的函数

bash 复制代码
[shizhan@shell ~]$ cat fun4.sh 
#!/bin/bash
function print () {
  if [ "$1" == "PASS" ];then
    echo -e '\033[1;32mPASS\033[0;39m'
  elif [ "$1" == "FAIL" ];then
    echo -e '\033[1;31mFAIL\033[0;39m'
  elif [ "$1" == "DONE" ];then
    echo -e '\033[1;35mDONE\033[0;39m'
  else
    echo "Usage: print PASS|FAIL|DONE"
  fi
}
read -p "请输入你想要打印的内容:" str
print $str

[shizhan@shell ~]$ bash fun4.sh 
请输入你想要打印的内容:`PASS`
PASS
[shizhan@shell ~]$ bash fun4.sh 
请输入你想要打印的内容:hello
Usage: print PASS|FAIL|DONE

示例4:hello函数

bash 复制代码
[shizhan@shell ~]$ cat fun5.sh 
#!/bin/bash
function print () {
  if [ "$1" == "PASS" ];then
    echo -e '\033[1;32mPASS\033[0;39m'
  elif [ "$1" == "FAIL" ];then
    echo -e '\033[1;31mFAIL\033[0;39m'
  elif [ "$1" == "DONE" ];then
    echo -e '\033[1;35mDONE\033[0;39m'
  else
    echo "Usage: $0 PASS|FAIL|DONE"
fi
}
str=$2
print $str

# 结果表明,脚本的第一个参数并没有传递给脚本内部函数。
[shizhan@shell ~]$ bash fun5.sh PASS FAIL
FAIL

# 结果表明,$0 仍然使用脚本名,而非函数名。
[shizhan@shell ~]$ bash fun5.sh 
Usage: fun5.sh PASS|FAIL|DONE

示例5:计算1+2+3+...+10

bash 复制代码
# libs脚本中定义求和函数sum
[root@shell bin 16:03:12]# vim libs
function sum() {
 num=$1
 echo "$(seq -s '+' $num) = "$(( $(seq -s '+' $num) ))
}

[root@shell bin 15:59:45]# source libs
[root@shell bin 16:00:17]# sum 10
1+2+3+4+5+6+7+8+9+10 = 55

8.5 利用 Shell 函数开发企业级URL检测脚本

bash 复制代码
[shizhan@shell ~]$ cat check_url.sh 
#!/bin/bash
function usage () {
  echo "usage: $0 url"
  exit 1
}

function check_url () {
  wget --spider -q -o /dev/null --tries=1 -T 5 $1
  [ $? -eq 0 ] && echo $1 is accessable || echo $1 is not accessable
}

function main () {
  [ $# -ne 1 ] && usage
  check_url $1
}

main $*

# 测试结果如下:
[shizhan@shell ~]$ bash check_url.sh www.baidu.com
www.baidu.com is accessable
[shizhan@shell ~]$ bash check_url.sh www.shizhan.com
www.shizhan.com is not accessable

8.6 函数的递归调用

示例1:求1+2+3+...+10 的和

bash 复制代码
#!/bin/bash
function sum_out() {
  if [ $1 -eq 1 ];then
    sum=1
  else
    sum=$[ $1 + $(sum_out $[ $1 - 1 ] ) ]
  fi
  echo $sum
}
read -p "输入一个你想计算和的整数:" num
sum_out $num

# 或者
function sum() {
  num=$1
  if ((num==1)); then
    echo 1
  else
    #echo $[ $(sum $[ num-1 ]) + num ]
    echo $[ $(sum $(( num-1 ))) + num ]
  fi
}

示例2:求1*2*3*...*10 的阶乘

bash 复制代码
#!/bin/bash
function fact_out() {
  if [ $1 -eq 1 ];then
    sum=1
  else
    sum=$[ $1 * $(fact_out $[ $1 - 1 ] ) ]
  fi
  echo $sum
}
read -p "输入一个你想计算和的整数:" num
fact_out $num

示例3:fork 炸弹

bash 复制代码
:(){ :|:& };:

解释如下:

bash 复制代码
:()   # 定义函数,函数名为":",即每当输入":"时就会自动调用{}内代码
{        # ":"函数起始字元
    :    # 用递归方式调用":"函数本身
    |    # 使用管道一次产生两个函数调用
    :    # 另一次递归调用的":"函数
    &    # 放后台运行,以便产生更多的子进程
}        # ":"函数终止
;        # ":"函数定义结束后将要进行的操作...
:        # 调用":"函数,"引爆"fork炸弹

fork 炸弹原因:无限制的启用新进程,直到消耗完所有计算资源。

解决办法:限制用户进程数量。

例如:限制100个进程

bash 复制代码
[shizhan@shell ~]$ ulimit -u 100
相关推荐
Deng8723473481 小时前
代码语法检查工具
linux·服务器·windows
霍夫曼3 小时前
UTC时间与本地时间转换问题
java·linux·服务器·前端·javascript
2301_810746313 小时前
CKA冲刺40天笔记 - day20-day21 SSL/TLS详解
运维·笔记·网络协议·kubernetes·ssl
❀͜͡傀儡师4 小时前
docker 部署 komari-monitor监控
运维·docker·容器·komari
物联网软硬件开发-轨物科技4 小时前
【轨物方案】软硬件一体赋能,开启矿山机械远程智慧运维新篇章
运维
月熊4 小时前
在root无法通过登录界面进去时,通过原本的普通用户qiujian如何把它修改为自己指定的用户名
linux·运维·服务器
大江东去浪淘尽千古风流人物5 小时前
【DSP】向量化操作的误差来源分析及其经典解决方案
linux·运维·人工智能·算法·vr·dsp开发·mr
打码人的日常分享5 小时前
智慧城市一网统管建设方案,新型城市整体建设方案(PPT)
大数据·运维·服务器·人工智能·信息可视化·智慧城市
赖small强5 小时前
【Linux驱动开发】NOR Flash 技术原理与 Linux 系统应用全解析
linux·驱动开发·nor flash·芯片内执行