SHELL脚本编程进阶(一)

前端之家收集整理的这篇文章主要介绍了SHELL脚本编程进阶(一)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

写在前面(最重要)

本文部分资料和示例援引自以下书籍。在此,感谢原作者的创作,以及所有译者的付出,向他们致敬。

  1. Advanced Bash-Scripting Guide

  2. 《高级Bash脚本编程指南》Revision 10中文版

  3. Linux脚本编程执导

其中《高级Bash脚本编程指南》Revision 10中文版《Advanced Bash-Scripting Guide》中文翻译版,文档翻译正在进行中,再次感谢译者付出。

前言

在之前的文章中,我们已经详细的介绍了SHELL脚本编程的一些基础知识,运用这些基础已经能够帮助我们高效率的解决日常生产中的一些问题了,但还远远不够。实际生产中可能面临这大量的复杂的处理任务,需要我们编写脚本去完成。此时就需要一些更高级的内容,来帮助我们完成对脚本的编写。
今天将介绍的是SHELL脚本中的循环与分支

分支与循环

1. 分支

2. 循环

3. 循环控制

4. 特殊用法

分支

条件选择if语句

if 语句是脚本编写过程中基本分支语句。简单的if语句可以使用一些测试结构代替,但是if语句的可读性,却是测试结构无法替代。

单分支的if语句

单分支的 if 语句的语法结构如下。

	if判断条件;then
		COMMANDS		
		...	
	fi

多分支的if语句

多分支的 if 语句的语法结构如下。

@H_404_69@ if判断条件;then COMMANDS ... elif判断条件;then COMMANDS ... elif判断条件;then COMMANDS ... else COMMANDS ... fi

if 语句其实还是比较好理解的,它是对某一个条件进行判断,然后根据判断的结构进行其他的操作。稍微有点编程经验的人,都会理解这种简短的分支结构。
if 语句还可以嵌套,可以根据实际生产中的情况,合理的进行嵌套组合。

例子

  • 判断某个变量的值是否在0-5之间

@H_404_69@#这是一个嵌套的if语句 a=3 if["$a"-gt0];then #then也可以写在下一行,这时就可以去掉分号 if["$a"-lt5];then echo"Thevalueof\"a\"liessomewherebetween0and5." fi fi #和下面的结果相同 if["$a"-gt0]&&["$a"-lt5] then echo"Thevalueof\"a\"liessomewherebetween0and5." fi
  • 判断某个学员的学习成绩,并输出。不及格,及格,优秀等信息。

@H_404_69@#读取用户输入的成绩 read-p"Pleaseinputyourscore"score if["$score"-lt60];then echo"不及格" elif["$score"-ge60-a"$score"-lt80];then echo"及格" elif["$score"-ge80-a"$score"-le100];then echo"优秀" fi

条件判断case语句

在一些比较流行的编程语言中也有多分支的条件判断语句,例如C/C++/JAVA中的switch语句,可以根据条件跳转到其中的任意一个分支,很适合用来创建菜单

case 语句的语法如下

@H_404_69@ case"$variable"in "$condition1") command... ;; "$condition2") command... ;; esac

注意

  • 对变量进行引用不是必须的,因为在这里不会进行字符分割

  • 条件测试语句必须以右括号 ) 结束。

  • 每一段代码块都必须以双分号 ;; 结束。

  • 如果测试条件为真,其对应的代码块将被执行,而后整个case代码段结束执行

  • case代码段必须以esac结束(倒着拼写case)。

case支持glob风格的通配

  • *: 任意长度任意字符

  • ?: 任意单个字符

  • []: 指定范围内的任意单个字符

  • a|b: a或b

示例

  • 判断用户输入的 是否是yes或者no(只要是这两个词就可以,忽略大小写)

@H_404_69@ read-p"Pleaseinput[yes/no]"INPUT case$INPUTin [Yy]|[Yy][Ee][Ss]) echo"Yourwordisyes" ;; [Nn]|[Nn][Oo]) echo"Yourwordisno" ;; *) echo"Yourwordiswrong" ;; esac
  • 实现简单的通讯录,输入索引,能够查看详细的信息

