Shell进阶(三) 交互式脚本 函数 数组 分片 字符串处理

1.函数介绍
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于:
Shell程序在子Shell中运行
而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改
查看系统所有函数命令:declare -f
查看func 1: declare -f func1
函数在当前shell里生效

2.定义函数
函数由两部分组成:函数名和函数体
help function
语法一(最简洁):
f_name (){
…函数体…
}
语法二:
function f_name {
…函数体…
}
语法三(太啰嗦):
function f_name () {
…函数体…
}
示例:
1. 测试函数,打印test function
func1 ()
{
echo test function
}
2.匿名函数:
false || {ls;exit;}
echo hello
发现么有打印hello,也没有定义函数名,但是它确实是执行了

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

函数里的变量建议加上local(局部变量,函数结束时自动销毁)

4.脚本中的函数return和exit的区别
return只是退出当前函数内的命令
exit 则结束退出整个脚本的命令。

5.引用函数的方法:
. funcname 或 source funcname
常用系统函数 /etc/init.d/functions

  1. /etc/init.d/functions里面的action函数,用来打印绿色[ OK ]或者红色[ FAILED ]
    用例,引用 action打印的函数
    . /etc/init.d/functions
    action “rm -rf /test” true
    action “rm -rf /test” false

7.函数返回值
函数有两种返回值:
函数的执行结果返回值:
(1) 使用echo等命令进行输出
(2) 函数体中调用命令的输出结果
函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回。
return 1-255 有错误返回

8.交互式环境下定义和使用函数
示例:
dir() {

ls -l
}
定义该函数后,若在$后面键入dir,其显示结果同ls -l的作用相同
dir
该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令
unset dir

9.在脚本中定义及使用函数(最好把函数写成文件)
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用
调用函数仅使用其函数名即可
示例:
cat func1

!/bin/bash

func1

hello()
{
echo “Hello there today’s date is date +%F
}
echo “now going to the function hello”
hello
echo “back from the function”

10.使用文件定义函数
可以将经常使用的函数存入函数文件,然后将函数文件载入shell
文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

11.创建函数文件 可以不用#!/bin/bash “sherbang”机制
函数文件示例:
cat functions.main

!/bin/bash

functions.main

findit()
{
if [ # -lt 1 ] ; then  echo “Usage:findit file”  return 1  fi  find / -name 1 –print
}

12.载入函数
函数文件已创建好后,要将它载入shell
定位函数文件并载入shell的格式
. filename 或 source filename
注意:此即<点> <空格> <文件名>
这里的文件名要带正确路径
示例:
上例中的函数,可使用如下命令:
. functions.main

13.检查载入函数
使用set命令检查函数是否已载入。set命令将在shell中显示所有的载入函数
示例:
set
findit=( )
{
if [ # -lt 1 ]; then  echo “usage :findit file”;  return 1  fi  find / -name 1 -print
}

14.执行shell函数
要执行函数,简单地键入函数名即可
示例:
findit groups
/usr/bin/groups
/usr/local/backups/groups.bak

15.删除shell函数
现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数
命令格式为:
unset function_name
示例:
unset findit
再键入set命令,函数将不再显示
环境函数
使子进程也可使用
声明:export -f function_name
查看:export -f 或 declare -xf
示例:
在当前脚本调用functions文件里的func1 func2,传递给f1.sh,让f1.sh也可以调用该函数
. functions
declare -xf func1 func2
func1 1 2
./f1.sh

16.函数变量
变量作用域:
环境变量:当前shell和子shell有效
本地变量(普通变量):只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
局部变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有局部变量,如果其名称同本地变量,使 用局部变量
在函数中定义局部变量的方法
local NAME=VALUE

17.函数递归示例
函数递归:
函数直接或间接调用自身
注意递归层数
递归实例:
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!
n!=1×2×3×…×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)…1
n(n-1)! = n(n-1)(n-2)!

示例:
fact.sh

!/bin/bash

#
fact() {
if [ 1 e q 0 o 1 -eq 1 ]; then
echo 1
else
echo [ 1* ( f a c t [ 1-1])]  fi  }  fact 1

18.fork炸弹
fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
函数实现
示例
1. :(){ :|:& };:
2. bomb() { bomb | bomb & }; bomb

脚本实现
cat Bomb.sh

!/bin/bash

./ 0 | . / 0&

练习
1.编写函数,实现OS的版本判断

2.编写函数,实现取出当前系统eth0的IP地址

3.编写函数,实现打印绿色OK和红色FAILED

4.编写函数,实现判断是否无位置参数,如无参数,提示错误

5.编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”
考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start
考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running…”
如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped…”
其中:SCRIPT_NAME为当前脚本名
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理

6.编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下
如:/bin/bash ==> /mnt/sysroot/bin/bash
/usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

7.编写函数实现两个数字做为参数,返回最大值

8.斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)
利用函数,求n阶斐波那契数列

