Shell高级用法-----函数(function)及expect用法
函数介绍(function用法)
1、function用法
1、函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程。
2、它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运 行,而是shell程序的一部分,定义函数只对当前的会话窗口有效,如果再打开一个窗口再定义另外一个函数,就对另一个窗口有效,两者互不影响。
3、函数和shell程序比较相似,区别在于以下两种:
(1)Shell程序在子Shell中运行。
(2)而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改。
2、定义函数
函数由两部分组成:函数名和函数体
1 2 3 4 5 6 7 8 9 10 | help function 语法一: f_name (){ ...函数体... } 语法二: function f_name { ...函数体... } 语法三: function f_name () { ...函数体... } |
可以使用declare -F 选项进行查看所有定义的函数,用unset 加上变量名 就可以删除定义的变量
3、函数的定义和使用:
1、函数的定义和使用:
(1)可在交互式环境下定义函数
(2)可将函数放在脚本文件中作为它的一部分
(3)可放在只包含函数的单独文件中
2、调用:函数只有被调用才会执行
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
3、函数的生命周期:被调用时创建,返回时终止
4、函数返回值
函数有两种返回值:
1、函数的执行结果返回值:
(1) 使用echo等命令进行输出
(2) 函数体中调用命令的输出结果
2、函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值:
(1)return 0 无错误返回。
(2)return 1-255 有错误返回
5、使用函数文件
1、可以将经常使用的函数存入函数文件,然后将函数文件载入shell
2、文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
3、一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命 令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
4、若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载 入此文件
6、删除shell函数
1、现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命 令完成删除函数
2、命令格式为: unset function_name
示例: unset findit
再键入set命令,函数将不再显示
3、环境函数
(1)使子进程也可使用
(2)声明:export -f function_name
(3)查看:export -f 或 declare -xf
7、函数参数
函数可以接受参数: 传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;
例如“testfunc arg1 arg2 ...”
在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $# 等特殊变量
8、函数变量
变量作用域:
环境变量:当前shell和子shell有效
本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程; 因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
局部变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有局部变量,如果其名称同本地变量,使用局部变量
在函数中定义局部变量的方法:local NAME=VALUE
实例1:
第一种写法:如果命令过多,这行执行不太方便
1 2 3 4 5 | #!/bin/bash func_os_version () { # 定义一个function函数名为func_os_version,然后在大括号里边定义命令,取出操作系统的版本号,类似于定义别名一样 sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release } echo OS version is `func_os_version` # 直接写上定义函数名称,或者用echo 加上反向单引号进行输出结果 |
查看输出结果:
1 2 | [root@centos-7 ~]# bash osversion.sh OS version is 7 |
第二种写法:将定义的函数存放到文件中,并将要执行的脚本与定义的函数以及定义函数的文件名进行关联
1 2 3 4 5 6 7 8 9 10 11 | [root@centos-7 ~]# cat functions # 将定义的函数放到functions文件中 func_os_version () { sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release } [root@centos-7 ~]# cat osversion.sh # 将要执行脚本的函数名和上面定义函数名的文件进行关联 #!/bin/bash source functions # source functions是关联上面的文件 func_os_version # 关联functions里边定义的函数名 [root@centos-7 ~]# chmod +x osversion.sh # 对脚本加上执行权限 [root@centos-7 ~]# ./osversion.sh # 查看此时的执行结果即可 7 |
实例2:
第一步:先定义functions函数文件
1 2 3 4 5 6 7 8 9 10 11 12 | [root@centos-7 data]# cat functions # 定义functions函数文件 func_is_digit(){ if [ ! "$1" ];then # 如果输入的信息不是空,就为真,但又不是数字 echo "Usage:func_is_digit number" # 请输入数字 return 10 elif [[ $1 =~ ^[[:digit:]]+$ ]];then # 如果输入是数字 return 0 # return 0 返回的是正确结果,但是不会推出脚本 else echo "Not a digit" # 如果上面都不是,就提醒不是数字 return 1 fi } |
第二步:调用functions函数文件,并对不同的成绩分段进行判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [root@centos-7 data]# cat score.sh #!/bin/bash source /data/functions # 调用指定的函数文件的绝对路径 read -p "Input your score:" SCORE func_is_digit $SCORE # 直接调用上面的functions文件 if [ $? -ne 0 ];then #判断上面的命令执行不等于0(不成功)就退出 exit else if [ $SCORE -lt 60 ];then # 如果成功了,对成绩的三种判断如下。 echo "You are loser" elif [ $SCORE -lt 80 ];then echo "soso" else echo "very good" fi fi |
实例3
生产中function配合case语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | #!/bin/bash #Author: liupengju #date: 2020-06-22 #TEL: xxxxxxxxxx #代码发布与回滚 set -e set -u #adx代码部署变量定义 ADX_DIR=/gnome/adx adx_new_version= "gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar" ADX_NEW_MD5=`md5sum $adx_new_version | awk '{ print $1 }' ` #此行需要修改为cf平台的md5码 ADX_CHK_MD5= "43bcfe7594f083a8653126e0896b93ac" #directAd代码部署变量定义 direct_DIR=/gnome/directAd/ direct_version= "direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar" direct_MD5=`md5sum $direct_version | awk '{ print $1 }' ` #direct_old_version=$(ls -l |tail -n1 | awk '{print $9}') #此行需要修改为cf平台的md5码 direct_CHK_MD5= "03c3c2fc62b2edfc92e548351010ee9f" ##########部署directAd代码############################# fun_copy_direct_code(){ mv $direct_DIR/$direct_version $direct_DIR/bak/${direct_version}_$(date + "%F-%T" ) echo "-----上一个版本已经移动到备份目录" cp /data/$direct_version $direct_DIR && echo "-----代码复制成功!!!" } fun_chk_direct_code(){ if [[ "$direct_MD5" == "$direct_CHK_MD5" ]];then echo "-----代码校验成功" && echo "代码部署成功后MD5值为:$direct_MD5" else echo "-----代码校验失败" && exit fi } fun_deploy_direct_restart(){ #$direct_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "后端服务重启成功!!!" } fun_chk_direct_port1(){ #验证端口存活状态 PORT1=`ss -nlt|grep 8080 |awk -F "[[:space:]]+|:" '{ print $7}' ` PORT2=`ss -nlt|grep 8182 |awk -F "[[:space:]]+|:" '{ print $7}' ` for port in $PORT1 $PORT2; do echo "The port is:$port------监听端口正常" done } #############回滚direct代码################################### fun_rollback_direct_code(){ cd $direct_DIR/bak direct_old_version=$(ls -l |tail -n1 | awk '{print $9}' ) # 提取上一个版本的jar包 mv $direct_DIR/${direct_version} $direct_DIR/bak/${direct_version}_$(date + "%F-%T" ) mv $direct_DIR/bak/${direct_old_version} $direct_DIR/${direct_version} echo "------旧版本代码移动成功" direct_old_MD5=$(md5sum $direct_DIR/${direct_version} | awk '{print $1}' ) echo "代码回滚后MD5值为:$direct_old_MD5" } fun_rollback_direct_restart(){ #$direct_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "--------后端服务重启成功" } fun_chk_direct_port2(){ #验证端口存活状态 PORT1=`ss -nlt|grep 8080 |awk -F "[[:space:]]+|:" '{ print $7}' ` PORT2=`ss -nlt|grep 8182 |awk -F "[[:space:]]+|:" '{ print $7}' ` for port in $PORT1 $PORT2; do echo "The port is:$port------端口监听正常" done } #####################adx代码部署######################################## fun_copy_adx__code(){ mv $ADX_DIR/$adx_new_version $ADX_DIR/bak/${adx_new_version}_$(date + "%F-%T" ) echo "-----上一个版本已经移动到备份目录" cp /data/$adx_new_version $ADX_DIR && echo "-----代码复制成功!!!" } fun_chk_adx_code(){ if [[ "$ADX_NEW_MD5" == "$ADX_CHK_MD5" ]];then echo "-----代码校验成功" && echo "代码部署成功后MD5值为:$ADX_NEW_MD5" else echo "-----代码校验失败" && exit fi } fun_deploy_adx_restart(){ #$ADX_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "后端服务已经启动!!!" } #验证端口存活状态 fun_chk_adx_port1(){ PORT1=`ss -nlt|grep 8080 |awk -F "[[:space:]]+|:" '{ print $7}' ` PORT2=`ss -nlt|grep 8182 |awk -F "[[:space:]]+|:" '{ print $7}' ` for port in $PORT1 $PORT2; do echo "The port is:$port------监听的端口正常启动" done } ###################################adx代码回滚########################### fun_rollback_adx_code(){ cd $ADX_DIR/bak adx_old_version=$(ls -l |tail -n1 | awk '{print $9}' ) mv $ADX_DIR/${adx_new_version} $ADX_DIR/bak/${adx_new_version}_$(date + "%F-%T" ) mv $ADX_DIR/bak/${adx_old_version} $ADX_DIR/${adx_new_version} echo "------旧版本代码移动成功" adx_old_MD5=$(md5sum $ADX_DIR/${adx_new_version} | awk '{print $1}' ) echo "代码回滚后MD5值为:$adx_old_MD5" } fun_rollback_adx_restart(){ #$ADX_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "--------后端服务已经启动" } fun_chk_adx_port2(){ #验证端口存活状态 PORT1=`ss -nlt|grep 8080 |awk -F "[[:space:]]+|:" '{ print $7}' ` PORT2=`ss -nlt|grep 8182 |awk -F "[[:space:]]+|:" '{ print $7}' ` for port in $PORT1 $PORT2; do echo "The port is:$port-------端口监听正常" done } case $1 in direct_deploy) fun_copy_direct_code fun_chk_direct_code fun_deploy_direct_restart fun_chk_direct_port1 ;; direct_rollback) fun_rollback_direct_code fun_rollback_direct_restart fun_chk_direct_port2 ;; adx_deploy) fun_copy_adx__code fun_chk_adx_code fun_deploy_adx_restart fun_chk_adx_port1 ;; adx_rollback) fun_rollback_adx_code fun_rollback_adx_restart fun_chk_adx_port2 ;; esac |
实例4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #!/bin/bash #Auth: liupengju #TEL: xxxxx ########部署完成校验####### ####验证adserver版本号############# fun_chk_adx_version(){ ansible adx -m shell -a 'md5sum /gnome/adx/gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}' |sort |head -n62 |tee version_adx |cat -n adx_version=$(ansible adx -m shell -a 'md5sum /gnome/adx/gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}' |sort |tail -n62 |uniq -c|awk '{print $2}' ) echo -e "\e[1;32m新发布的版本号为:$adx_version\e[0m" version1=$(diff metadata version_adx) if [ -z $version1 ];then echo -e "\e[1;32m代码部署成功 \e[0m" else echo -e "\e[1;31m请检查错误 \e[0m" fi } ####验证directAd版本号############ fun_chk_direct_version(){ ansible adx -m shell -a ' md5sum /gnome/directAd/direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}' |sort |head -n62 |tee version_direct |cat -n direct_version=$(ansible adx -m shell -a 'md5sum /gnome/directAd/direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}' |sort |tail -n62 |uniq -c|awk '{print $2}' ) echo -e "\e[1;32m新发布的版本号为:$direct_version\e[0m" version2=$(diff metadata version_direct) if [ -z $version2 ];then echo -e "\e[1;32m代码部署成功 \e[0m" else echo -e "\e[1;31m请检查错误 \e[0m" fi } ###验证8080端口状态############### fun_chk_8080_port(){ chk_ip_8080=$(ansible adx -m shell -a ' netstat -ntulp |grep 8080' |awk '{print $1}' |egrep "[0-9]+\.*" |sort | tee data_8080.bak |cat -n) DIR_8080=$(diff metadata data_8080.bak) if [ -z $DIR_8080 ];then echo -e "\e[1;32m端口检查成功,端口号:8080 \e[0m" else echo -e "\e[1;31m请检查错误 \e[0m" fi } ####验证8182端口状态############# fun_chk_8182_port(){ chk_ip_8182=$(ansible adx -m shell -a ' netstat -ntulp |grep 8182' |awk '{print $1}' |egrep "[0-9]+\.*" |sort |tee data_8182.bak |cat -n) DIR_8182=$(diff metadata data_8182.bak) if [ -z $DIR_8182 ];then echo -e "\e[1;32m端口检查成功,端口号:8182 \e[0m" else echo -e "\e[1;31m请检查错误 \e[0m" fi } case $1 in adx) fun_chk_adx_version fun_chk_8080_port fun_chk_8182_port ;; direct) fun_chk_direct_version fun_chk_8080_port fun_chk_8182_port ;; esac |
expect命令
expect 语法:
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
选项
-c:从命令行执行expect脚本,默认expect是交互地执行的
示例:expect -c 'expect "\n" {send "pressed enter\n"}
-d:可以输出输出调试信息
示例:expect -d ssh.exp
expect中相关命令 :
1 2 3 4 5 | spawn:启动新的进程 send:用于向进程发送字符串 expect:从进程接收字符串 interact:允许用户交互,并停留在远程连接的主机上 exp_continue 匹配多个字符串在执行动作后加此命令 |
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:
1 | expect “hi” {send “You said hi\n"} |
匹配到hi后,会输出“you said hi”,并换行
多分支模式语法:
1 2 3 | expect "hi" { send "You said hi\n" } \ "hehe" { send "Hehe yourself\n" } \ "bye" { send "Good bye\n" } |
匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
1 2 3 4 5 | expect { "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send " Good bye\n" } } |
实例5
1 2 3 4 5 6 7 8 9 10 11 | #!/usr/bin/expect set ip 192.168.8.100 set user root set password centos set timeout 10 spawn ssh $user@$ip expect { "yes/no" { send "yes\n" ;exp_continue } "password" { send "$password\n" } } interact |
加上之行权限,和不能使用bash xxx方式执行expect的脚本,会报错
1 2 | chmod + expect.sh ./expect.sh # 执行脚本 |
实例6:shell调用脚本expect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/bin/bash ip=$1 user=$2 password=$3 expect <<EOF # 开启expect命令多行重定向 set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n" ;exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } expect "]#" { send "echo centos |passwd --stdin hehe\n" } expect "]#" { send "exit\n" } expect eof # 结束语 EOF |
实例7:实现批量复制文件
1、先编辑IP地址清单
1 2 3 4 | [root@centos-7 opt]# cat iplist.txt 192.168.7.101 192.168.7.102 192.168.7.103 |
2、书写脚本读取IP地址清单内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #!/bin/bash while read ip; do user=root password=centos expect <<EOF set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n" ;exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } # 远程ssh登录后创建用户名 expect "]#" { send "echo centos |passwd --stdin hehe\n" } # 设置密码 expect "]#" { send "exit\n" } expect eof EOF done < iplist.txt |
实例8:根据不同用户名和密码传递公钥,实现免密钥登录
1、创建IP地址,密码清单
1 2 3 4 | [root@centos-7 opt]# cat iplist.txt 192.168.7.101 wangwang 192.168.7.102 centos 192.168.7.103 hahahaha |
2、通过while实现批量读取文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash ssh-keygen -t rsa -P "" -f /root/.ssh/id_rsa while read ip password; do user=root set timeout 10 expect << EOF spawn ssh-copy-id -i /root/.ssh/id_rsa.pub $user@$ip expect { "yes/no" { send "yes\n" ;exp_continue } "password" { send "$password\n" } } expect eof EOF done <iplist.txt |
实例9:基于function函数实现多台虚机部署服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/bin/bash IP=$(cat /data/mengze/ip.txt) deploy_code(){ for ip in $IP; do ssh $ip "[ ! -d " /data/mengze " ] && mkdir -p /data/mengze" ssh $ip "mv -f /usr/share/nginx/html/index.html /data/mengze/index.html_$1" scp /data/mengze/index.html $ip:/usr/share/nginx/html/index.html ssh $ip "systemctl restart nginx" done } rollback_code(){ for ip in $IP; do ssh $ip "unalias cp" ssh $ip "cp /data/mengze/index.html_$1 /usr/share/nginx/html/index.html " ssh $ip "systemctl restart nginx" done } case $1 in deploy) deploy_code $2 ;; rollback) rollback_code $2 ;; *) echo "please input right arguements!!!" esac |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架