前言
之前写过一篇关于shell脚本流程控制总结,这次继续写关于shell脚本的问题。本篇文章主要包含shell脚本中的函数以及数组的用法介绍。同时也涵盖了一些字符串处理以及shell脚本比较使用的小工具的介绍。
函数
函数function是由若干条shell命令组成的语句块,实现代码重用和模块话编程。
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分。
函数shell程序比较相似,区别在于:
shell程序在shell中运行
而shell函数在当前shell中运行。因此在当前shell中,函数可以对shell中变量进行修改。
定义函数
函数由两部分组成:函数名和函数体
语法一:
function f_name {
...函数体...
}
语法二:
function f_name () {
...函数体...
}
#语法三的写法相对简单,并且系统中的函数一般也使用第三种写法,所以简易使用第三中写法。
语法三:
f_name (){
...函数体...
}
函数返回值
函数有两种返回值:
- 函数的执行结果返回值:
(1)使用echo等命令进行输出
(2)函数中调用命令的输出结果 - 函数的退出状态码
(1)默认取决于函数中执行的最后一条命令的退出状态码
(2)自定义退出状态码,其格式为,:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255有错误返回
函数的使用
- 可以在交互式环境下定义函数
- 可将函数放在脚本文件中作为他的一部分
- 可以单独写在一个文件中功能模块化,使用的时候单独调用
交互式环境使用函数
交互式环境下别名的优先级高于函数,如果函数的名字与别名相同,那么优先运行别名。但是一般函数只在脚本中使用,而脚本中别名不生效,所以不用考虑这个问题。
declear -f 查看所有定义过的函数。
交互式环境下函数会一直保留到用户从系统中退出,或者unset funcname 清除。
调用:函数只有被调用才会执行
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码。
函数的声明周期:被调用时创建,返回时终止
函数的定义会影响当前shell环境,使用local 声明函数定义的内容只在函数内部生效
当函数要定义一个数字时使用declare -i 声明要定义的函数是一个数字,声明的同时也会附带声明函数的本地效果,既只在函数内部生效。加参数-g再次声明为全局变量:declare -ig
[root@CentOS6 ~]#name=wang
[root@CentOS6 ~]#func1 () { name=mage; echo $name ; }
[root@CentOS6 ~]#func1
mage
[root@CentOS6 ~]#echo $name
mage
[root@CentOS6 ~]#name=wang
[root@CentOS6 ~]#func1 () { local name=mage; echo $name ; }
[root@CentOS6 ~]#echo $name
wang
在脚本中定义及使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始本分,直至shell首次发现它后才能使用。
调用函数仅仅使用其函数名即可。
函数变量
变量作用域:
环境变量:当前shell和子shell有效
本地变量:只在当前shell进程有效,为执行脚本会启动 专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
局部变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有局部变量,如果其名称同本地变量,使 用局部变量
在函数中定义局部变量的方法 local NAME=VALUE
[root@CentOS6 bin]#vim testfunc.sh
#!/bin/bash
func1 () {
echo "func1 is running" #定义函数执行echo命令
local funcvar="i am func1" #函数内定义本地变量
echo $funcvar #函数内显示本地变量
}
echo func1 will start...
func1 #调用函数
echo func1 is finished
echo "funcvar is $funcvar" #函数外显示变量
"testfunc.sh" 20L, 500C written
[root@CentOS6 bin]#testfunc.sh
func1 will start...
func1 is running
i am func1 #函数内显示函数本地变量
func1 is finished
funcvar is #函数外显示变量为空
#函数中使用变量一般使用local关键字定义本地变量,否则容易影响脚本中的变量。
使用函数文件
可以将经常使用的函数存入函数文件,然后将函数文件载入shell,这样一些常用的函数就不用每次用到都要重新去写了。
函数文件的文件名可任意选取,但最后是与相关的任务有联系,例如:functions.main
一旦函数文件载入shell,就可以在命令行或者脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包含已经载入shell的所有函数。
若要改动函数,首先用unset命令将函数从shll中删除。改动完毕后,在重新载入函数文件。
创建函数文件:
函数文件示例:
[root@CentOS6 bin]#cat functions.main
#!/bin/bash
# Filename : functions.main
#---------------+--------------------------------------+
findit () {
if [ $# -lt 1 ] ;then
echo "Usage:findit file"
return 1
fi
find / -name $1 -print
}
载入函数文件:
函数文件创建好之后,要将他载入shell中
定位函数文件,并载入shell的格式为:
点 空格 文件名:. filename
或 source 空格问价名:source filename
文件名要带正确路径
[root@CentOS6 ~]#. /etc/rc.d/init.d/functions #系统自带的函数
[root@CentOS6 ~]#action "success"
success [ OK ]
删除shell函数
现在对函数做一些改动后,需要先删除函数,使其对shell不可用, 使用unset命令完成删除函数
命令格式为:
unset function_name
[root@CentOS6 bin]#unset findit
[root@CentOS6 bin]#findit
-bash: findit: command not found
环境函数
当前进程导入的函数不能传给子进程
声明函数为环境函数使子进程也可以使用
声明:export –f function_name
查看:export -f 或 declare -xf
函数参数
函数可以接受参数:
传递参数给函数:调用函数时在函数名后面以空白分隔给定参数列表即可:例如“testfunc arg1 arg2 ...”
在函数体中当中,可使用$1, $2, ...调用这些参数;还 可以使用$@, $*, $#等特殊变量
[root@CentOS6 bin]#vim testfunc2.sh
#!/bin/bash
func1 () {
echo "func1 1st var is $1"
echo "func1 2st var is $2"
echo "func1 3st var is $3"
echo "the func1var is $*"
}
func1 func{$1,$2,$3}
echo "the scriptvar is $*"
[root@CentOS6 bin]#testfunc2.sh x y z
func1 1st var is funcx
func1 2st var is funcy
func1 3st var is funcz
the func1var is funcx funcy funcz
the scriptvar is x y z
函数递归示例
函数递归:
函数直接或间接调用自身
注意递归层数
一般循环可以解决的问题不用递归
递归实例:
阶乘是基斯顿·卡曼于 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)!
[root@CentOS6 bin]#vim n\!.sh
#!/bin/bash
fact () {
if [ $1 -eq 1 -o $1 -eq 0 ] ; then
echo 1 #1和0的阶乘都是1
else echo $[$1*`fact $[$1-1]`]
fi
}
fact $1
fork炸弹
fork炸弹是一种恶意程序,它的内部是一个不断在fork进程 的无限循环,实质是一个简单的递归程序。由于程序是递归 的,如果没有任何限制,这会导致这个简单的程序迅速耗尽 系统里面的所有资源
函数实现
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb
脚本实现
cat Bomb.sh
#!/bin/bash
./$0|./$0&
数组
数组的作用以及意义与变量类似
变量:储存单个元素的内存空间。
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引:
索引:编号从0开始,属于数值索引
注意:数组索引支持使用自定义格式,即为关联所索引,bash4.0版本之后开始支持
bash的数组支持稀疏格式(索引不连续)
声明数组:
declare -a ARRAY_NAME
declare -A ARRAY_NAME: 关联数组
注意:关联数组和一般数组之间不可相互转换
数组元素的赋值:
- 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
weekdays[0]="Sunday"
weekdays[4]="Thursday" #双引号可以不加,但是如果元素中含有空格需要双引号 - 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
[root@CentOS6 ~]#title=(boss ceo coo cto opt)
[root@CentOS6 ~]#echo ${title[*]}
boss ceo coo cto opt
数组的复制支持列表格式和seq命令
- 只赋值特定元素 (稀疏格式)
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...) - 交互式数组值对赋值
read -a ARRAY
显示所有数组:declare -a
引用数组
引用数组元素:
${ARRAY_NAME[INDEX]}
注意:省略[INDEX]表示引用下标为0的元素
引用数组所有元素:
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
数组的长度(数组中元素的个数):
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
数组元素个数减一等于最末端数组元素下标
遍历数组:
[root@CentOS6 ~]#for i in `seq 0 $[${#title[*]}-1]` ;do echo title[$i]=${title[$i]}; done
title[0]=boss
title[1]=ceo
title[2]=coo
title[3]=cto
title[4]=opt
删除数组中的某元素:导致稀疏格式
unset ARRAY[INDEX]
删除整个数组:
unset ARRAY
数组处理
引用数组中的元素
数组切片: ${ARRAY[@]:offset:number}
offset:要跳过的元素个数
number:要取出的元素个数
取偏移量之后的所有元素:
${ARRAU[@]:offset}
向元素中追加元素
元素个数减一,是数组中最末端的元素的下标,所以在确保数组不是稀疏格式的前提下,要想数组中追加的元素的下标等于当前数组的数量。
ARRY[${#ARRY[*]}]=vale
关联数组
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用
练习
- 生成十个随机数保存在数组中,并找出其最大值和最小值
[root@CentOS6 bin]#vim max.sh
#!/bin/bash
declare -i min max #声明max min都是数值
declare -a digit #声明数组
for ((i=0;i<10;i++));do #引入变量I初始值0,循环十次,每次循环以后自增1
digit[$i]=$RANDOM #取随机数赋值给数组元素
[ $i -eq 0 ] && max=${digit[$i]} && min=${digit[$i]} && continue
#每次循环都给最大值和最小值重新赋值,第一次循环最大最小值都是digit[i]并且continue跳过本次循环
[ ${digit[$i]} -gt $max ] && max=${digit[$i]}
#如果digit[$i]大于当前最大值$max时,就将digit[$i]重新付给max
[ ${digit[$i]} -lt $min ] && min=${digit[$i]}
#如果digit[$i]小于当前最小值$max时,就将digit[$i]重新付给min
done
echo ALL digit are ${digit[*]}
echo Max is $max
echo Min is $min
[root@CentOS6 bin]#max.sh
ALL digit are 22019 3896 15981 5024 6535 4256 14632 31882 395 1400
Max is 31882
Min is 395
- 编写脚本,定义一个数组,数组的元素是/var/log目录下所有以.log结尾的文件;统计其下标为偶数的文件中的行数之和
[root@CentOS6 bin]#vim linesum.sh
#!/bin/bash
declare -a logfiles=(/var/log/*.log) #定义数组
#echo All logfile are ${logfiles[@]}
i=0
linesum=0
filelines=0
while [ $i -lt ${#logfiles[@]} ] ; do #循环次数为数组元素个数
if [ $[$i%2] -eq 0 ] ;then #判断数组下标为偶数通过条件判断
filelines=`cat /${logfiles[$i]} | wc -l` #将下标为偶数的文件行数赋值给变量filelines
let linesum+=$filelines #加总
echo ${logfiles[$i]} filelines is $filelines
fi
let i++ #变量i也就是数组元素下标自增
done
echo linesum is $linesum
[root@CentOS6 bin]#linesum.sh
/var/log/anaconda.ifcfg.log filelines is 219
... #省略
/var/log/Xorg.9.log filelines is 309
linesum is 4194
字符串切片
${#var}:返回字符串变量var的长度
${var:offset}:返回字符串变量var中从第offset个字符后(不包括 第offset个字符)的字符开始,到最后的部分,offset的取值在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前空格
基于模式取子串
${var#*word}:其中word可以是指定的任意字符
功能:自左而右,查找var变量所存储的字符串中,第一 次出现的word, 删除字符串开头至第一次出现word字符之间的 所有字符
${var##*word}:同上,贪婪模式,不同的是,删除的 是字符串开头至最后一次由word指定的字符之间的所有内容
示例:
[root@CentOS6]#file=var/log/messages
[root@CentOS6]#echo ${file#*/}
log/messages
[root@CentOS6]#echo ${file##*/}
messages
${var%word*}:其中word可以是指定的任意字符;
功能:自右而左,查找var变量所存储的字符串中,第一 次出现的word, 删除字符串最后一个字符向左至第一次出现 word字符之间的所有字符; file="/var/log/messages"
${file%/*}: /var/log
${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;
示例:
[root@CentOS6 ~]#url=http://www.badu.com:80
[root@CentOS6 ~]#echo ${url##*:}
80
[root@CentOS6 ~]#echo ${url%%:*} #:*顺序有变化
http
查找替换
${var/pattern/substr}:查找var所表示的字符串中,第 一次被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}: 查找var所表示的字符串中, 所有能被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}:查找var所表示的字符串中, 行首被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}:查找var所表示的字符串中, 行尾被pattern所匹配到的字符串,以substr替换之
查找删除
${var/pattern}:删除var所表示的字符串中第一次被 pattern所匹配到的字符串
${var//pattern}:删除var所表示的字符串中所有被 pattern所匹配到的字符串
${var/#pattern}:删除var所表示的字符串中所有以 pattern为行首所匹配到的字符串
${var/%pattern}:删除var所表示的字符串中所有以 pattern为行尾所匹配到的字符串
大小写转换
${var^^}:把var中的所有小写字母转换为大写
${var,,}:把var中的所有大写字母转换为小写
高级变量用法-有类型变量
shell变量一般是无类型的,但是bash shell提供了declare和typest两个命令用于指定变量的类型,两个命令是等价的
declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数
-l 声明变量为小写字母 declare –l var=UPPER
-u 声明变量为大写字母 declare –u var=lower
eval命令
eval命令将会首先扫描命令行进行所有的置换,让你后在执行该敏玲。该命令适用于那些一次扫描无法实现其功能的变量。该命令对变量进行两次扫描
示例:
[root@CentOS6 ~]#CMD=whoami
[root@CentOS6 ~]#echo $CMD
whoami
[root@CentOS6 ~]#eval $CMD
root
[root@CentOS6 ~]#n=10
[root@CentOS6 ~]#echo {0..$n}
{0..10}
[root@CentOS6 ~]#eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10
间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量应用。
variable1的值是variable2,而variable2又是变量名, variable2的值为value,间接变量引用是指通过variable1获 得变量值value的行为
variable1=variable2
variable2=value
bash shell提供了两种格式实现间接变量引用
eval tempvar=$$variable1
tempvar=${!variable1}
创建临时文件
mktemp命令:创建并显示临时文件,可避免冲突
mktemp [OPTION]... [TEMPLATE]
TEMPLATE: filename.XXX
X至少要出现三个
[root@CentOS6 app]#mktemp /app/tmpfile.XXX
/app/tmpfile.8Tw #文件后缀必须是大写X有多少个X就会生成多少个随机字符
#赋值给变量直接引用
[root@CentOS6 app]#tmpfile=`mktemp /app/tmpfile.XXX`
[root@CentOS6 app]#echo $tmpfile
/app/tmpfile.2AO
将指定目录赋值给变量直接引用
[root@CentOS6 app]#dir=/app
[root@CentOS6 ~]#mktemp -p $dir testfile.XXX
/app/testfile.wzQ
OPTION:
-d: 创建临时目录
-p DIR或--tmpdir=DIR:指明临时文件所存放目录位置
示例:
mktemp /tmp/test.XXX
tmpdir=`mktemp –d /tmp/testdir.XXX`
mktemp --tmpdir=/testdir test.XXXXXX
安装复制文件
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 755 –d /testdir/installdir
若有疑问以及错误指正请留言吧。