@H_404_69@#!/bin/bash#简易的通讯录数据库 #清屏 clear echo-e"ContactList ----------- ChooSEOneofthefollowingpersons: [E]vans,Roland [J]ones,Mildred [S]mith,Julie [Z]ane,Morris"echo read-p"PleasechooSEOnemenu[E|J|S|Z]"person case"$person"in #注意变量是被引用的。 "E"|"e") #同时接受大小写的输入。 echo-e" RolandEvans 4321FlashDr. Hardscrabble,CO80753 (303)734-9874 (303)734-9892fax revans@zzy.net Businesspartner&oldfriend" ;; #注意用双分号结束这一个选项。 "J"|"j") echo-e" MildredJones 249E.7thSt.,Apt.19 NewYork,NY10009 (212)533-2814 (212)533-9972fax milliej@loisaida.com x-girlfriend birthday:Feb.11" ;; *) #缺省设置。 #空输入(直接键入回车)也是执行这一部分。 echo echo"Notyetindatabase." ;; esac echo exit0
  • 查看当前系统的设备架构 是 i386 还是i486 或者是X86_64

@H_404_69@#!/bin/bash #使用命令替换生成"case"变量。 case$(arch)in#$(arch)返回设备架构。 #等价于'uname-m"。 i386) echo"80386-basedmachine" ;; i486) echo"80486-basedmachine" ;; i586) echo"Pentium-basedmachine" ;; i686) echo"Pentium2+-basedmachine" ;; X86_64) echo"X86_64-basedmachine" ;; *) echo"Othertypeofmachine" ;; esac exit0

select

select 语句严格来说不能算作循环,因为它们并没有反复执行代码块。但是和循环结构相似的是,它们会根据代码块顶部或尾部的条件控制程序流。
select 结构的语法如下

@H_404_69@selectvariable[inlist] do command... break done
  • 如果不使用break命令,select 语句将变成无限循环,会重复的执行那些代码段。

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准输出上。

  • select 默认使用提示字串3(Prompt String 3,$PS3,即#?),但同样可以被修改

  • 用户输入菜单列表中的某个数字,执行相应的命令

  • 用户输入被保存在内置变量 REPLY 中

  • 选出一个最喜欢吃的蔬菜

@H_404_69@#!/bin/bash PS3='Chooseyourfavoritevegetable:'#设置提示字串。 #否则默认为#?。 echo selectvegetablein"beans""carrots""potatoes""onions""rutabagas" do echo echo"Yourfavoriteveggieis$vegetable." echo"Yuck!" echo break#如果没有'break',整个代码会一直循环下去,直到按了Ctrl+C为止。 done exit

上面这段代码,会输出如下的效果

@H_404_69@1)beans 2)carrots 3)potatoes 4)onions 5)rutabagas Chooseyourfavoritevegetable:1 Yourfavoriteveggieisbeans. Yuck!

如果省略了[in list]那么select将会使用传入脚本的命令行参数($@)或者传入函数的参数作为 list。可以与for variable [in list]in list被省略的情况做比较。
下面将省略 这个列表来实现上面的例子。 运行效果与上面的结果是一致的。

@H_404_69@#!/bin/bash PS3='Chooseyourfavoritevegetable:' echo choice_of(){ selectvegetable #[inlist]被省略,因此'select'将会使用传入函数的参数作为list。 do echo echo"Yourfavoriteveggieis$vegetable." echo"Yuck!" echo break done } choice_ofbeansricecarrortsradishesrutabagaspinach #$1$2$3$4$5$6 #传入了函数choice_of() exit0

循环

循环,顾名思义就是重复性的执行某一基本操作。在Linux/Bash 中有三种基本循环,for,while,until循环。

for 循环 for arg in [list]

for 循环 可以有以下的列表生成方式

  • 直接给出列表

@H_404_69@forvarinitem1item2...itemN do command1 command2 .... ... commandN done
  • 直接给出一个整数列表