9.汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤

19.信号捕捉trap
trap ‘触发指令’ 信号
自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作
trap ” 信号
忽略信号的操作
trap ‘-’ 信号
恢复原信号的操作
trap -p
列出自定义信号操作

trap示例

!/bin/bash

trap ‘echo “signal:SIGINT”’ int
trap -p
for((i=0;i<=10;i++))
do
sleep 1
echo i d o n e t r a p i n t t r a p p f o r ( ( i = 11 ; i <= 20 ; i + + ) ) d o s l e e p 1 e c h o i
done
trap ‘-’ int
trap -p
for((i=21;i<=30;i++))
do
sleep 1
echo $i
done


1.数组
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
索引(下标):编号从0开始,属于普通数组索引
注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联数组索引,bash4.0版本之后开始支持
bash的数组支持稀疏格式(索引不连续)
声明数组:
declare -a ARRAY_NAME
declare -A ARRAY_NAME: 关联数组
注意:两者不可相互转换

2.数组赋值
数组元素的赋值
(1) 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
weekdays[0]=”Sunday”
weekdays[4]=”Thursday”
(2) 一次赋值全部元素
ARRAY_NAME=(“VAL1” “VAL2” “VAL3” …)
(3) 只赋值特定元素
ARRAY_NAME=([0]=”VAL1” [3]=”VAL2” …)

(4) 交互式数组值对赋值
read -a ARRAY
显示所有数组:declare -a
只要能生成列表的方法都可以给数组赋值
例如:
1.digit=({1..10})
echo ${digit[*]}
2.alpha=(seq 10 20)

3.引用数组
引用数组元素:
A R R A Y N A M E [ I N D E X ] [ I N D E X ] 0 {ARRAY_NAME[*]}
A R R A Y N A M E [ @ ] ( ) {#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
删除数组中的某元素:导致稀疏格式
unset ARRAY[INDEX]
删除整个数组:
unset ARRAY

示例:1.df.sh,当磁盘利用率大于5%的时候,显示报警 待完成

4.数组数据处理
引用数组中的元素:
数组切片: A R R A Y [ @ ] : o f f s e t : n u m b e r o f f s e t : n u m b e r : {ARRAY[@]:offset}
向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value
关联数组:
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]=’val1’ [idx_name2]=’val2‘…)
注意:关联数组必须先声明再调用

