Linux中shell编程的函数递归用法和脚本自动化讲解

一、函数递归

1.1 函数简介

样式1:函数间调用

  • 函数体内部调用其他的函数名

样式2:文件间调用

  • 函数体内部调用另外一个文件的函数名

  • 需要额外做一步文件source的加载动作

注意:我们将专门提供函数的文件称为 -- 函数库

样式3:函数自调用

  • 函数体内部调用自己的函数名,将复杂的逻辑简单化

1.2 简单实践

需求: 实现数学阶乘的实践

示例:5的阶乘

完整格式:5! = 1 * 2 * 3 * 4 * 5 = 120

简写格式:5! = 5 * (1 * 2 * 3 * 4) = 5 * 4!

公式: x! = x * (x-1)!

复制代码
查看脚本内容
[root@localhost ~]# vim a.sh
#!/bin/bash
# 功能:函数自调用实践

# 定制功能函数框架
self_func(){
    # 接收一个参数
    num=$1
    if [ ${num} -eq 1 ];then
        echo 1
    else
        # 定制一个临时本地变量,获取递减后的值
        local temp=$[ ${num} - 1 ]
        # 使用函数自调用方式获取内容
        local result=$(self_func $temp)
        # 格式化输出信息
        echo $[ $result * ${num} ]
    fi
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的阶乘:" value
    result=$(self_func ${value})
    echo "${value}的阶乘是: ${result}"
done

1.3 案例实践

实践1-遍历制定目录下的所有文件

复制代码
[root@localhost ~]# vim a.sh
#!/bin/bash
# 功能:扫描目录下所有文件
# 版本:v0.1
# 作者:书记
# 联系:

# 定制功能函数框架
# 定制目录扫描功能函数
scan_dir() {
    # 定制临时局部功能变量
	# cur_dir 当前目录 workdir 工作目录
    local cur_dir workdir
	
	# 接收要检查的目录,进入到目录中
    workdir=$1
    cd ${workdir}
	
	# 对工作目录进行简单判断,根目录没有父目录
    if [ ${workdir} = "/" ]
    then
        cur_dir=""
    else
        cur_dir=$(pwd)
    fi
    
	# 查看当前目录下的文件列表
    for item in $(ls ${cur_dir})
    do
        # 如果文件是目录,则继续查看目录下文件
        if test -d ${item};then
            cd ${item}
            scan_dir ${cur_dir}/${item}
            cd ..
        # 如果文件是普通文件,则输出信息即可
        else
            echo ${cur_dir}/${item}
        fi
    done
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的目录:" value
    if [ -d ${value} ]
    then
        scan_dir ${value}
    else
        echo "您输入的不是目录,请重新输入!"
    fi
done

二、脚本自动化

2.1 简介

信号 描述 信号 描述
1 SIGHUP 挂起进程 15 SIGTERM 优雅的终止进程
2 SIGINT 终止进程 17 SIGSTOP 无条件停止进程,不终止进程
3 SIGQUIT 停止进程 18 SIGTSTP 停止或暂停进程,不终止进程
9 SIGKILL 无条件终止进程 19 SIGCONT 继续运行停止的进程

默认情况下,bash shell会忽略收到的任何SIGQUIT(3)和SIGTERM(15)信号(正因为这样交互式shell才不会被意外终止)。但是bash shell会处理收到的SIGHUP(1)和SIGINT(2)信号。

如果bash shell收到SIGHUP信号,它会退出。但在退出之前,它会将信号传给shell启动的所有进程(比如shell脚本)。通过SIGINT信号,可以中断shell,Linux内核停止将CPU的处理时间分配给shell,当这种情况发生时,shell会将SIGINT信号传给shell启动的所有进程。

生成信号

复制代码
终止进程:
	ctrl+c,
暂停进程:
	ctrl+z,停止的进程继续保留在内存中,并能从停止的位置继续运行
恢复进程:
	jobs查看运行任务,fg num 重新执行
杀死进程:
	kill -9 pid

2.2 信号捕捉

简介

shell编程提供了一种方式,让我们可以随意的控制脚本的运行状态,这就需要涉及到信号的捕获操作。在shell编程中,我们可以借助于 trap命令实现指定shell脚本要watch哪些linux信号并从shell中拦截。如果脚本收到了trap命令中列出的信号,它会阻止它被shell处理,而在本地处理。

trap命令格式

复制代码
命令格式
	trap commands signals
	
命令示例:
	# 收到指定信号后,执行自定义指令,而不会执行原操作
    trap '触发指令' 信号
     
    # 忽略信号的操作
    trap '' 信号

    # 恢复原信号的操作
    trap '-' 信号
    
    # 列出自定义信号操作
    trap -p
    
    # 当脚本退出时,执行finish函数
    trap finish EXIT

简单实践

实践1-捕获终止信号

复制代码
查看脚本内容
[root@localhost ~]# vim a.sh
#!/bin/bash
# 功能:脚本信号捕捉

# 捕获关闭信号
trap "echo '你敢关我,就不关,气死你'" SIGINT SIGTERM
trap "echo '走了,不送'" EXIT

# 检测逻辑效果
while true
do
    read -p "请输入一个数据:" value
    echo "您输入的数据是: ${value}"
done

效果:

复制代码
脚本执行效果
[root@localhost ~]# /bin/bash a.sh
请输入一个数据:4
您输入的数据是: 4
请输入一个数据:^Csignal_trap_test1.sh:行1: 你敢关我,就不关,气死你: 未找到命令

您输入的数据是:
请输入一个数据:^Z
[1]+  已停止               /bin/bash signal_trap_test1.sh
[root@localhost ~]#
[root@localhost ~]# jobs
[1]+  已停止               /bin/bash signal_trap_test1.sh
[root@localhost ~]# fg 1
/bin/bash signal_trap_test1.sh

您输入的数据是:
请输入一个数据:3
您输入的数据是: 3

另开一个终端,直接kill进程
[root@localhost ~]# ps aux | grep sign
root      39142  0.0  0.0 113288  1460 pts/0    S+   17:43   0:00 /bin/bash signal_trap_test1.sh
[root@localhost ~]# kill -9 39142

回到之前的终端查看效果
[root@localhost ~]# fg 1
/bin/bash signal_trap_test1.sh

您输入的数据是:
请输入一个数据:3
您输入的数据是: 3
请输入一个数据:已杀死

实践2-捕获正常退出

复制代码
查看脚本内容
[root@localhost ~]# vim a.sh
#!/bin/bash
# 功能:脚本信号捕捉

# 捕获关闭信号
trap "echo '走了.不送'" EXIT

value="0"
# 检测逻辑效果
while true
do
    read -p "请输入一个数据:" value
    if [ ${value} == "9" ]
    then
        exit
    else
        echo "您输入的数据是: ${value}"
    fi
done

实践3-移除捕获

复制代码
查看脚本内容
[root@localhost ~]# vim a.sh
#!/bin/bash
# 功能:移除脚本信号捕捉

# 捕获关闭信号
trap "echo '走了.不送'" EXIT

i=1
# 检测逻辑效果
while [ $i -le 3 ]
do
    read -p "请输入一个数据:" value
    if [ ${value} == "9" ]
    then
        exit
    else
        echo "您输入的数据是: ${value}"
    fi
    let i+=1
done

# 移除捕获信号
trap - EXIT
echo "移除了捕获信号"

三、expect基础

3.1 基础知识

场景需求

在日常工作中,经常会遇到各种重复性的"手工交互"操作,虽然没有什么技术含量,但是相当的重要。在实际的工作场景中,这种重复性的手工操作动作,非常的繁多,但是对于量大的工作来说,效率就非常低效了。所以我们就需要有一种工具,能够简化我们重复的手工操作。

expect简介

expect是一个免费的编程工具,由DonLibes制作,作为Tcl脚本语言的一个扩展,它可以根据程序的提示,模拟标准输入提供给程序,从而实现自动的交互式任务,而无需人为干预,可以用作Unix系统中进行应用程序的自动化控制和测试的软件工具。

说白了,expect就是一套用来实现自动交互功能的软件。它主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信。在使用的过程中,主要是以脚本文件的样式来存在

官方网站:

https://www.nist.gov/services-resources/software/expect

工具手册:

man expect

软件部署

复制代码
安装软件
[root@localhost ~]# yum install expect -y

进入专用的命令交互界面
[root@localhost ~]# expect
expect1.1>  ls
anaconda-ks.cfg
expect1.2> exit

命令帮助
    -c:	执行脚本前先执行的命令,可多次使用,多个命令之间使用;隔开
    -d:	debug模式,可以在运行时输出一些诊断信息,与在脚本开始处使用exp_internal 1相似。
    -D:	启用交换调式器,可设一整数参数。
    -f:	从文件读取命令,仅用于使用#!时。如果文件名为"-",则从stdin读取(使用"./-"从文件名为-的文件读取)。
    -i:	交互式输入命令,使用"exit"或"EOF"退出输入状态。
    --:	标示选项结束(如果你需要传递与expect选项相似的参数给脚本时),可放到#!行:#!/usr/bin/expect --。
    -v:	显示expect版本信息

3.2 简单实践

在进行expect脚本编写的时候,我们需要记住 -- expect 用的不是我们普通的shell或者python语法,它使用的是Tcl语法。

Tcl 全称是 Tool command Language。它是一个基于字符串的命令语言,基础结构和语法非常简单,易于学习和掌握。Tcl 语言是一个解释性语言,所谓解释性是指不象其他高级语言需要通过编译和联结,它象其他 shell 语言一样,直接对每条语句顺次解释执行。

Tcl 数据类型简单。对 Tcl 来说,它要处理的数据只有一种------字符串。Tcl 将变量值以字符串的形式进行存储,不关心它的实际使用类型。

输出语法

输出:tcl使用"puts"关键字来作为输出语句

样式:puts -nonewline string

属性解析:

如果string中间有特殊字符,可以使用 {} 或者 "" 将其作为一个小组,共同输出

-nonewline 代表输出结果的时候,不输出换行符

put 和 puts 都可以在命令行使用,但是脚本中,最好用puts

脚本基础

1 文件名后缀

.expect 作为标识符

2 文件首行,要指定命令的执行解释器

#!/usr/bin/expect

3 脚本文件的执行

expect 脚本名

复制代码
脚本内容示例
[root@localhost ~]# vim a.expect
#!/usr/bin/expect
# 设定一个环境变量
set var nihao
# 输出环境变量
puts $var

脚本执行效果
[root@localhost ~]# expect expect_test.expect
nihao

3.3 语法实践

赋值语法

赋值:tcl 使用"set"关键字来定义参数,不必指定变量值的类型,因为变量值的类型仅一种------字符串

样式:set varName [value]

注意:

变量名是由 数字、下划线、字符组成,数字不能开头,大小写敏感。

复制代码
expect1.7> set a Hello			# 设定一个变量名a
Hello
expect1.8> put $a				# 使用$ 符号获取变量名的存储值
Hello
expect1.9> put "$a"				# 使用 "" 方式打印变量的值
Hello
expect1.10> put {$a}			# {} 有别于"" 的特点在于原字符输出
$a
expect1.11> set b $a			# 变量的传递
Hello
expect1.12> puts $b
Hello

替换语法

复制代码
替换(解析):
	- $符号可以实现引用替换,用于引用变量名代替的参数值,但是TCL对嵌套的"$"不于理睬
	- [] 方括号"[]"完成命令替换。用"[]"将一条命令括起来,命令执行完成后,返回结果

expect1.20> set b [set a 5]					# 相当于 set b $a,传递赋值
5
expect1.21> puts $b
5
expect1.22> set c [expr 5 * 10 ]			# expr是执行系统命令,将计算结果交给c
50
expect1.24> puts $c
50

注意事项

复制代码
变量的设定
expect1.13> set var value					# 设定一个普通变量名
value
expect1.14> puts $var						# 获取变量名的值
value

不支持嵌套$
expect1.15> set var1 $$value				# TCL不支持嵌套的$
can't read "value": no such variable
    while executing
"set var1 $$value"
expect1.16> set var1 $$var					# 由于$var 已经是变量,所以前面的$就无效了
$value
expect1.17> puts $var1
$value

原字符输出
expect1.18> set var2 {$var1}				# {} 代表原字符输出
$var1
expect1.19> puts $var2
$var1

脚本实践

内置变量

对于tcl来说,它内部包含了大量的内置变量,可以让我们实现快速的功能操作。

常见的内置变量有:

argc 指命令行参数的个数。

argv 指包含命令行参数的列表。

argv0 是指被解释的文件或由调用脚本的名称的文件名。

env 用于表示是系统环境变量的内容,普通变量我们还是使用$即可

tcl_version 返回Tcl解释器的最新版本,注意不是expect的版本号

内置参数实践

复制代码
[root@localhost ~]# cat expect_test1.expect
#!/usr/bin/expect
# 查看当前文件传递的参数数量
puts "当前文件传递的参数数量: $argc"

# 查看当前文件传递的参数
puts "当前文件传递的参数: $argv"

# 查看当前文件名称
puts "当前文件名称: $argv0"

# 获取变量值
puts "当前系统变量PATH的值是: $env(PATH)"
set key value
puts "普通变量 key 的值是: $key"

# 查看版本信息
puts "当前tcl版本信息: $tcl_version"

脚本执行效果
[root@localhost ~]# expect expect_test1.expect
当前文件传递的参数数量: 0
当前文件传递的参数:
当前文件名称: expect_test1.expect
当前系统变量PATH的值是: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
普通变量 key 的值是: value
当前tcl版本信息: 8.5

3.4 交互基础

脚本基础

命令解释器

复制代码
命令解释器
#!/usr/bin/expect
#!/usr/bin/expect -f    从文件中读取自动化命令
#!/usr/bin/expect -     如果文件名为 - ,那么从终端输入中读取
#!/usr/bin/expect -i	交互式输入命令
#!/usr/bin/expect -- 	脚本传递的选项参数和expect选项相似的参数给脚本
注意:
	#!是对脚本的解释器程序路径,脚本的内容是由解释器解释的
	
注释信息
# 被注释的信息

常见符号

复制代码
{ }:
	作用1:保留所有字符原有的意思,而不做解释,类似于shell中的单引号
		样式:set var {"nihao hehehe"}
	作用2:代码块儿,但是两个 {} 边界必须在一起。
		正确样式:
		if {代码块1 } {
   			代码块2
			}
		错误示例:
		if {$count < 0}
        {
           break;
        }
	注意:
		无论什么时候,{}边界符号与其他内容都最好有空格隔开,尤其是边界外的内容
[]:
	作用:执行命令,类似shell中的 ``(反引号)或者 $()
	样式:set count [expr $count - 1 ]
	
注意:
	在expect 中,没有小括号的概念和应用

常用命令

复制代码
set 		设定环境变量
				格式:set 变量名 变量值
				样式:set host "192.168.8.12"

				
spawn 		启动新的进程,模拟手工在命令行启动服务
				格式:spawn 手工执行命令
				样式:spawn ssh python@$host

expect 		接收一个新进程的反馈信息,我们根据进程的反馈,再发送对应的交互命令
				格式:expect "交互界面用户输入处的关键字"
				样式:expect "*password*"
				
send 		接收一个字符串参数,并将该参数发送到新进程。
				格式:send "用户输入的信息"	
				样式:send "$password\r"
				
interact 	退出自动化交互界面,进入用户交互状态,如果需要用户交互的话,这条命令必须在最后一行
				格式:interact
				样式:interact
				
其他命令
	exit		退出expect脚本
	expect eof	expect执行内容的结束标识符,退出当前脚本,与interact只能存在一个
	puts		输出变量的信息,相当于linux命令中的echo
	wait		退出程序后,等待时间,回收僵尸进程
	disconnect	断开一个进程连接,但让它在后台继续运行。
	exp_continue 	expect获取期望后,还会有另外的期望,那么我们就把多个期望连续执行

简单实践

实践1-简单的登录交互脚本

复制代码
查看脚本内容
[root@localhost ~]# cat login_test.expect
#!/usr/bin/expect

# 1 设定环境变量
set username python

# 2 发起远程登录请求
spawn ssh $username@192.168.66.146

# 3 识别用户输入的位置关键字
expect "yes/no"

# 4 发送正确的信息
send "yes\r"

# 5 识别密码关键字,并传递密码信息
send "\r"
expect "password:"
send "123456\r"

# 6 切换回用户交互界面
interact

注意:
	由于password前面会涉及到一次Enter操作,所以在password匹配前,输入一次 \r

清理历史记录

root@localhost \~\]# rm -f \~/.ssh/known_hosts 执行脚本内容 \[root@localhost \~\]# expect login_test.expect spawn ssh python@10.0.0.12 The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established. ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4. ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '10.0.0.12' (ECDSA) to the list of known hosts. python@10.0.0.12's password: \[python@localhost \~\]$ id uid=1000(python) gid=1000(python) 组=1000(python)

实践2-脚本结合

expect 除了使用专用的expect脚本来实现特定功能之外,它还可以与其他脚本嵌套在一起进行使用。最常用的结合方式就是 shell结合。

在于shell结合使用的时候,无非就是将expect的执行命令使用 <<-EOF 。。。 EOF 包装在一起即可。

样式:

/usr/bin/expect<<-EOF

spawn ...

...

expect eof

EOF

注意:

由于expect在shell中是作为一个子部分而存在的,所以,一般情况下,expect结束的时候,使用eof命令表示expect的内容到此结束

复制代码
查看脚本内容
[root@localhost ~]# cat expect_auto_login.sh
#!/bin/bash
# 功能:shell自动登录测试
# 版本:v0.1
# 作者:书记
# 联系:

# 定制普通变量
host="$1"
username="$2"
password="$3"

/usr/bin/expect <<-EOF
# 发出连接进程
spawn ssh ${username}@${host}

# - 正常登陆
expect {
    "yes/no*" {  send "yes\n"; exp_continue  }
    "password:" {send "${password}\n";}
}
puts "测试完毕!!!"
expect eof
EOF

脚本测试效果

root@localhost \~\]# /bin/bash expect_auto_login.sh 10.0.0.12 python 123456 spawn ssh python@10.0.0.12 python@10.0.0.12's password: 测试完毕!!! \[python@localhost \~\]$ exit \[root@localhost \~\]#

相关推荐
Trouvaille ~17 小时前
【Linux】库制作与原理(二):ELF格式与静态链接原理
linux·运维·c语言·操作系统·动静态库·静态链接·elf文件
写代码的橘子n17 小时前
IPV6复习(基础入手版)
运维·服务器·网络
天竺鼠不该去劝架17 小时前
财务自动化怎么做?财务RPA选型清单与路径
人工智能·科技·自动化
ICT技术最前线18 小时前
H3C双WAN口策略路由配置技术教程
运维·网络·h3c·策略路由
一分半心动18 小时前
windows docker desktop 安装VibeVoice
运维·docker·容器
向日葵.18 小时前
中间件交接文档
linux·运维·服务器
LucidX18 小时前
Docker核心操作实战
运维·docker·容器
隔壁阿布都18 小时前
Docker Compose中的网络管理
运维·docker·容器
云和数据.ChenGuang18 小时前
运维工程师技术教程之Pull Requests(PR)
运维·分布式·git·数据库运维工程师·运维教程
小快说网安18 小时前
抗 DDoS 防护在等保测评中的权重提升:云服务器如何通过防护能力加分?
运维·服务器·ddos·等保测评