# Shell流程控制-函数 ## 一、Shell 编程之条件结构 ### 0、走在测试之前--认识各种括号 **[]**、()、{}、(())、[[]]、**$(())、$[]、$()、${}** #### 1.单括号[] () {} ```txt [] test命令的另一种写法 () 重新开一个子shell然后执行,里面的最后一个命令可以不用分号 {} 在当前 shell 执行,里面的最后一个命令要用分号,第一个命令和左括号之间必须有一个空格 ``` ()和{}里的某个命令的重定向只影响该命令, 而括号外的重定向则影响到括号里的所有命令。 **()测试示例** ```bash # 测试数据定义 [root@qfedu.com ~]# var=test # 子shell中执行命令 [root@qfedu.com ~]# echo $var test [root@qfedu.com ~]# (var=notest;echo $var) notest [root@qfedu.com ~]# echo $var test # ()里的执行完毕后没有改变当前shell变量的值,说明在子shell中执行的 ``` **{}测试示例** ```bash [root@qfedu.com ~]# {var=notest;echo $var} {var=notest: command not found test} [root@qfedu.com ~]# {var=notest;echo $var;} -su: syntax error near unexpected token `}' [root@qfedu.com ~]# { var=notest;echo $var;} notest [root@qfedu.com ~]# echo $var notest # {}修改了变量的值。表明在当前shell中运行的 适用场景 # { ls;pwd;}>a.txt # 对比一下下面的输出有何不同 # ls;pwd>a.txt ``` **[]测试示例-见test命令详解** #### 2.双括号(()) [[]] (()) 适合数值比较、运算 ,C语言形式 ```bash [root@wing tmp]# a=8 [root@wing tmp]# b=9 [root@wing tmp]# ((c=a+b)) [root@wing tmp]# echo $c 17 [root@wing tmp]# ((a>b)) [root@wing tmp]# echo $? 1 [root@wing tmp]# ((a | s1>s2 | 如果按自定顺序s1在s2之后,则测试条件为真 | **示例** ```shell # 字符串比较必须使用双引号 [root@qfedu.com ~]# [ "$USER" = "root" ];echo $? 0 [root@qfedu.com ~]# [ "$USER" == "root" ];echo $? 0 [root@qfedu.com ~]# BBB="" [root@qfedu.com ~]# echo ${#BBB} 0 [root@qfedu.com ~]# [ -z "$BBB" ] # 字符长度是为0 [root@qfedu.com ~]# echo $? 0 [root@qfedu.com ~]# [ -n "$BBB" ] # 字符长度不为0 [root@qfedu.com ~]# echo $? 1 [root@qfedu.com ~]# var1=111 [root@qfedu.com ~]# var2= [root@qfedu.com ~]# # var3变量没有定义 [root@qfedu.com ~]# echo ${#var1} 3 [root@qfedu.com ~]# echo ${#var2} 0 [root@qfedu.com ~]# echo ${#var3} 0 [root@qfedu.com ~]# [ -z "$var1" ];echo $? 1 [root@qfedu.com ~]# [ -z "$var2" ];echo $? 0 [root@qfedu.com ~]# [ -z "$var3" ];echo $? 0 [root@qfedu.com ~]# [ -n "$var1" ];echo $? 0 [root@qfedu.com ~]# [ -n "$var2" ];echo $? 1 [root@qfedu.com ~]# [ -n "$var3" ];echo $? 1 ``` #### 3.整数操作符 | **在[]和test中使用** | **在[[ ]]和(( ))中使用** | **说明** | | -------------------- | ------------------------ | -------------------------------- | | -eq | ==或= | 等于,全拼为equal | | -ne | != | 不等于,全拼为not equal | | -gt | > | 大于,全拼为greater than | | -ge | >= | 大于等于,全拼为greater or equal | | -lt | < | 小于,全拼为less than | | -le | <= | 小于等于,全拼为less or equal | 1、判断变量是不是数字 ```shell [root@qfedu.com ~]# num10=123 [root@qfedu.com ~]# num20=ssss1114ss [root@qfedu.com ~]# [[ "$num10" =~ ^[0-9]+$ ]];echo $? 0 [root@qfedu.com ~]# [[ "$num20" =~ ^[0-9]+$ ]];echo $? 1 ``` 2、数值比较 [ 整数1 操作符 整数2 ] ```shell [root@qfedu.com ~]# disk_use=$(df -P |grep '/$' |awk '{print $5}' |awk -F% '{print $1}') [root@qfedu.com ~]# [ $disk_use -gt 90 ] && echo "war......" [root@qfedu.com ~]# [ $disk_use -gt 60 ] && echo "war......" war...... [root@qfedu.com ~]# id -u 0 [root@qfedu.com ~]# [ $(id -u) -eq 0 ] && echo "当前是超级用户" 当前是超级用户 [alice@qfedu.com ~]$ [ $UID -eq 0 ] && echo "当前是超级用户" || echo "you不是超级用户" you不是超级用户 ``` 3、C语言风格的数值比较 ```shell [root@qfedu.com ~]# ((1<2));echo $? 0 [root@qfedu.com ~]# ((1==2));echo $? 1 [root@qfedu.com ~]# ((1>2));echo $? 1 [root@qfedu.com ~]# ((1>=2));echo $? 1 [root@qfedu.com ~]# ((1<=2));echo $? 0 [root@qfedu.com ~]# ((1!=2));echo $? 0 [root@qfedu.com ~]# ((`id -u`>0));echo $? 1 [root@qfedu.com ~]# (($UID==0));echo $? 0 ``` #### 4.逻辑操作符 | **在[]和test中使用** | **在[[ ]]和(( ))中使用** | **说明** | | -------------------- | ------------------------ | ---------------------------------- | | -a | && | and,与,两端都为真,则结果为真 | | -o | \|\| | or,或,两端有一个为真,则结果为真 | | ! | ! | not,非,两端相反,则结果为真 | ```shell [root@qfedu.com ~]# [ 1 -lt 2 -a 5 -gt 10 ];echo $? 1 1 0 [root@qfedu.com ~]# [ 1 -lt 2 -o 5 -gt 10 ];echo $? 0 [root@qfedu.com ~]# [[ 1 -lt 2 && 5 -gt 10 ]];echo $? 1 [root@qfedu.com ~]# [[ 1 -lt 2 || 5 -gt 10 ]];echo $? 0 ``` #### 5、测试表达式区别总结 | **测试表达式符号** | **test** | **[ ]** | **[[ ]]** | **(( ))** | | ------------------ | ---------------------------- | ---------------------------- | ------------------------------------------------- | ------------------- | | 边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 | | 逻辑操作符 | !、-a、 -o | !、-a、 -o | !、&&、\|\| | !、&&、\|\| | | 整数比较操作符 | -eq、-ne、-lt、-gt、-ge、-le | -eq、-ne、-lt、-gt、-ge、-le | -eq、-ne、-lt、-gt、-ge、-le或=、!=、<、>、>=、<= | =、!=、<、>、>=、<= | | 字符串比较操作符 | =、==、!= | =、==、!= | =、==、!= | =、==、!= | | 是否支持正则 | 不支持 | 不支持 | 支持 | 不支持 | 注:变量为空或未定义长度都为0 ```shell [root@qfedu.com ~]# [ "$USER" = "root" ];echo $? 0 [root@qfedu.com ~]# [ "$USER" = "alice" ];echo $? 1 [root@qfedu.com ~]# [ "$USER" != "alice" ];echo $? 0 [root@qfedu.com ~]# [ "$USER" = "root" ];echo $? 0 [root@qfedu.com ~]# [ "$USER" =~ ^r ];echo $? bash: [: =~: binary operator expected 2 [root@qfedu.com ~]# [[ "$USER" =~ ^r ]];echo $? # 使用正则 0 ``` ### 3、Shell 分支if语句 #### 1、单分支 IF 条件语句 ```shell if [ 条件判断式 ];then 条件成立时,执行的程序 fi # if语句使用fi结尾和一般语言使用大括号结尾不同 # [条件判断式] 就是使用test命令判断,所以中括号和条件判断式之间必须有空格 # then 后面跟符号条件之后执行的程序,可以放在[]之后,用";"分割。也可以换行写入,就不需要";"了 ``` #### 2、双分支语句 ```shell if [ 条件判断式 ];then 条件成立时,执行的程序 else 条件不成立时,执行的另一个程序 fi ``` #### 3、多分支语句 ```shell if [ 条件判断式1 ] then 当条件判断式1成立时,执行程序1 elif [ 条件判断式2 ] then 当条件判断式2成立时,执行程序2 ...省略更多条件.... else 当所有条件都不成立,最后执行此程序 fi ``` ### 4、Shell 分支case语句 ```bash case $变量名 in 值1) 如果变量的值等于值1则执行指令1 ;; 值2) 如果变量的值等于值2则执行指令2 ;; 值3) 如果变量的值等于值3则执行指令3 ;; *) 如果变量的值不等于以上列出的任何值则执行默认指令 esac ``` ## 二、Shell 编程之循环结构 ### 1、Shell 循环 for 语句 for循环的运作方式,是将串行的元素一一取出,依序放入指定的变量中,然后重复执行含括的命令区域(在do和done 之间),直到所有元素取尽为止。 其中,串行是一些字符串的组合,彼此用$IFS所定义的分隔符(如空格符)隔开,这些字符串称为字段。 ```shell for 变量 in 值集合 do 执行命令 done ``` - for 每次从值集合中取一个值赋值给变量 - do - done 将赋值后的变量带入执行的命令得到执行结果 , - 重复以上两个步骤,直到值集合中的值被一一获取赋值给变量的到所有结果,循环结束 ```bash 使用变量 num=8 for i in $(seq 2 $num) do echo $i done for i in {2..8} ``` ```bash for((i=1;i<=10;i++)) do echo $i done i=1 初始化i的值为1 i<=10 比较条件 i++ 步长 每次加1 嵌套的 for((i=1;i<=10;i++)) do for((i=1;i<=10;i++)) do echo $i done echo $i done ``` ### 2、Shell 循环 while 语句 1、while 循环语法 ```shell while 条件测试 do 执行命令 done ``` - while 首先进行条件测试,如果传回值为0(条件测试为真),则进入循环,执行命令区域,否则不进入循环 - 满足 while 测试条件,执行命令区域,直到 while 的测试条件不满足结束执行while循环(如果条件一直满足执行无穷循环)。 ### 3、Shell 循环 until 语句 while循环的条件测试是测真值,until循环则是测假值。 ```shell until 条件测试 do 执行命令 done ``` - until 条件测试结果为假(传回值不为0),就进入循环。 - 条件测试不满足,执行命令区域。直到 until 条件满足,结束执行until 循环(如果条件一直不满足则执行无穷循环)。 ```shell #!/bin/bash until ((i>10)) # 条件测试:只要i值未超过10,就进入循环 do let sum+=i # sum+=i和sum=sum+i是一样的,sum累加上i let ++i # i的值递增1,此行是改变条件测试的命令,一旦i大于10,可终止循环 done # 遇到done,回到 until条件测试 echo $sum # 直到 until 的条件满足显示sum的值 ``` ```shell #!/bin/bash a=1 b=1 until ((a>9)) # 条件测试:只要a值未超过9,就进入循环,一旦超过9就不执行,until和while条件相反,条件真就done结束 do until ((b>a)) # b>a,一旦b大于a就不执行 do let "c=a*b" echo -n "$a*$b=$c " let b++ done let a++ let b=1 echo "" done ``` ### 4、Shell 循环控制 break,continue,exit 一般用于循环结构中控制循环的走向。 break和continue只能用在循环里面 exit可以用于脚本的任何地方 | **命令** | **说明** | | ----------- | ------------------------------------------------------------ | | break n | n 表示跳出循环的次数,如果省略 n 表示跳出整个循环 | | continue n | n 表示退到第n层继续循环,如果省略n表示跳过本次循环进入下一次循环 | | exit n | 退出当前的shell程序,并返回 n,n 也可以省略 | | return | 用于返回一个退出值给调用的函数 | | shift | 用于将参数列表list左移指定次数,最左端的那个参数就从列表中删除,其后边的参数继续进入循环 | break[N]:提前结束第N层循环,最内层为第1层 ```bash while CONDITION1; do CMD1 ... if CONDITION2; then break fi CMDn ... done ``` break:结束本次循环(整个),退出脚本 ```shell [root@qfedu.com ~]#vim test.sh #!/bin/bash for i in {1..10} do [ $i -eq 5 ] && break echo i=$i sleep 0.5 done echo test is finished [root@qfedu.com ~]#chmod +x test.sh [root@qfedu.com ~]#./test.sh i=1 i=2 i=3 i=4 test is finished [root@localhost ~]# cat break1.sh #!/bin/bash for((i=0;i<=5;i++)) do if [ $i -eq 3 ];then break; fi echo $i done echo "ok" # 运行结果为: [root@localhost ~]# bash break1.sh 0 1 2 ok ``` continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层 ```bash while CONDITION1;do CMD1 ... if CONDITION2; then continue fi CMDn ... done ``` ```shell [root@localhost ~]# cat continue.sh #!/bin/bash for((i=0;i<=5;i++)) do if [ $i -eq 3 ];then continue; fi echo $i done echo "ok" #运行结果为: [root@localhost ~]# bash continue.sh 0 1 2 4 5 ok [root@qfedu.com ~]#vim test.sh #!/bin/bash for i in {1..10} do [ $i -eq 5 ] && continue echo i=$i sleep 0.5 done echo test is finished [root@qfedu.com ~]#./test.sh i=1 i=2 i=3 i=4 i=6 i=7 i=8 i=9 i=10 test is finished ``` exit 指令 ```shell [root@localhost ~]# cat exit.sh #!/bin/bash for((i=0;i<=5;i++)) do if [ $i -eq 3 ];then exit fi echo $i done echo "ok" #运行结果为: [root@localhost ~]# bash exit.sh 0 1 2 ``` ### 5、shift 指令(了解) shift 命令用于将参数列表 list 左移指定次数,最左端的那个参数就从列表中删除,其后边的参数继续进入循环。 shift[N]:用于将参量列表 list 左移指定次数,缺省为左移一次。 参量列表 list 一旦被移动,最左端的那个参数就从列表中删 除。while 循环遍历位置参量列表时,常用到 shift 实例 ```shell [root@qfedu.com ~]# vim demo.sh #!/bin/bash while [ $# -gt 0 ] do echo $* shift done [root@qfedu.com ~]# ./demo.sh a b c d e f g h a b c d e f g h b c d e f g h c d e f g h d e f g h e f g h f g h g h h [root@qfedu.com ~]# vim shift.sh #!/bin/bash until [ -z "$1" ] do echo "$1" shift done echo [root@qfedu.com ~]# ./shift.sh a b c d e f g h a b c d e f g ``` ## 三、Shell 编程之函数 Shell 函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接调取即可 ### 1、定义函数 可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。 ```php # 方法一 function name() { commands [return value] } # 方法二 name() { commands [return value] } ``` - function 是 Shell 中的关键字,专门用来定义函数; - name 是函数名; - commands 是函数要执行的代码,也就是一组语句; - return value 表示函数的返回值,其中 return 是 Shell 关键字,专门用在函数中返回一个值;这一部分可以写也可以不写。 - 由 { } 包围的部分称为函数体,调用一个函数,实际上就是执行函数体中的代码。 - 函数的优势 - 方便n次使用,减少代码量,使之方便,整洁。 - 当需要修改里面的重复代码时,只需要修改一次函数即可实现需求; - 将函数写进文件,需要时直接通过文件调用 ### 2、调用函数 1、执行不带参数的函数 直接输入函数名即可,不需要带括号, ```undefined functionName ``` - 执行函数时,函数名前的关键字function和函数名后面的()均不需要带 - 函数的定义必须要在执行的程序前定义或加载 2、执行带参数的函数 ```undefined functionName arg1 arg2 ``` - Shell中的位置参数($1/$2.../$#/$?/$@)均可以做为函数的参数进行传递 - $0比较特殊,仍然是父脚本的名称 - 此时父脚本的参数会临时被函数的参数所掩盖或隐藏 - 函数的参数变量是在函数体内里面进行定义 调用函数示例 ```shell [root@qfedu.com ~]# cat testfunction.sh #!/bin/bash # first function function HelloWorld() { echo "Hello world" } # second function Welcome() { echo "Welcome to qfedu" } # third function function HelloShell { echo "Hello Shell" } # file functions HelloWorld # 调用函数 Welcome HelloShell [root@qfedu.com ~]# bash testfunction.sh Hello world Welcome to qfedu Hello Shell ``` 从文件中调用函数示例 ```shell [root@qfedu.com ~]# cat filefunction.sh function Sum () { for((i=1;i<=100;i++)) do ((sum=sum+i)) done echo '{1..100} sum is :' $sum } [root@qfedu.com ~]# cat filefunctionfromfile.sh #!/bin/bash path="/root/Test/filefunction.sh" if [ -f ${path} ] then source $path # 加载函数 Sum # 调用函数 else echo "file not exist or error" fi [root@qfedu.com ~]# bash filefunctionfromfile.sh {1..100} sum is : 5050 ``` 函数参数传递示例 ```shell [root@qfedu.com ~]# cat functionwithargs.sh #!/bin/bash function Add () { # 定义函数 ((sum=$1+$2)) echo "$1 + $2 sum is" ${sum} } Add $1 $2 # 调用函数并传递参数 [root@qfedu.com ~]# bash functionwithargs.sh 100 150 100 + 150 sum is 250 [root@qfedu.com ~]# bash functionwithargs.sh 509 150 509 + 150 sum is 659 ``` ### 3、return 返回函数结果 注意return的数字必须是数字,用引号包裹也一样 ```shell [root@qfedu.com ~]# cat functionwithreturn.sh #!/bin/bash function TestReturn() { if [ -d $1 ];then return "122" else return "222" fi } TestReturn $1 result=$? # 获取函数返回值 if [ ${result} == "122" ] then echo "$1 exist ,return value is:" ${result} else echo "$1 not exist ,return value is:" ${result} fi [root@qfedu.com ~]# bash functionwithreturn.sh /etc/sysconfiggg /etc/sysconfiggg not exist ,return value is: 222 [root@qfedu.com ~]# bash functionwithreturn.sh /etc/sysconfig /etc/sysconfig exist ,return value is: 122 ``` 在该示例中,主要通过 $? 获取返回值,但返回值的范围只能是 0~255 ### 4、echo 返回函数结果 ```shell [root@qfedu.com ~]# cat functionwithecho.sh #!/bin/bash function TestReturn() { if [ -d $1 ];then echo "122" else echo "222" fi } result=$(TestReturn $1) # 获取函数返回值 if [ ${result} == "122" ];then echo "$1 exist ,return value is:" ${result} else echo "$1 not exist ,return value is:" ${result} fi [root@qfedu.com ~]# bash functionwithecho.sh /etc/sysconfig /etc/sysconfig exist ,return value is: 122 [root@qfedu.com ~]# bash functionwithecho.sh /etc/sysconfiggg /etc/sysconfiggg not exist ,return value is: 222 ``` 该示例中,主要使用 $() 获取返回值,在该方法中,没有范围限制,是一种比较安全的返回方式。 ```shell [root@qfedu.com ~]# cat functionwithecho.sh #!/bin/bash function TestReturn() { if [ -d $1 ] then echo "$1 exist" else echo "$1 not exist" fi } result=$(TestReturn $1) # 获取返回值,返回的结果是字符串 if [ "${result}" == "$1 exist" ] then echo "$1 exist ,return value is:" ${result} else echo "$1 not exist ,return value is:" ${result} fi [root@qfedu.com ~]# bash functionwithecho.sh /etc/sysconfiggg /etc/sysconfiggg not exist ,return value is: /etc/sysconfiggg not exist [root@qfedu.com ~]# bash functionwithecho.sh /etc/sysconfig /etc/sysconfig exist ,return value is: /etc/sysconfig exist ``` ### 5、全局变量和局部变量 全局变量在shell 脚本中任何地方都能使用;局部变量在函数内部使用,声明前加一个 local 就好 ```bash [root@qfedu.com ~]# cat test3.sh function fun() { a=$[ $b + 5 ] c=$[ $a * 2 ] } a=4 b=6 fun if [ $a -gt $b ] then echo "$a is larger than $b" else echo "$a is smaller than $b" fi function fun() { local a=$[ $b + 5 ] c=$[ $a * 2 ] } a=4 b=6 fun if [ $a -gt $b ] then echo "$a is larger than $b" else echo "$a is smaller than $b" fi [root@qfedu.com ~]# bash test3.sh 11 is larger than 6 4 is smaller than 6 ``` ### 6、递归函数(纯了解) ```bash [root@qfedu.com ~]# cat test6.sh function factorial() { if [ $1 -eq 1 ] then echo 1 else local temp=$[ $1 - 1 ] local result=$(factorial $temp) echo $[ $result * $1 ] fi } read -p "Enter value: " value result=$(factorial $value) echo "The factorial of $value is: $result" [root@qfedu.com ~]# bash test6.sh Enter value: 5 The factorial of 5 is: 120 ``` **扩展** **内建命令** ​ 因为安装了shell而产生的命令