示例:1.rand.sh 十个随机数,比较最大和最小值,打印出来
写法一老王思路:
declare -a rand
for ((i=0;i<10;i++));do
rand[ i ] = RANDOM #第一个数即是最大值又是最小值
if [ i e q 0 ] ; t h e n m a x = {rand[ i]}  min= max
continue
fi
if [ “ m a x " l t " r a n d [ i]” ];then
max= r a n d [ $ i ] e l i f [ min" -gt r a n d [ $ i ] ] ; t h e n m i n = {rand[ i]}  else  true  fi  done  echo ALLrandom is &{rand[@]}  echo max= max
echo min=$min

写法二:
生成10个随机数保存于数组中,并找出其最大值和最小值

!/bin/bash

declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[ i ] = RANDOM
[ i -eq 0 ] && min= {nums[ i]} && max= {nums[ i]}&& continue  [ {nums[ i]} -gt max ] && max= n u m s [ $ i ] [ {nums[ i]} -lt min ] && min= n u m s [ $ i ] d o n e e c h o A l l n u m b e r s a r e {nums[*]}”
echo Max is m a x e c h o M i n i s min

2.编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和

!/bin/bash

#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in ( s e q 0 [ {#files[*]}-1]); do  if [ [ i l e t l i n e s + = (wc -l f i l e s [ $ i ] | c u t d f 1 ) f i d o n e e c h o L i n e s : lines.”

练习
输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序
将下图所示,实现转置矩阵matrix.sh
1 2 3 1 4 7
4 5 6 ===> 2 5 8
7 8 9 3 6 9
打印杨辉三角形

5.字符串切片
${#var}:返回字符串变量var的长度

v a r : o f f s e t : v a r o f f s e t o f f s e t o f f s e t 0 {#var}-1 之间(bash4.2后,允许为负值)

${var:offset:number}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分

${var: -length}:取字符串的最右侧几个字符
注意:冒号后必须有一空白

${var:offset:-length}:从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容

${var: -length:-offset}:先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容 -length > -offset ,倒取右边length,再从length里抛掉右侧offset,保留剩下的
注意:-length前空格

6.字符串处理
基于模式取子串
{var#*word}:其中word可以是指定的任意字符  功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符 {var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,可以取基名
示例:
file=“var/log/messages”
{file#*/}: log/messages {file##*/}: messages

7.字符串处理
{var%word*}:其中word可以是指定的任意字符  功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符之间的所有字符  file=”/var/log/messages” {file%/*}: /var/log
${var%%word*}:同上贪婪模式,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;
示例:
url=http://www.magedu.com:80
{url##*:} 80 {url%%:*} http

8.字符串处理-查找替换
查找替换
v a r / p a t t e r n / s u b s t r v a r p a t t e r n s u b s t r {var//pattern/substr}: 贪婪模式 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
{var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之 {var/%pattern/substr}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之

9.字符串处理-查找删除–大小写转换
查找并删除
v a r / p a t t e r n v a r p a t t e r n {var//pattern}:删除var表示的字符串中所有被pattern匹配到的字符串
{var/#pattern}:删除var表示的字符串中所有以pattern为行首匹配到的字符串 {var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
字符大小写转换
{var^^}:把var中的所有小写字母转换为大写 {var,,}:把var中的所有大写字母转换为小写

10.变量赋值

11.高级变量用法-有类型变量
Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的
declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数
-l 声明变量为小写字母 declare –l var=UPPER
-u 声明变量为大写字母 declare –u var=lower

12.eval命令
eval命令将会首先扫描命令行进行所有的先置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量.该命令对变量进行两次扫描
示例:
[root@server ~]# CMD=whoami
[root@server ~]# echo CMD  whoami  [root@server ~]# eval CMD
root
[root@server ~]# n=10
[root@server ~]# echo {0.. n}    #echo 根本不执行 n,只是置换变量值
{0..10}
[root@server ~]# eval echo {0..$n} #加上eval就不一样了,先替换再执行
0 1 2 3 4 5 6 7 8 9 10

13.间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为
variable1=variable2
variable2=value

bash Shell提供了两种格式实现间接变量引用
eval tempvar=$ v a r i a b l e 1 t e m p v a r = {!variable1}
示例:
1.
[root@server ~]# N=NAME
[root@server ~]# NAME=wangxiaochun
[root@server ~]# N1= {!N}  [root@server ~]# echo N1
wangxiaochun
[root@server ~]# eval N2=$ N  [root@server ~]# echo N2
wangxiaochun

2.
title=ceo
ceo=mage
eval echo $ t i t l e m a g e e c h o {!title}

14.创建临时文件
mktemp命令:创建并显示临时文件,可避免冲突
mktemp [OPTION]… [TEMPLATE]
TEMPLATE: filenameXXX
X至少要出现三个
OPTION:
-d: 创建临时目录
-p DIR或–tmpdir=DIR:指明临时文件所存放目录位置
示例:
mktemp /tmp/testXXX
tmpdir=mktemp –d /tmp/testdirXXX
mktemp –tmpdir=/testdir testXXXXXX

15.安装复制文件
复制文件,如果目标文件夹没有可以自动创建,还可以改权限
install命令:
install [OPTION]… [-T] SOURCE DEST 单文件
install [OPTION]… SOURCE… DIRECTORY
install [OPTION]… -t DIRECTORY SOURCE…
install [OPTION]… -d DIRECTORY…创建空目录
选项:
-m MODE,默认755
-o OWNER
-g GROUP
示例:
install -m 700 -o wang -g admins srcfile desfile
install –m 770 –d /testdir/installdir

16.expect介绍
expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率

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中相关命令
spawn:启动新的进程
send:用于向进程发送字符串
expect:从进程接受字符,捕获输入到感兴趣字符后,执行后边的命令
interact:允许用户交互
exp_continue 匹配多个字符串在执行动作后加此命令

用法:
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:
expect “hi” {send “You said hi\n”}
匹配到hi后,会输出“you said hi”,并换行
多分支模式语法:
expect “hi” { send “You said hi\n” } \
“hehe” { send “Hehe yourself\n” } \
“bye” { send “Good bye\n” }

匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
expect {
“hi” { send “You said hi\n”}
“hehe” { send “Hehe yourself\n”}
“bye” { send “Good bye\n”}
}

示例
1.远程登录scp传文件,全自动

!/usr/bin/expect

spawn scp /etc/fstab 192.168.8.100:/app
expect {
“yes/no” { send “yes\n”;exp_continue }
“password” { send “magedu\n” }
}
expect eof

2.远程登录,允许用户交互

!/usr/bin/expect

spawn ssh 192.168.8.100
expect {
“yes/no” { send “yes\n”;exp_continue }
“password” { send “magedu\n” }
}
interact

expect eof

3.用变量表示IP

!/usr/bin/expect

set ip 192.168.8.100
set user root
set password magedu
set timeout 10
spawn ssh u s e r @ ip
expect {
“yes/no” { send “yes\n”;exp_continue }
“password” { send “$password\n” }
}
interact

4.把IP、用户名、密码变量做成数组

!/usr/bin/expect

set ip [lindex a r g v 0 ] s e t u s e r [ l i n d e x argv 1]
set password [lindex a r g v 2 ] s p a w n s s h user@ ip  expect {  “yes/no” { send “yes\n”;exp_continue }  “password” { send “ password\n” }
}
interact

./ssh3.exp 192.168.8.100 root magedu

5.执行多个命令

!/usr/bin/expect

set ip [lindex a r g v 0 ] s e t u s e r [ l i n d e x argv 1]
set password [lindex a r g v 2 ] s e t t i m e o u t 10 s p a w n s s h user@ ip  expect {  “yes/no” { send “yes\n”;exp_continue }  “password” { send “ password\n” }
}
expect “]#” { send “useradd haha\n” }
expect “]#” { send “echo magedu |passwd –stdin haha\n” }
send “exit\n”
expect eof

./ssh4.exp 192.168.8.100 root magedu

6.:shell脚本调用expect

!/bin/bash

ip= 1 u s e r = 2
password=$3
expect <

./ssh5.sh 192.168.8.100 root magedu

猜你喜欢

转载自blog.csdn.net/weixin_40647174/article/details/82532607
今日推荐