shell编程之函数的定义、执行、传参和递归函数|条件测试操作与流程控制语句|内部字段分隔符IFS、脚本调试DEBUG
函数定义:
格式1
function name() {
command sequence(命令序列);
}
格式2
name() {
Command sequence(命令序列);
}
可以带function name()定义,也可以直接name()定义,不带任何参数。
执行
name;
直接使用函数名称即可调用某个函数。
传递参数
#!/bin/bash
aa="this is aa"
bb="this is bb"
function name() { #定义函数name
local cc="this is cc" #定义局部变量$cc
local dd="this is dd" #定义局部变量$dd
echo $aa, $bb #访问参数1和参数2
echo $cc #打印局部变量
return 0 #shell函数返回值是整形,并且在0~257之间。
}
echo $dd #这里将会打印不生效,因为dd是局部变量。
name #使用函数name
上例中:
- aa 和 bb 定义的是全局变量。
- cc 和 dd 定义的是局部变量,只能在函数 name 中使用。
$aa是第一个参数$1,$bb是第一个参数$2,以此类推$n是第n个参数$n。return 0参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果作为返回值。
递归函数
bash也支持递归函数(能够调用自身的函数)例如:
#!/bin/bash
function name() {
echo $1
name hello
sleep 1
}
name
运行此脚本后不断打印出hello,按Ctrl+C结束。
递归经典:fork 炸弹
可能很多人都曾经听说过 fork 炸弹,它实际上只是一个非常简单的递归程序,程序所做的事情只有一样:这个递归函数能够调用自身,不断的生成新的进程,这会导致这个简单的程序迅速耗尽系统里面的所有资源,造成拒绝服务攻击。
.()
{
.|.&
}
;
.
- 第 1 行说明下面要定义一个函数,函数名为小数点,没有可选参数。
- 第 2 行表示函数体开始。
- 第 3 行是函数体真正要做的事情,首先它递归调用本函数,然后利用管道调用一个新进程(它要做的事情也是递归调用本函数),并将其放到后台执行。
- 第 4 行表示函数体结束。
- 第 5 行并不会执行什么操作,在命令行中用来分隔两个命令用。从总体来看,它表明这段程序包含两个部分,首先定义了一个函数,然后调用这个函数。
- 第 6 行表示调用本函数。
================================================
条件测试操作与流程控制语句
程序中的流程控制是由比较和测试语句来处理的,bash具备多种与UNIX系统级特性相兼容的执行测试方法。
常用测试操作
test命令,测试特定的表达式是否成立,当条件成立时,命令执行后的返回值为0,否则为其他数值。
格式1 test 条件表达式 格式2 [ 条件表达式 ] //常用格式,使用方括号时,要注意在条件两边加上空格。
常见测试类型
- 测试文件状态
- 字符串的比较
- 整数值的比较
- 逻辑测试
测试文件
格式 [ 操作符 文件或目录 ]
操作符:
- -d:测试是否为目录,是则为真(Directory)
- -e:测试目录或文件是否存在,存在则为真(Exist)
- -f:测试是否为文件,是则为真(file)
- -r:测试当前用户是否有权限读取,是则为真(read)
- -w:测试当前用户是否有权限写入,是这为真(write)
- -x:测试当前用户是否可执行该文件,可执行则为真(Excute)
- -L:测试是否为符号链接文件,是则为真(Link)
- -nt:file1 -nt file2 如果 file1 比 file2 新(修改时间),则为真
- -ot:file1 -ot file2 如果 file1 比 file2 旧(修改时间),则为真
ex:文件夹不存在则创建
if [ ! -d "/data/" ];then
mkdir /dataelseecho "文件夹已经存在"fi字符串比较
格式 [ 字符串1 = 字符串2 ] [ 字符串1 != 字符串2 ] [ -z 字符串 ]
操作符:
- =:字符串内容相同则为真,就是说包含的文本一摸一样。
- !=:字符串内容不同,则为真(!号表示相反的意思)
- -z:字符串内容为空(长度为零)则为真
- -n:字符串内容非空(长度非零)则为真
- <:string1 < string2 如果string1在本地的字典序列中排在string2之前,则为真
- >:string2 如果string1在本地的字典序列中排在string2之后,则为真
注意点:
1、字符串的 “等于” 比较,为了与POSIX一致,在[]中使用=,(尽管==也可以可以用的)
2、注意在=前后各有一个空格,如果没有空格就是赋值的关系,不是比较的关系。
3、字符串的> <比较运算符,一般放在[[ ]]之中,而不是test ("[]")
4、字符串的> <比较的结果,与本地的locale有关,是按照其字典序列进行比较的
整数值比较
格式 [ 整数1 操作符 整数2 ]
- -eq:等于(equal)
- -ne:不等于(not equal)
- -gt:大于(Greater than)
- -lt:小于(lesser than)
- -le:小于等于(lesser or equal)
- -ge:大于等于(Greater or equal)
逻辑测试
格式 [ 表达式1 ] 操作符 [ 表达式2 ] ...
操作符
- -a 或 && :逻辑与,“而且”的意思,前后两个表达式都成立时整个测试结果才为真,否则为假
- -o 或 || : 逻辑或,“或者”的意思,操作符两边至少一个为真时,结果为真,否为为假
- ! :逻辑否,当制定条件不成立时,返回结果为真
流程控制语句
Shell有一套自己的流程控制语句,其中包括条件语句、循环语句、选择语句等。
if条件语句
if 单分支:当“条件成立”时执行相应的操作。
if 条件测试操作 then 命令序列 fi
ex:
#!/bin/bash
#当/boot分区的空间使用超过80%,就输出报警信息。
use=`df -hT | grep "/boot" | awk '{print $6}' | cut -d "%" -f1`
if [ $use -gt 80 ];
then
echo "Warning!!/boot disk is full"
fi
if 双分支:当“条件成立”、“条件不成立”时执行不同操作。
if 条件测试命令 then 命令序列1 else 命令序列2 fi
ex:
#!/bin/bash
#判断iptables是否在运行,如果已经在运行提示信息,如果没有开启它。
service iptables status &> /dev/null
if [ $? -eq 0 ];
then
echo "iptables service is running"
else
service iptables restart
fi
if 多分支:相当于if语句嵌套,针对多个条件执行不同操作。
if 条件测试命令1 ; then 命令序列1 elif 条件测试命令2 ; then 命令序列2 elif ... else 命令序列n fi
for循环语句
根据标量的不同取值,重复执行一组命令操作。
for 变量名 in 取值列表 do 命令序列 done
for循环的几种应用形式:
最基本的for循环: (传统的形式,for var in …)
for循环总是接收in语句之后的某种类型的字列表。在本例中,指定了四个英语单词,但是字列表也可以引用磁盘上的文件,甚至文件通配符。
对目录中的文件做for循环
#!/bin/bash
for x in /var/log/*
do
#echo "$x is a file living in /var/log"
echo $(basename $x) is a file living in /var/log
done
这个$x获得的是绝对路径文件名,可以使用basename可执行程序来除去前面的路径信息。如果只引用当前工作目录中的文件(例如如果输入for x in *),则产生的文件列表将没有路径信息的前缀。
对位置参数做for循环
#!/bin/bash
for thing in "$@"
do
echo you typed ${thing}.
done
for循环中用seq产生循环次数,加上C语言形式的for循环语句
#!/bin/bash echo "for: Traditional form: for var in ..." for j in $(seq 1 5) do echo $j done echo "for: C language form: for (( exp1; exp2; exp3 ))" for (( i=1; i<=5; i++ )) do echo "i=$i" done
对于固定次数的循环,可以通过seq命令来实现,就不需要变量的自增了,这里的C语言for循环风格是挺熟悉的吧。
while循环语句
重复测试指令的条件,只要条件为真则反复执行对应的命令操作,直到条件为假。如果使用true作为循环条件能够产生无限循环。
while 命令表达式 do 命令列表 done
示例
#!/bin/bash
#批量添加20个系统账户用户名依次为user1~20
i=1
while [ $i -le 20 ]
do
useradd user$1
echo "123456" | passwd --stdin user$i &> /dev/null
i=`expr $i + 1`
done
只要特定条件为真,”while” 语句就会循环执行。
case多重分支语句
根据变量的不通取值,分别执行不同的命令操作。
case 变量值 in 模式1) 命令序列1 ;; 模式2) 命令序列2 ;; …… *) 默认执行的命令序列 ;; esac
示例
#!/bin/bash
case $1 in
start)
echo "start mysql"
;;
stop)
echo "stop mysql"
;;
*)
echo "usage: $0 start|stop"
;;
esac
until循环语句
根据条件执行重复操作,直到条件成立为止。Until语句提供了与while语句相反的功能:只要特定条件为假,它们就重复循环,直到条件为真。
until 条件测试命令 do 命令序列 done
shift迁移语句
用于迁移位置变量,将$1~$9依次向左传递。
例如:若当前脚本程序获得的位置变量如下:
$1=file1、$2=file2、$3=file3、$4=file4
执行一次shift命令后,各位置变量为:
$2=file2、$3=file3、$4=file4
在执行一次:
$3=file3、$4=file4
示例
#!/bin/bash
res=0
while [ $# -gt 0 ]
do
res=`expr $res + $1`
shift
done
echo "the sum is:$res"
循环控制语句
break语句:在for、while、until等循环语句中,用于跳出当前所在的循环体,执行循环体之后的语句。
continue语句:在for、while、until等循环语句中,用于跳过循环体内余下的语句,重新判断条件以便执行下一次循环。
============================================================================================
内部字段分隔符
内部字段分隔符(Internal Field Separator, IFS)是shell脚本中的一个特殊变量,在处理文本数据很有用。把单个数据流划分成不同的数据元素的定界符,内部字段分隔符就是用于特定用途的定界符。IFS是存储定界符的环境变量,是shell环境中的默认定界符字符串,默认值为空白字符(换行符、制表符、空格)
迭代一个字符串或者CSV(Comma Separated Value, 逗号分隔型数值)中的单词:
#!/bin/bash data="111,222,333,444,555,666" oldIFS=$IFS #定义一个变量为默认IFS IFS=, #设置IFS为逗号 for i in $data do echo S:$i done IFS=$oldIFS #还原IFS为默认值
执行结果:
[root@mail text]# ./6.sh
S:111
S:222
S:333
S:444
S:555
S:666
IFS被设置为逗号(,),shell将逗号解释为一个定界符,因此变量$i在每次迭代中读取由逗号分隔的字符串作为变量值。
Shell脚本的调试
调式功能是每种编程语言都具备的特性之一,出现一些始料未及的情况,使用调式功能可以弄清楚是什么原因发生了错误或者异常。shell脚本自身已经包含了调式选项,能都打印出脚本接受的参数和输入。
1、使用选项 -x
bash -x script.sh 或者 sh -x script.sh
-x 选项是打印所有行的信息。
2、使用 set +/-x ;set +/-v
- set -x:在执行时候显示参数和命令。
- set +x:禁止调式。
- set -v:当命令进入读取时候显示输入。
- set +v:禁止打印输入。
ex:
#!/bin/bash
for i in {1..5}
do
set -x
echo $i
set +x
done
echo "end"
上例中,仅在 -x 和 +x 的区域中才会显示调式信息。
3、使用 _DEBUG 环境变量
如果需要自定义格式显示调式信息可以通过_DEBUG环境变量来建立:
#!/bin/bash
DEBUG() {
[ "$_DEBUG" = "on" ] && $@ || :
}
for i in {1..5}
do
DEBUG echo $i
done
将调试功能设置为“on”来运行脚本:
_DEBUG=on ./script.sh
将需要调式的行前加上DEBUG,运行脚本前没有加_DEBUG=on就不会显示任何信息,脚本中“:”告诉shell不要进行任何操作。
4、使用shebang调式方法
这是最便捷的方法。把shebang 从#!/bin/bash 修改成 #!/bin/bash -xv ,其他就不用做任何操作了。
浙公网安备 33010602011771号