@H_404_69@#直接给出整数列表 forvarin1234...100 do command1 command2 .... ... commandN done #或者直接生成整数列表 forvarin{1..100..2} do command1 command2 .... ... commandN done #或者直接生成整数列表 forvarin$(seq02100)##seq[start[step]]end do command1 command2 .... ... commandN done
  • 能够返回列表的命令

@H_404_69@#直接给出整数列表 forvarin$(ls*) do command1 command2 .... ... commandN done
  • 使用glob 如*.sh,便是当前目录下的.sh文件列表

@H_404_69@forvarin*.sh do command1 command2 .... ... commandN done
  • 直接使用某个变量引用 如$@,$*,或者某个变量

@H_404_69@fileNames="HELLO1HELLO2HELLO3HELLO4" forvarin$fileNames do command1 command2 .... ... commandN done
  • 也可以直接遍历某个数组

@H_404_69@ArrayName=(/etc/*.conf) forvarin${ArrayName[@]} do command1 command2 .... ... commandN done

检查某些指定的文件是否存在

@H_404_69@#!/bin/bash #fileinfo.sh FILES="/usr/sbin/accept /usr/sbin/pwck /usr/sbin/chroot /usr/bin/fakefile /sbin/badblocks /sbin/ypbind"#你可能会感兴趣的一系列文件。 #包含一个不存在的文件,/usr/bin/fakefile。 echo forfilein$FILESdo if[!-e"$file"] #检查文件是否存在。 then echo"$filedoesnotexist."; echo continue#继续判断下一个文件。 fi echo done exit0

while 循环

Bash中的while循环结构与其他编程语言的while循环结构一致的,在循环的开始就判断条件是否满足,如果循环条件为真,就继续执行循环,如果为假则跳出循环。
其语法结构如下

@H_404_69@while[condition] do command1 ... commandN done
  • CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环。

  • 因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正。

  • 进入条件:CONDITION为true

  • 退出条件:CONDITION为false

  • while循环的括号结构不是必须存在的

示例:简单的while循环

@H_404_69@#!/bin/bash var0=0 LIMIT=10 while["$var0"-lt"$LIMIT"] do echo-n"$var0"#-n不会另起一行 var0=`expr$var0+1`#var0=$(($var0+1))效果相同。 #var0=$((var0+1))效果相同。 #let"var0+=1"效果相同。 done#还有许多其他的方法也可以达到相同的效果

示例:多测试条件的while循环

一个while循环可以有多个测试条件,但只有最后的那一个条件决定了循环是否终止。这是一种你需要注意到的不同于其他循环的语法。

@H_404_69@#!/bin/bash var1=unset prevIoUs=$var1 whileecho"prevIoUs-varialbe=$prevIoUs" echo prevIoUs=$var1 #记录下$var1之前的值。 ["$var1"!=end] #在while循环中有4个条件,但只有最后的那个控制循环。 #最后一个条件的退出状态才会被记录。 do echo"Inputvariable#1(endtoexit)" readvar1 echo"variable#1=$var1" done exit0

示例:在 while 循环中结合 read 命令,我们就得到了一个非常易于使用的 while read 结构。它可以用来读取和解析文件

@H_404_69@whilereadvalue#一次读入一个数据。 do echo"Thevalueis$value" done

until

与while循环相反,until循环是其测试条件为真时,跳出循环。也就是说,测试条件为假,进入循环,测试条件为真退出循环。 而且,与其他编程语言不一样的地方在于,until循环的测试条件在循环的顶部。语法如下所示

@H_404_69@until[condition-is-true]docommands(s)...done

示例:读取用户输入,并输出到标准输出上,如果是end结束

@H_404_69@#!/bin/bash END_CONDITION=end until["$var1"="$END_CONDITION"] #在循环顶部测试条件。 do echo"Inputvariable#1" echo"($END_CONDITIONtoexit)" readvar1 echo"variable#1=$var1" echo done

循环控制 break,continue,shift

break [N],continue [N]

break 和 continue 命令的作用和其他编程语言中的作用一样。break 是跳出结束循环,continue是跳出本次循环,进入到下一次循环。
但是,在bash 中,break 和 continue 有一种特殊用法,能够指定跳出其上 N 层的循环,也就是 continue [N]

接下来我们使用一个示例来演break和continue的使用。

@H_404_69@#!/bin/bash #"continueN"命令可以影响其上N层循环。 forouterinIIIIIIIVV#外层循环 do echo; echo-n"Group$outer:" #-------------------------------------------------------------------- forinnerin12345678910#内层循环 do if[["$inner"-eq7&&"$outer"="III"]] then continue2#影响两层循环,包括“外层循环”。 #将其替换为普通的"continue",那么只会影响内层循环。 fi echo-n"$inner"#78910将不会出现在"GroupIII."中。 done #-------------------------------------------------------------------- done exit0

shift [N]

用于将参量列表左移指定次数,默认是左移1次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift。

示例:批量添加用户,并能够根据输入的选项,进行不同的输出

@H_404_69@#使用示例 #./user.sh--addMAGE,WANG,HELLO-v #./user.sh-h #!/bin/bash DEBUG=0 ADD=0 DEL=0 forIin`seq$#`;do case$1in -v|--verbose) DEBUG=1 #是否用来输出详情 shift #参数列表向左移动1个 ;; -h|--help) echo"Usage:`basename$0`--addUSER_LIST--delUSER_LIST-v|--verbose-h|--help" exit0 ;; --add) ADD=1 ADDUSERS=$2 shift2 ;; --del) DEL=1 DELUSERS=$2 shift2 ;; esac done if[$ADD-eq1];then forUSERin`echo$ADDUSERS|sed's@,@@g'`;do #将用户列表分割 ifid$USER&>/dev/null;then [$DEBUG-eq1]&&echo"$USERexists" elseuseradd$USER [$DEBUG-eq1]&&echo"Adduser$USERfinished." fi done fi if[$DEL-eq1];then forUSERin`echo$DELUSERS|sed's@,@@g'`;do ifid$USER&>/dev/null;thenuserdel-r$USER [$DEBUG-eq1]&&echo"Deleteuser$USERfinished" else [$DEBUG-eq1]&&echo"user$USERnotexists." fi done fi

特殊用法

  • 双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作。for循环和while循环都可以使用这种((…))格式

** for 循环 的语法格式如下 **

@H_404_69@for((EXPR1;EXPR2;EXPR3));do COMMAND .... COMMAND done #例如输出1-10 LIMIT=10 for((a=1;a<=LIMIT;a++))#双圆括号语法,不带$的LIMIT do echo-n"$a" done

until循环的语法如下

@H_404_69@until((EXPR));do COMMAND .... COMMAND done #例输出0-10 LIMIT=10 var=0 until((var>LIMIT)) do#^^^^^^没有方括号,没有$前缀。 echo-n"$var" ((var++)) done#012345678910

while循环的语法如下

@H_404_69@while((EXPR1))#双圆括号结构, do done #例如实现1-10的和 LIMIT=10 while((a<=LIMIT))#双圆括号结构, do#+并且没有使用"$"。 echo-n"$a" ((a+=1))#let"a+=1" #是的,就是这样。 #双圆括号结构允许像C语言一样自增一个变量。 done
@H_404_69@#依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line whilereadline;do 循环体 done</PATH/FROM/SOMEFILE

在实际的使用过程中,可以实现多种循环的嵌套,以便实现复杂任务,应当具体情况具体分析。

如何在 for,while 和 until 之间做出选择?我们知道在C语言中,在已知循环次数的情况下更加倾向于使用 for 循环。但是在Bash中情况可能更加复杂一些。Bash中的 for 循环相比起其他语言来说,结构更加松散,使用更加灵活。因此使用你认为最简单的就好。


个人博客地址:http://www.pojun.tech/欢迎访问

猜你在找的Bash相关文章