第六章、函数和数组
6.1 函数
函数几乎是学习所有的程序设计语言时都必须过的一关。对于学习过其他的程序语言的用户来说,函数可能并不陌生。但是Shell中的函数与其他的程序设计语言的函数有许多不同之处。为了使用户了解Shell中的函数,本节将介绍函数的相关基础知识。
6.1.1 什么是函数
通俗地将,所谓函数就是将一组功能相对独立的代码集中起来,形成一个代码块,这个代码可以完成某个具体的功能。从上面的定义可以看出,Shell中的函数的概念与其他的语言的函数的概念并没有太大的区别。从本质上讲,函数是一个函数名到某个代码块的映射。也就是说,用户在定义了函数之后,就可以通过函数名来调用其所对应的一组代码。
使用shell函数优势
1、把相同的程序段定义为函数,可以减少整个程序段代码量,提升开发效率。
2、增加程序段可读性、易读性,提升管理效率。
3、可以实现程序功能模块化,使得程序具备通用性(可移植性)。
函数定义不会被执行,只有调用该函数才会被执行,定义在脚本文件最前面或者定义在函数调用前
函数和循环的区别:函数有传参和返回值,循环没有
函数一般可以执行相对功能,循环一般是对数据处理。
查看所有变量:set 请注意,set
命令不仅会列出环境变量,还会列出所有的 shell 变量
6.1.2 函数语法
1.
function 函数名() { 指令return
}
简化写法2:
function 函数名 { ————————————————————函数名和{之间必须要有空格指令return
}简化写法3:
函数名() {指令return
}
函数命名规则:字母,数字,下划线组成
6.1.3 函数的返回值
首先,用户可以使用return语句来返回某个数值,这与绝大部分的程序设计语言是相同的。但是,在Shell中,return语句只能返回某个0~255之间的整数值。
返回代码执行之后的状态值
函数执行时匹配到return[0-255]:结束函数的执行;匹配到exit:结束当前脚本
6.1.4 函数的调用
在Shell中,函数调用的基本语法如下: function_name param1 param2 … 在上面的语法中,function_name表示函数名称,其后面跟的param1、param2…表示函数的参数。
函数执行:
调用函数: 直接执行函数名即可。 函数名
带参数的函数执行方法: 函数名 参数
6.1.5函数定义以及调用函数的方式 0-255 0 1-255
1.函数的定义及调用
#function printMsg() { //--函数定义的三种语法
#function printMsg {
printMsg() {
# echo “hello shell” //静态赋值--默认有参数值,直接调用函数
@ echo $1 //默认没有参数值echo "$msg"
}
printMsg
printMsg $1 // 通过执行脚本调用函数时通过位置变量参入参数值
read -p " Please input str…" msg //函数调用通过变量传入参数值
printMsg $msg
--------------------------------------------------
注:[root@localhost /]# ./xx.sh hello world
Hello ---若传入一个位置参数值,值中间有空格则需要将“$1”用=引号引用。 传入的值“hello world”用引号无用。
注:函数三种定义方式如果有()大括号前有无空格无所谓,但第二种函数定义无小括号,大括号后面必须加至少一个空格
2.return关键字接收函数返回值默认返回值为0-255,如果是其他字符则会报语法错误。
#function printMsg() {
#function printMsg {
printMsg() {return 3 //--不写默认为0 return $1 return "$msg" //-表示打断并返回值所以echo指令不执行echo " hello shell"
}
printMsg
printMsg $1
read -p " Please input str…" msg
printMsg $msg
echo $?
-----------------------------------------------------------
注:打断程序接收函数返回值,返回值可以直接定义也可以调用函数传入返回值返回值范围0-255,输入其他字符报语法错误。“: line 3: return: msg: numeric argument required//
返回:msg:需要数字参数”、
第一种:1.无参数,无返回值
#ping1.sh
PING() { if ping -c 2 -w 0.2 -i 0.1 www.baidu.com &>/dev/null; then echo "up" else echo "down" fi
} PING
特殊
Msg (){
echo $1
}Msg $1[root@localhost ~]# bash zixi.sh hello world
hello
[root@localhost ~]# bash zixi.sh "hello world"
hello
在函数调用时传入的是一个位置变量参数,那么哪怕是 用双引号将多个空格连接的参数 它也识别的是多个参数。Msg (){
echo $1
}Msg "$1"[root@localhost ~]# bash zixi.sh "hello world"
hello world
Msg "$1"。引用变量时加上双引号
第二种:有参数,无返回值
#!/bin/bash
PING() {if $(ping $ip -c 2 -w 0.2 -i 0.1 &>/dev/null) ;thenecho "up"elseecho "down"fi
}
read -p "..." ip
PING $ip
第三种:无参数,有返回值
echo $?:查看命令是否执行成功,成功为0,失败为1
#!/bin/bash
PING() {if $(ping www.baidu.com -c 2 -w 0.2 -i 0.1 &>/dev/null) ;thenreturn 0elsereturn 1fi
}
PING
echo $?
第四种:有参数,有返回值
#!/bin/bash
PING() {if $(ping $ip -c 2 -w 0.2 -i 0.1 &>/dev/null) ;thenreturn 0elsereturn 1fi
}
read -p "..." ip
PING $ip
echo $?
函数案例
示例1:写一个脚本,判定192.168.0.200-192.168.0.254之间的主机哪些在线。
要求: 1、使用函数来实现一台主机的判定过程;
2、在主程序中来调用此函数判定指定范围内的所有主机的在线情况。
#直接使用函数实现(无参数,无返回值)
#!/bin/bash
#
PING() {for I in {200..254};doif ping -c 1 -W 1 192.168.0.$I &> /dev/null; thenecho "192.168.0.$I is up."elseecho "192.168.0.$I is down."fidone
}PING
#无参数有返回值
#!/bin/bash
PING() {if $(ping www.baidu.com -c 2 -w 0.2 -i 0.1 &>/dev/null) ;thenreturn 0elsereturn 1fi
}
PING
echo $?
if [ $? -eq 0 ];thenecho up
elsedown
fi
#使用函数传参(有参数,无返回值)
#!/bin/bash
#
PING() {if ping -c 1 -W 1 $1 &> /dev/null; thenecho "$1 is up."elseecho "$1 is down."fi
}for I in {200..254}; doPING 192.168.0.$I
done
#使用函数返回值判断(有参数,有返回值)
#!/bin/bash
#
PING() {if ping -c 1 -W 1 $1 &> /dev/null; thenreturn 0 elsereturn 1fi
}for I in {200..254}; doPING 192.168.0.$Iif [ $? -eq 0 ]; thenecho "192.168.0.$I is up."elseecho "192.168.0.$I is down."fi
done
示例2:写一个脚本:使用函数完成
1、函数能够接受一个参数,参数为用户名;
判断一个用户是否存在
如果存在,就返回此用户的shell和UID;并返回正常状态值;
如果不存在,就说此用户不存在;并返回错误状态值;
2、在主程序中调用函数;
示例代码:
#!/bin/bash
#
user () {
if id $1 &> /dev/null ;thenecho "`grep ^$1 /etc/passwd | cut -d: -f3,7`"return 0
elseecho "no $1"return 1
fi
}
read -p "please input username:" username
until [ $username == q -o $username == Q ]; douser $usernameif [ $? == 0 ];thenread -p "please input again:" usernameelseread -p "no $username,please input again:" usernamefi
done
6.1.6 函数库文件
为了方便地重用这些功能,可以创建一些可重用的函数。这些函数可以单独地放在函数库文件中。本节将介绍如何在Shell程序中创建和调用函数库文件。
函数库文件定义
创建一个函数库文件的过程非常类似于编写一个Shell脚本。脚本与库文件之间的唯一区别在于函数库文件通常只包括函数,而脚本中则可以既包括函数和变量的定义,又包括可执行的代码。此处所说的可执行代码,是指位于函数外部的代码,当脚本被载入后,这些代码会立即被执行,毋需另外调用。
函数库文件的调用
当库文件定义好之后,用户就可以在程序中载入库文件,并且调用其中的函数。在Shell中,载入库文件的命令为.,即一个圆点,其语法如下: . filename 其中,参数filename表示库文件的名称,必须是一个合法的文件名。
库文件可以使用相对路径,也可以使用绝对路径,另外,圆点命令和库文件名之间有一个空格。. f1.sh 或者 . /root/f1.sh source f1.sh
[root@localhost day]# cat f1.sh aaa.sh
PING() {if `ping -c 2 $ip &>/dev/null` ;thenecho $ip=upelse echo $ip=downfi
}
------------------------------------------·-
#!/bin/bash
. f1.sh
ip=www.baidu.com
PING $ip
Vim fun.sh
#!/bin/bash
user () {if id $1 &> /dev/null ;thenecho "`grep "^$1" /etc/passwd | cut -d: -f3,7`"return 10elseecho "no $1"return 1fi
}
user $1
------------------------------------------------
Vim fun3.sh
#!/bin/bash
read -p "please input username:" username
until [ $username == q -o $username == Q ]; do./fun.sh $usernameif [ $? == 10 ];thenread -p "please input again:" usernameelseread -p "no $username ;please input again:" usernamefi
done
#!/bin/bash
PING() {if $(ping $ip -c 2 -w 0.2 -i 0.1 &>/dev/null) ;thenreturn 0elsereturn 1fi
}
read -p "..." ip
PING $ip
echo $?
-------------------------------
#!/bin/bash
[ -f n.sh ] && . /n.sh
调用函数库文件
例子:函数配置nginx服务
[root@localhost ~]# cat day6.sh
nginx_service() {cat <<EOF >/etc/nginx/conf.d/day6.conf
#默认写入文件位于行首的话就不要加缩进
server {listen 192.168.111.130:8909;root /day6;}
EOFmkdir /day6echo this is test > /day6/index.htmlsystemctl restart nginxif [ $? -eq 0 ];thencurl http://192.168.111.130:8909elseecho "服务启动失败"cat /etc/nginx/conf.d/day6.conffi
}[root@localhost ~]# cat day6_1.sh
#只能用./filename或者source运行
source day6.shif ` command -v nginx &> /dev/null`;thennginx_service
elsemount /dev/sr0 /mnt &> /dev/nulldnf install nginx -y &> /dev/nullif [ $? -eq 0 ];thennginx_service elseecho nginx安装失败fi
fi
[root@localhost ~]# bash -n day6_1.sh ------语法检测
[root@localhost ~]# bash day6_1.sh
mkdir: 无法创建目录 “/day6”: 文件已存在
this is test
会出现的问题:显示之前网页的内容
解决方法:使用多IP访问多网站或者多端口访问多网站
查看IP:
[root@localhost ~]# hostname -I
192.168.111.130
6.1.7 递归函数
Linux的Shell也支持函数的递归调用。也就是说,函数可以直接或者间接地调用自身。在函数的递归调用中,函数既是调用者,又是被调用者。作为一个Shell函数介绍的补充内容,本节将介绍如何在Shell中实现递归函数。 递归函数的调用过程就是反复地调用其自身,每调用一次就进入新的一层。
递归求阶乘示例:
fact() {
#被乘数num 5 4 3 2 5*4*3*2local num=$1local fac #累乘结果#当被乘数为1时直接输出递乘为1.if ((num==1));thenfac=1
#若果大于一,则被乘数递减为乘数else((dec=num-1)) 4 3 2 1fact $dec #递归函数--#循环次数 4 3 2 1fac=$? #1 5 20 60 120fac=`expr $num \* $fac` #5*1=5 4*5 3*20 2*60=120 1*120fireturn $fac #5 20 60 120
}
fact $1
echo $?
-------------------------------------
res=1
read -p "请输入数值" n
for ((i=1;i<=n;i++));do #i=1echo $ilet res*=i #res=res*i
done
echo $res
-
递归遍历目录
#多层级路径文件类型判断 [root@localhost ~]# cat digui.sh #read -p "请输入要判断的文件路径:" dir #for file in ` ls $dir`;do # if [ -f $dir/$file ];then # echo $dir/$file是文本文件 # else # echo $dir/$file是目录文件 # for file1 in `ls $dir/$file`;do # if [ -f $dir/$file/$file1 ];then # echo $dir/$file/$file1是文本文件 # else # echo $dir/$file/$file1是目录文件 # fi # # done # fi #donefile_type(){for file1 in `ls $1`;do if [ -f $1/$file1 ];then echo $1/$file1是文本文件elseecho $1/$file1是目录文件file_type $1/$file1fidone} file_type $1 [root@localhost ~]# bash digui.sh /day6 /day6/d1是目录文件 /day6/d1/t1是目录文件 /day6/d1/t2是目录文件 /day6/d1/t3是目录文件 /day6/d2是目录文件 /day6/d2/t1是目录文件 /day6/d2/t2是目录文件 /day6/d2/t3是目录文件 /day6/d3是目录文件 /day6/d3/t1是目录文件 /day6/d3/t2是目录文件 /day6/d3/t3是目录文件 /day6/index.html是文本文件
#!/bin/bashread -p "请输入遍历路径" pathfor var in `ls $path`;doif [ -d $path/$var ];thenecho "-d $path/$var"for var1 in `ls $path/$var`;doif [ -d $path/$var/$var1 ];thenecho -d $path/$var/$var1elseecho "- $path/$var/$var1"fidoneelseecho "- ${var}"fidone#遍历深层次文件#/test/1/2/3#函数在他的函数体内调用他自身称为递归调用,没执行一次进入新的一层。#递归定义式:ls /test ls /test/1 ls /test/1/2 ls /test/1/2/3#终止条件当多级结构下没有文件是终止read -p "请输入遍历路径" pathlist() {for var in `ls $1`;doif [ -d $1/${var} ];thenecho d $varlist "$1/$var"elseecho "- ${var}"fidone}list ${path}
#打印100-1 function show() {if test $1 -eq 0;thenreturnfiecho $1show `expr $1 - 1` } show 100
笔试题经常考:
.(){ .|.& };. 13个字符,递归死循环,可耗尽系统资源代码解析: .() 定义一个名字为 . 的函数 { 函数块开始 .|.& 在后台递归调用函数 . } 函数块结束 ; 与下一条执行语句隔开 . 再次调用函数[root@localhost ~]# .(){ .|.& };.
[1] 8412
[root@localhost ~]#
[1]+ 已完成 . | .
-f -d -L -p -S -b -c -r -w -xls -F
* 代表[可执行文件](https://so.csdn.net/so/search?q=可执行文件&spm=1001.2101.3001.7020)
/ 代表目录
@ 代表链接文件
| 代表管道文件
= 代表套接字
\> 代表进程间通讯设备
6.2 数组
有序列的元素列表,将有限个相同类型的变量集合命名,数组中各个变量称为元素或分量或下标变量;用来区分各个元素的数字标号称为下标。----数组用户存放多个相同类型数据的集合。
Shell语言对于数组的支持非常强大。在Shell中,用户可以通过多种方式来创建一个数组。为了能够使读者充分了解数组的创建方法,本节将介绍其中最常用的几种数组定义方法。
6.2.1 定义数组
有了a变量后,还可以创建一个a数组,但默认保存数组
方法一:用小括号将变量值括起来赋值给数组变量,每个变量之间要用空格进行分隔。
array=(value1 value2 value3 … )
方法二:用小括号将变量值括起来,同时采用键值对的形式赋值。
array=([1]=one [2]=two [3]=three)
方法三:通过分别定义数组变量方法。
array[0]=a;array[1]=b;array[2]=c
方法四:动态的定义变量,并使用命令的输出结果作为数组的内容。
array=(命令
)
array=(1 2 3 4 5)
array2=([0]=1 [1]=2 [2]=3 [3]=4 [4]=5)
array3[0]=1
array3[1]=2
array3[2]=3
array4=(`seq 5`)
array5=(`ls *.sh`)
array6=($@)
6.脚本中通过read给数组赋值
i=0
while [ $i -le 10 ];doecho -n "请输入你的名字" read name[i]let i++
done
echo ${name[@]}#while read arr5[i]
#do
# echo ${arr5[i]}
#
# let i++
#done < /filefor v in `seq 1 3`
doread -p "输入元素" arr6[i]let i++
done
echo ${arr6[@]}
定义关联数组: 申明关联数组
普通数组的下标只能是整数,而关联数组的下标是字符串。当然也可以使用数字作为下标
关联数组虽然大部分操作类似普通数组,但是其实它不是数组,而是字典,里面存储着键值对,且键的顺序不是按自然顺序排列的。
申明关联数组变量
# declare -A ass_array1
# declare -A ass_array2
方法一: 一次赋一个值
数组名[索引]=变量值
# ass_array1[index1]=pear
# ass_array1[index2]=apple
# ass_array1[index3]=orange
# ass_array1[index4]=peach
方法二: 一次赋多个值
# ass_array2=([index1]=tom [index2]=jack [index3]=alice [index4]='bash shell')查看数组:
declare -A ass_array1='([index4]="peach" [index1]="pear" [index2]="apple" [index3]="orange" )'
declare -A ass_array2='([index4]="bash shell" [index1]="tom" [index2]="jack" [index3]="alice" )'访问数组元数:# echo ${ass_array2[index2]}
访问数组中的第二个元数
# echo ${ass_array2[@]}
访问数组中所有元数 等同于 echo ${array1[*]}
# echo ${#ass_array2[@]}
获得数组元数的个数
# echo ${!ass_array2[@]}
获得数组元数的索引
用函数给数组赋值
[root@localhost ~]# cat shuzu.sh
for i in 1 2 3;doread arr[$i]
done
echo 显示数组元素(下标变量值)${arr[*]}
echo 显示数组的下标值 ${!arr[*]}
echo 显示数组元素个数 ${#arr[@]}[root@localhost ~]# bash shuzu.sh
5
9
6
显示数组元素(下标变量值)5 9 6
显示数组的下标值 1 2 3
显示数组元素个数 3
6.2.2 数组操作
获取所有元素:
# echo ${array[*]} # *和@ 都是代表所有元素
获取元素下标:!
# echo ${!array[@]}
获取数组长度: #
# echo ${#array[*]}
获取第一个元素:
# echo ${array[0]}
添加元素:
# array[3]=d
添加多个元素:
# array+=(e f g)
删除第一个元素:
# unset array[0] # 删除会保留元素下标
删除数组:
# unset array
遍历数组:
方法 1:使用数组元素索引
#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for ((i=0;i<${#IP[*]};i++)); doecho ${IP[$i]}
done
# bash test.sh
192.168.1.1
192.168.1.2
192.168.1.3方法 2:使用数组元素个数
#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for IP in ${IP[*]}; do
echo $IP
done
6.2.3 数组案例
1、从“标准输入”读入n次字符串,每次输入的字符串保存在数组array里
i=0n=5while [ "$i" -lt $n ] ; doecho "Please input strings ... `expr $i + 1`"read array[$i]let i++doneecho ${array[@]}
2、将字符串里的字母逐个放入数组,并输出到“标准输出”
chars='abcdefghijklmnopqrstuvwxyz'for (( i=0; i<${#chars}; i++ )) ; doarray[$i]=${chars:$i:1}echo ${array[$i]}done${chars:$i:1},表示从chars字符串的 $i 位置开始,获取 1 个字符
3、把1-3 3个数字存到数组里 分别乘以8 然后依次输出。
#!/bin/basharray1=(`seq 3`)for ((i=0;i<${#array1[@]};i++))doecho $[${array1[$i]}*8] done
练习:
1、编写函数,实现打印绿色OK和红色FAILED判断是否有参数,存在为Ok,不存在为FAILED
2、编写函数,实现判断是否无位置参数,如无参数,提示错误
3、编写函数实现两个数字做为参数,返回最大值
4、编写函数,实现两个整数位参数,计算加减乘除。
5、将/etc/shadow文件的每一行作为元数赋值给数组
6、使用关联数组统计文件/etc/passwd中用户使用的不同类型shell的数量
7、使用关联数组按扩展名统计指定目录中文件的数量
grep [option...] 'pattern' [FILENAME]
参数
-n :显示行号
-o :只显示匹配的内容
-q :静默模式,没有任何输出,得用$?来判断执行成功没有,即有没有过滤到想要的内容
-l :如果匹配成功,则只将文件名打印出来,失败则不打印,通常-rl一起用,grep -rl 'root' /etc
-A :如果匹配成功,则将匹配行及其后n行一起打印出来
-B :如果匹配成功,则将匹配行及其前n行一起打印出来
-C :如果匹配成功,则将匹配行及其前后n行一起打印出来
--color
-c :如果匹配成功,则将匹配到的行数打印出来
-E :等于egrep,扩展
-i :忽略大小写
-v :取反,不匹配
-w:匹配单词
-x 仅选择与整行完全匹配的匹配项。精确匹配每行内容(包括行首行尾看不到的空格内容)
-R -r以递归方式读取每目录下的文件
指定过滤器
--exclude-dir= 指定过滤目录,排除目录顾虑选择
[root@node1 ~]# grep -rl 'aaa' /path --exclude-dir=2 (注:直接写子路径)
--exclude-from=file指定过滤器文件,通过文件内容指定要排除的文件名
总结: shell脚本异常处理
set 异常处理
set -u 检测脚本中变量是否未定义,未定义终止脚本----------------------------------------------
set -e bug模式开启相当于bug打断点,当脚本遇到返回值非零的情况,就错误退出,不会继续执行
(1)
set -e
cat aaa.sh
echo 1
(2)
set -e
cat aaa.sh | echo "123" #注全部执行,原因是cat文件不存在失败,但|echo 正确,最终第一条命令的返回结果为0,所以代码全部运行
echo success
对于set -e 检测不准精确可以通过 set -o pipefail 解决,管道后的命令优先管道前的命令所以返回值是管道前的命令的返回值
(3)
set -e
set -o pipfail
cat aaa.sh | echo "123" #注全部不执行
---------------------------------------
set -x set +x 仅显示指定范围内代码是否成功执行,显示执行过程