bash脚本编程
bash脚本编程: 什么叫编程? 使用人类自然语言或机器语言进行程序源代码书写的过程。 为什么要编程? 为了能够让用户在使用计算机的时候,可以让计算机以非交互的方式完成某些任务;此时,用户需要将此类任务编辑称为一个文件,并且让计算机按照特定书序进行任务读取,从而实现预期的功能; 为了让计算机能够解读此类文件的内容并正确的予以执行,必须将程序源代码文件转换为计算机可以直接识别并使用的二进制格式,此转换过程称为编译;而想要完成编译过程,必须使用特定的编译器工具;因此,无论使用何种编程语言进行程序编写,都必须严格按照编译器所能够识别的特定格式和语法结构进行程序编写; 编程语言: 高级语言: 根据源代码的处理方式分类: 编译型语言: 源代码 --> 编译器(编译) --> [链接器(链接) --> ] 汇编器(汇编) --> 可以执行的二进制代码文件; 解释型语言 源代码 --> 解释器(逐行解释) --> 边解释边执行 根据编程过程中的功能实现是调用库还是调用外部程序文件分类: 完整编程语言: 利用库或编程组件进行编程; 脚本编程语言: 利用解释器调用被选择的外部应用程序; 根据程序的编写规范分类: 过程式语言: 程序 = 指令 + 数据 以指令为中心,围绕指令的功能实现设计数据和数据结构,数据为指令服务; 算法和指令的实现形式: 顺序执行 选择执行 循环执行 面向对象语言: 程序 = 算法 + 数据结构 以数据和数据结构为中心,将数据实例化,围绕数据的需求来部署算法; 类(Class):被实例化的数据; 属性(attibution):同一类中的不同对象的区分依据; 方法(method):类的正确的操作方法; 低级语言: 汇编语言: 机器语言:二进制语言 shell脚本编程——bash脚本编程: 过程式编程语言,解释运行的编程语言,脚本类语言(依靠外部应用程序文件运行) shell脚本到底是什么? 1.纯文本文档——文件中所有存储或包含的指令+数据都是以字符为单位进行存储的; 2.根据用户的需求来解决用户问题的简单或复杂的命令组合体; 3.是一种具有"执行幂等性"的程序实体; 执行幂等性:任何命令的一次执行结果和多次执行结果是一致的; 注意: 很多命令都不具备"执行幂等性",因此在shell脚本中需要使用大量的程序逻辑来判断某个命令是否符合其运行条件,从而避免在运行过程中出现的严重错误; shell脚本中的代码内容如何书写? 1.首行必须是shebang,即:解释器程序的绝对路径,必须占据绝对行首且必须单独占据第一行;在执行脚本时,会根据shebang的指示,启动相应的解释器以解释脚本被诸多的命令; #!/bin/bash #!/bin/sh #!/usr/bin/python #!/usr/bin/perl ... 2.在shell脚本中,除了shebang之外,所有行首为#字符的行,均被解释为注释行;即:解释器只会解释其内容,但并不予以执行; 3.解释器会忽略脚本文件中所有的空白行;空白行指的是:在一行文本中,除了空白字符,空格字符,制表字符之外不具备其他任何类型字符的行; 4.大量的命令和关键字 命令:内部或外部应用程序 关键字:内置于shell,只能在某种特定结构体中执行的命令;keyword; 如:if,else,then,do,while,for,select,until,case,fi,esac,... 5.shell中的所有的特殊功能字符; 注意:所有被编写进shell脚本文档的命令、关键字及符号必须是ASCII编码格式的字符,其他编码格式的字符可以出现在shell脚本文件中,但不具有任何特殊含义; 如何编写shell脚本? 可以利用所有的文本文档编辑工具进行shell脚本编写,如: nano,vi,vim,pico,emacs,... 通常在Linux的各发行版本中,推荐使用vim; 脚本文件的命名方式: 一般情况下,会为脚本设置".sh"的名称后缀;较低版本的编辑工具,会根据文件的后缀名称来识别是否为shell脚本文件;交高版本的文本编辑工具,如vim7,无需过多的关系文件后缀名的问题。 脚本的运行方式: 1.为脚本文件赋予执行权限,可以直接以绝对路径或相对路径的方式运行此文件; # chmod +x /PATH/TO/SOME_SCRIPT_FILE # /PATH/TO/SOME_SCRIPT_FILE 注意:如果脚本文件所在目录路径存储于PATH变量中,则直接以脚本文件名来执行即可; 2.直接使用解释器运行脚本,将脚本文件作为解释器程序的参数; # bash /PATH/TO/SOME_SCRIPT_FILE bash命令的常用选项: -x:使bash在解释脚本的过程展示在标准输出上;一般用于为shell脚本排错 -n:对脚本文件进行预执行,以分析脚本中是否存在语法类错误;如果没有错误,则不输出任何信息;相反,则输出简洁的提示信息;具体的错误定位需要自行判断; 注意:此种方式中,脚本文件是否有执行权限并不是很重要的属性; 注意:以上两种方式,在执行脚本时,会在当前shell中开启一个新的子shell以运行脚本;一般情况下,当脚本运行结束,该子shell也会被随之销毁;因此,所有在脚本中定义的变量,在脚本的末尾处,最好将其明确的撤销; 3.使用source命令运行脚本; # source /PATH/TO/SOME_SCRIPT_FILE # . /PATH/TO/SOME_SCRIPT_FILE 注意: 1.source命令不会在运行脚本时开启子shell,而是在当前shell中运行; 2.使用source命令执行的脚本中不要包括诸如exit类的命令;
利用bash脚本程序实现算术运算: 算术运算操作符: 常用的基本算术运算符: +,-,*,/,%,**
增强型的算术运算符: +=,-=,*=,/=,%= 特殊的增强型算术运算符: ++,-- 算术运算方法: 1.$[expression] 其中的表达式可以是纯数字组成的,也可以使用变量引用变量值;在使用变量时,可以将$符号省略; 示例: # echo $[3+4] NUM1=5 ; NUM2=4 ; echo $[NUM1*NUM2] 2.let VAR=EXPRESSION 根据算术表达式完成算术运算并赋值给指定的变量; 3.$((EXPRESSION)) 其中的表达式可以是纯数字组成的,也可以使用变量引用变量值;在使用变量时,可以将$符号省略; 4.expr ARGU1 ARGU2 ARGU3 其中ARGU1和ARGU3必须是整数数值;ARGU2是算术运算符; 5.echo "EXPRESSION" | bc 6.bc <<< EXPRESSION 变量: 特性:一个变量中仅能存储一个数值; 数组:能够容纳多个数组元素的连续的内存空间; 稀疏数组: 稠密数组: 数组元素:数组中任何一个存放数据的存储单元,其作用相当于一个变量; 数组元素的标识: 索引数组标识:所有的数组元素都是使用数字编号的; 通常数字编号是从0开始的,即:0,1,2,... 关联数组标识:所有的数组元素都可以使用名称(字符串)来标识; 注意:bash4.0以上的版本才有可能支持关联数组; 数组的声明和定义: 1.declare命令: -a to make NAMEs indexed arrays (if supported) 将其后的变量名称声明为索引数组; -A to make NAMEs associative arrays (if supported) 将其后的变量名称声明为关联数组; 示例: 定义稠密数组: # declare -a NAMES=("zhang" "wang" "liu" "li") 定义稀疏数组: # declare -a NAMES2=([0]='zhangsan' [2]='lisi' [5]='mayun') 2.直接使用变量赋值的方式: 定义稠密索引数组: ARRAY_NAME=("value1" "value2" "value3" ...) 定义稀疏索引数组: ARRAY_NAME=([0]="value1" [1]="value2" [4]="value3" ...) 定义关联数组: ARRAY_NAME=([Index_name1]='value1' [Index_name2]='value2' ...) 3.分别定义数组元素: ARRAY_NAME[0]='value1' ARRAY_NAME[1]='value2' ... ARRAY_NAME[N-1]='value[n-1]' 引用数组元素的方式: ${ARRAY_NAME[INDEX]} 注意:如果在引用数组元素时没有给出具体的索引编号,则默认编号为"0",即显示第一个数组元素的数值; 引用整个数组中所有元素: ${ARRAY_NAME[*]} 或者 ${ARRAY_NAME[@]} 引用整个数组的所有元素的索引号: ${!ARRAY_NAME[*]} 或者 ${!ARRAY_NAME[@]} 查看数组中的元素个数(数组长度): ${#ARRAY_NAME[*]} 或者 ${#ARRAY_NAME[@]} 数组切片: ${ARRAY_NAME[*]:offset} //显示包括offset数值所对应表示的位置的元素及其后所有的元素; ${ARRAY_NAME[*]:offset:number} //显示数组中包括offset数值所对应表示的位置的元素及其后number个元素的值; 撤销数组: unset ARRAY_NAME RANDOM变量: 随机数变量:0-32767,整数值; 从熵池中取随机数: 熵池: /dev/random: 两次敲击键盘的时间间隔; 两次IO的时间间隔; ... /dev/urandom:伪熵池 利用应用程序计算得到的随机数;
bash脚本编程的结构: bash脚本编程语言: 脚本类语言 解释型语言 过程式编程语言
过程式编程语言的结构: 顺序执行结构:默认 从上到下,自左而右地执行所有的语句(命令) 选择执行结构: 当条件满足或不满足时,才会执行对应的语句(命令) 循环执行结构: 重复执行某段语句(命令) bash脚本编程语言中也具备上述结构: 顺序执行结构:默认 选择执行结构: 根据给定条件的逻辑判断结果或根据某个可选取的取值范围,进而选择某个分支结构中的命令语句予以执行的方式; if: 选择执行结构的标准,根据条件的逻辑判断结果选择执行的语句内容; case: 选择执行结构的标准,根据符合某特定范围的取值标准选择执行的语句内容; 循环执行结构: 对于特定语句内容,重复执行0次,1次或多次; for:以遍历列表的方式进行循环; while:根据给定条件的逻辑判断结果进行循环;逻辑判断结果为真,才循环;否则,停止循环; until:根据给定条件的逻辑判断结果进行循环;逻辑判断结果为假,才循环;否则,停止循环; select:死循环,即没有默认退出条件的循环;利用循环提供一个可选择的列表;
bash脚本的执行结构之if选择执行结构: if语句: if - if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
if语句的单分支结构: if - if COMMANDS; then COMMANDS; fi 注意:是否会执行then后面的命令,取决于if后面的命令的执行状态返回值: 1.如果其返回值为真,则执行then后面的命令; 2.如果其返回值为假,则不执行then后面的命令; 建议在脚本中的书写格式: if CONDITION ; then STATEMENT ... fi 或 if CONDITION then STATEMENT ... fi if语句的双分支结构: if - if COMMANDS; then COMMANDS; else COMMANDS; fi 注意:是否会执行then后面的命令或else后面的命令,取决于if后面的命令的执行状态返回值: 1.如果其返回值为真,则执行then后面的命令; 2.如果其返回值为假,则执行else后面的命令; 建议在脚本中的书写格式: if CONDITION ; then STATEMENT ... else STATEMENT ... fi 或 if CONDITION then STATEMENT ... else STATEMENT ... fi if语句的多分支结构: if - if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi 注意:是否会执行then后面的命令或else后面的命令,取决于if后面的命令的执行状态返回值或elif后面的命令的执行状态返回值: 1.首先判断if后面的命令的状态返回值是否为真,如果为真,就执行then后的语句;如果为假,就继续判断第一个elif后面的命令的执行状态返回值; 2.第一个elif后面的命令的执行状态返回值为真,就执行第一个elif语句中then后面的命令,否则,就继续判断第二个elif后面的命令的执行状态返回值; 3.以此类推,会判断每个elif后面的命令的执行状态返回值是否为真;如果所有的if和elif后面的命令的执行状态返回值均为假,则执行else后面的语句; 建议在脚本中的书写格式: if CONDITION1 ; then STATEMENT ... elif CONDITION2 ; then STATEMENT ... elif CONDITION3 ; then STATEMENT ... ... else STATEMENT ... fi 或 if CONDITION then STATEMENT ... elif CONDITION2 ; then STATEMENT ... elif CONDITION3 ; then STATEMENT ... ... else STATEMENT ... fi 注意:if的多分支结构,使用场景不多,而且有些时候,可以使用嵌套的单分支或双分支if结构代替if多分支结构; 嵌套的if结构: if CONDITION1 ; then if CONDITION2 ; then if CONDITION3 ; then STATEMENT ... else STATEMENT ... fi else STATEMENT ... fi else STATEMENT ... fi COMMAND1 && COMMAND2 || COMMAND3
示例: 1.写一个脚本,判断某个用户的默认登录shell是否为/bin/bash; #!/bin/bash
USERNAME=$(cut -d: -f1 /etc/shadow | sort -R | head -1) USERSHELL=$(egrep "^$USERNAME>" /etc/passwd | cut -d: -f7) if [ "$USERSHELL" == "/bin/bash" ] ; then echo "${USERNAME}'s login shell is /bin/bash." fi
unset USERNAME USERSHELL
2.写一个脚本,判断某个用户的默认登录shell是否为/bin/bash,如果不是,显示其登录shell #!/bin/bash
USERNAME=$(cut -d: -f1 /etc/shadow | sort -R | head -1) USERSHELL=$(egrep "^$USERNAME>" /etc/passwd | cut -d: -f7) if [ "$USERSHELL" == "/bin/bash" ] ; then echo "${USERNAME}'s login shell is /bin/bash." else echo "${USERNAME}'s login shell is ${USERSHELL}" fi
unset USERNAME USERSHELL
bash脚本编程之用户交互使用: 位置参数变量: $0:命令的本身,对于脚本而言,就是该脚本的路径; $1,$2,...$N:脚本后面通过命令行给脚本传递的命令行参数; N>9时,引用该位置变量时需要加{},即:${10}
特殊变量: $@:给出的所有位置参数的列表,当使用双引号引用时,每个参数作为单独的字符串存在; $*:给出的所有位置参数的列表,当使用双引号引用时,整个参数列表被当做一个字符串; $#:表示出去$0之外,整个命令行中有多少个参数; read命令: Read a line from the standard input and split it into fields. read - read [-a array] [-p prompt] [-t timeout] [name ...] -a array:定义索引数组; -p prompt:给用户输出提示信息; -t timeout:用户输入的超时时间; name:变量或数组的名称;如果省略此内容,bash会将read读到的信息直接保存到内置的名为REPLY变量中; 注意: linux哲学思想之一:尽量不与用户交互; 在使用read命令时,通常会使用-t选项来指定与用户交互的时间,一旦时间超过预定时间,脚本中后续的命令内容会自动被执行;因此,通常需要在后面判断通过read赋值的变量值是否为空,如果为空,可能需要为该变量提供默认值; read -t 5 VAR1 [ -z $VAR1 ] && VAR1=value1
管理用户脚本: 脚本可以接受两个参数,第一个参数为-a或-d,第二个参数为用户名;如果第一个参数是-a,则创建其后面参数命名的用户;如果第一个参数为-d,则删除其后面参数命名的用户;
#!/bin/bash
if [ $# -ne 2 ] ; then echo "Make sure provide TWO arguments." exit 5 fi
if [ $1 == '-a' ] ; then if ! id $2 &> /dev/null ; then useradd $2 &> /dev/null echo $2 | passwd --stdin $2 &> /dev/null echo "User $2 created succesfully and password changed to it's username." else echo "$2 exists already." fi elif [ $1 == '-d' ] ; then if id $2 &> /dev/null ; then userdel -r $2 &> /dev/null echo "User $2 delete finished." else echo "$2 does not exist yet." fi else echo "Usage: $(basename $0) -a USERNAME | -d USERNAME" exit 6 fi
改进版:使用read命令; #!/bin/bash
if [ $# -ne 1 ] ; then echo "Make sure provide ONE argument." exit 5 fi
if [ $1 == '-a' ] ; then read -p "Please input a username for creating: " USERNAME if ! id $USERNAME &> /dev/null ; then useradd $USERNAME &> /dev/null echo $USERNAME | passwd --stdin $USERNAME &> /dev/null echo "User $USERNAME created succesfully and password changed to it's username." else echo "$USERNAME exists already." fi elif [ $1 == '-d' ] ; then read -p "Please input a username for deleting: " USERNAME read -t 5 -p "confirm? Input 'yes' to continue: " CHOICE [ -z $CHOICE ] && CHOICE='no'
if [ $CHOICE == 'yes' ] ; then if id $USERNAME &> /dev/null ; then userdel -r $USERNAME &> /dev/null echo "User $USERNAME delete finished." else echo "$USERNAME does not exist yet." fi else echo echo "$USERNAME is not deleted." fi else echo "Usage: $(basename $0) { -a | -d }" exit 6 fi
写脚本解决问题: 1.判断用户通过命令行给的一个参数是否为整数。
#!/bin/bash
if [ $# -ne 1 ] ; then echo "Make sure provide ONE digit." exit 5 fi
if ! [[ $1 =~ [^[:digit:]] ]] ; then if [[ $1 =~ ^[[:digit:]]+$ ]] ; then echo "$1 is a pure digit." else echo "$1 is not a digit." fi
循环执行结构: 循环:将某一段代码或命令重复执行0次,1次或多次;
一个好的循环结构,必须要包括两个重要的环节: 1.进入循环的条件: 在符合要求或满足条件时才开始循环; 2.退出循环的条件 达到某个要求或符号某个条件时需要结束或终止循环的执行; for循环: 1.遍历列表的循环: Execute commands for each member in a list. for - for NAME [in WORDS ... ] ; do COMMANDS; done 建议在脚本中的书写格式: for VAR_NAME in LIST ; do 循环体 done 或 for VAR_NAME in LIST do 循环体 done 注意: VAR_NAME:任意指定的变量名称,变量的值是从LIST中遍历获取的各个元素; LIST:for循环需要遍历的列表;可以通过以下方式生成列表: 1.直接给出列表; 2.纯整数列表: 1) 花括号展开: {FIRSTNUMM..LASTNUM} {FIRST,SECOND,THIRD,....,LAST} 2) seq命令 seq [OPTION]... LAST seq [OPTION]... FIRST LAST seq [OPTION]... FIRST INCREMENT LAST 3.花括号展开: {FIRST..LAST} 4.命令的执行结果: ls /etc grep /PATH/TO/SOMEFILE 5.GLOBBING 6.某些特殊变量的值: $*,$@ 循环体: 一般来说,循环体中应该包括能够用到VAR_NAME变量的值的命令或命令的组合;如果循环体中的命令并没有用到VAR_NAME变量的值的话,列表的元素个数就是此次for循环的次数;
写一个脚本,计算1到100的数字之和; #!/bin/bash
declare -i SUM=0 for I in {1..100} ; do SUM=$[SUM+I] done echo "The summary is: $SUM"
给脚本传递一个整数,分别计算该整数以内所有偶数之和以及奇数之和。 [root@localhost ~]# vim q.sh #!/bin/bash declare i sum=0 sum1 if [[ $1 =~ [^[:digit:]] ]] ;then echo "$1should be an integer" exit 5 fi if [ "$1" -eq 1 ] ; then echo "奇数和为1" exit 5 fi if [ "$1" -eq 2 ] ; then echo "偶数和为2" exit 5 fi
for i in $(seq 1 2 $1) ; do sum=$[sum+i]
done echo "奇数和为$sum" for i in $(seq 2 2 $1 ) ; do sum1=$[sum1+i] done echo "偶数的和为$sum1"
结果:[root@localhost ~]# bash q.sh 3 奇数和为4 偶数的和为2 [root@localhost ~]# bash q.sh 5 奇数和为9 偶数的和为6
写一个脚本,能够通过-a或-d选项添加或删除一个或多个用户账户; #!/bin/bash
if [ $# -lt 2 ] ; then echo "Make sure provide more than TWO arguments." exit 5 fi
if [ $1 == '-a' ] ; then shift for I in "$@" ; do if ! id $I &> /dev/null ; then useradd $I &> /dev/null echo $I | passwd --stdin $I &> /dev/null echo "User $I created succesfully and password changed to it's username." else echo "$I exists already." fi done elif [ $1 == '-d' ] ; then shift for J in "$@" ; do if id $J &> /dev/null ; then userdel -r $J &> /dev/null echo "User $J delete finished." else echo "$J does not exist yet." fi done else echo "Usage: $(basename $0) -a UNAME1 [UNAME2 ...] | -d UNAME1 [UNAME2 ...]" exit 6 fi
总结: 1.进入循环的条件:LIST中尚有未被取尽的元素; 2.退出循环的条件:LSIT中的元素被取尽; 3.for循环几乎不会出现死循环; 4.在执行循环的过程中,需要将整个LIST载入内存,因此,对于大列表来说,可能会消耗较多的内存及cpu资源;
计算指定数字范围内自然数的和: #!/bin/bash
declare -i SUM=0 read -p "Please input TWO integer: " INT1 INT2 if [[ $INT1 =~ [^[:digit:]] ]] ; then echo "$INT1 must be an integer." exit 5 fi
if [[ $INT2 =~ [^[:digit:]] ]] ; then echo "$INT2 must be an integer." exit 5 fi
if [ $INT1 -gt $INT2 ] ; then for I in $(seq $INT2 $INT1) ; do SUM=$[SUM+I] let SUM+=$I done echo "The summary is: $SUM" else for I in $(seq $INT1 $INT2) ; do SUM=$[SUM+I] let SUM+=$I done echo "The summary is: $SUM" fi
写一个脚本,打印有""组成的倒置的等腰三角形; ********* 1行,0个空白字符,9个 ******* 2行,1个空白字符,7个* ***** 3行,2个空白字符,5个* *** 4行,3个空白字符,3个* * 5行,4个空白字符,1个*
N行,N-1个空白字符,2*(总行数-当前行号)+1 个*
#!/bin/bash
if [ $# -ne 1 ] ; then echo "Usage: $(basename $0) INTEGER" exit 5 fi
if [[ $1 =~ [^[:digit:]] ]] ; then echo "Usage: $(basename $0) INTEGER" exit 6 fi
LINENUM=$1 for I in $(seq $LINENUM) ; do for J in $(seq $[I-1]) ; do echo -n " " done for K in $(seq $[2*(LINENUM-I)+1]) ; do echo -n "*" done echo done
给脚本传递一个整数作为参数,显示出所有比该数字小的且能被3整除的整数。 [root@localhost ~]# vim 666 #!/bin/bash
if [ $# -ne 1 ] ; then echo "please provide one argument" exit 5 fi if [[ $1 =~ [^[:digit:]] ]] ;then echo "$1should be an integer" exit 5 fi for i in $(seq 1 $1) ; do if (( i%3 == 0)) ; then echo "$i" fi done
结果:[root@localhost ~]# bash 666 15 3 6 9 12 15
打印九九乘法表 第一行:1个 第二行:2个 ... 第九行:9个
#!/bin/bash
for I in {1..9} ; do for J in $(seq $I) ; do echo -ne "$I×$J=$[I*J]\t" done echo done
#!/bin/bash
for (( I=1 ; I<=9 ; I++ )) ; do for (( J=1 ; J<=I ; J++ )) ; do echo -ne "$J×$I=$[I*J]\t" done echo done
以上两个例子,均使用for循环的嵌套;往往需要两层的循环嵌套才能打印出平面效果;外层的for循环,负责控制行数输出;内层的for循环,负责控制每一行中各个列的输出; 2.通过控制变量实现for循环: for (( exp1; exp2; exp3 )); do COMMANDS; done 可以在脚本中写成如下格式: for (( exp1; exp2; exp3 )); do COMMANDS done exp1:表达式1,为指定的变量赋初始值; exp2:表达式2,此次循环的退出条件; exp3:表达式3,指定的变量的值的变化规律;
计算从1到100的自然数的和; #!/bin/bash
for (( I=1 ; I<=100 ; I++ )) ; do let SUM+=$I done echo "The summary is: $SUM"
编程思想: 将人类的自然语言转换成程序的代码语言的方式;
DevOPS 不会开发的运维是没有出路的。
流程图
case选择分支结构: case: case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac 在脚本中使用的case的结构: case ${VAR_NAME} in PATTERN1) COMMAND ... ;; PATTERN2) COMMAND ... ;; ... esac PATTERN(模式)可以是下列几类字符: 1.普通的文本字符; 2.Globbing风格的通配符; *:任意长度的任意字符 ?:任意单个字符; []:指定范围内的任意单个字符; [^]:指定范围以外的任意单个字符; 3.| 或字符 写一个脚本: 判断用户利用脚本删除某文件时,是否执行删除操作;
#!/bin/bash
if [ -e $1 ] ; then echo -e "\033[5;1;31mDanger!\033[1;31mAre you sure to delete it? [yes or no] \033[0m" read -t 5 CHOICE [ -z $CHOICE ] && CHOICE=no case $CHOICE in yes) rm -rf $1 ;; no) echo "Right choice." ;; esac else echo "$1 does not exist." fi
#!/bin/bash
if [ -e $1 ] ; then echo -e "\033[5;1;31mDanger!\033[0m\033[1;31mAre you sure to delete it? [yes or no] \033[0m" read -t 5 CHOICE [ -z $CHOICE ] && CHOICE=no if [ "$CHOICE" == 'yes' ] ; then rm -rf $1 elif [ "$CHOICE" == 'no' ] ; then echo "Right choice." fi else echo "$1 does not exist." fi
if的多分支结构和case的选择分支结构的异同: 相同点: 1.判断的条件为真时,才会执行对应分支中的语句;条件为假,就跳过不执行; 2.都可以设置默认分支语句,即:所有给定的条件的判定结果都为假时才会执行的语句;
不同点: 1.if是根据命令的执行状态返回值的真或假来判断是否该执行某个分支中的语句;case是根据变量中保存的值与指定的模式匹配的结果的真或假来判断是否该执行某个分支中的语句; 2.if的每个分支中无需单独的结束标记,case的每个分支都必须以;;结束;
编写管理用户账户的脚本,第四版,利用case语句+for循环,同时接受创建和删除用户的操作;
#!/bin/bash
if [ $# -lt 1 ] ; then echo -e "Usage: $(basename $0) options... USERLIST\n" echo -e " Options: " echo -e " -a,--add: \vAdd some users from USERLIST." echo -e " -d,--delete: \vDelete some users from USERLIST." echo -e " -h,--help: \vPrint help informationn." echo -e " -v,--verbose: \vPrint more informationn about manage users." echo echo -e " USERLIST FORMAT: " echo -e " USERNAME1,USERNAME2,...,USERNAMEN" exit 5 fi
ADDUSER=0 DELUSER=0 DEBUG=0
for I in $(seq $#) ; do if [ $# -ne 0 ] ;then case $1 in -h|--help) echo -e "Usage: $(basename $0) options... USERLIST\n" echo -e " Options: " echo -e " -a,--add: \vAdd some users from USERLIST" echo -e " -d,--delete: \vDelete some users from USERLIST" echo -e " -h,--help: \vPrint help informationn" echo -e " -v,USERNAMEN" exit 0 ;; -v|--verbose) DEBUG=1 shift ;; -a|--add) ADDUSERLIST=$2 ADDUSER=1 shift 2 ;; -d|--delete) DELUSERLIST=$2 DELUSER=1 shift 2 ;; *) echo -e "Usage: $(basename $0) options... USERLIST\n" echo -e " Options: " echo -e " -a,USERNAMEN" exit 6 ;; esac fi done
if [ $ADDUSER -eq 1 ] ; then for J in $(echo $ADDUSERLIST | tr ',' ' ') ; do if ! id $J &> /dev/null ; then useradd $J &> /dev/null echo $J | passwd --stdin $J &> /dev/null [ $DEBUG -eq 1 ] && echo "Create user $J successfully." else echo "$J exist already." fi done fi
if [ $DELUSER -eq 1 ] ; then for J in $(echo $DELUSERLIST | tr ',' ' ') ; do if id $J &> /dev/null ; then userdel -r $J &> /dev/null [ $DEBUG -eq 1 ] && echo "Delete user $J finished." else echo "$J does not exist yet." fi done fi
while循环结构 while: while COMMANDS; do COMMANDS; done
在脚本中可以写成下列结构: while CONDITION ; do COMMANDS done while循环进入循环的条件:CONDITION逻辑判断结果为真; while循环退出循环的条件:CONDITION逻辑判断结果为假;
until循环结构 until: until COMMANDS; do COMMANDS; done
在脚本中可以写成下列结构: until CONDITION ; do COMMANDS done until循环进入循环的条件:CONDITION逻辑判断结果为假; until循环退出循环的条件:CONDITION逻辑判断结果为真;
注意: 1.while CONDITION 相当于 until ! CONDITION 2.while和until循环结构中,没有变量自增或自减的变化方法,因此需要使用语句手动给出变量的变化方式;
写一个脚本,使用while或until循环,计算100以内整数的和; #!/bin/bash
declare -i I=0 until [ $I -eq 100 ] ; do let I++ let SUM+=$I done
echo $SUM
#!/bin/bash
declare -i I=0 while [ $I -lt 100 ] ; do let I++ let SUM+=$I done
echo $SUM
循环控制语句: continue continue: continue [n] Resume for,or until loops. 提前结束第n层当前循环,直接进入下一轮条件判断,如果条件判断结果仍然满足循环进入条件,则开启下一轮循环;
break break: break [n] Exit for,or until loops. 提前结束第n层的循环,而且不再继续后续循环;
while和until的两种特殊循环使用方法: 1.无限循环方法: while true ; do COMMANDS done
until false ; do COMMANDS done
猜数字游戏: #!/bin/bash
NUMBER=$[RANDOM%100+1] while true ; do read -p "Input a number: " INPTNUM if [ $INPTNUM -gt $NUMBER ] ; then echo "Too big" elif [ $INPTNUM -lt $NUMBER ] ; then echo "Too small" else echo "Yes! you WIN. That's $NUMBER." break fi done
#!/bin/bash
NUMBER=$[RANDOM%100+1] until false ; do read -p "Input a number: " INPTNUM if [ $INPTNUM -gt $NUMBER ] ; then echo "Too big" elif [ $INPTNUM -lt $NUMBER ] ; then echo "Too small" else echo "Yes! you WIN. That's $NUMBER." break fi done
注意:在此类循环结构中,需要适当的添加continue或break控制语句,以使得无限循环可控;
2.实现遍历功能的while和until循环结构: while read LINES ; do COMMANDS done < /PATH/FROM/SOMEFILE until ! read LINES ; do COMMANDS done < /PATH/FROM/SOMEFILE
注意:在做遍历循环时,建议使用for;
select循环结构 select: select NAME [in WORDS ... ;] do COMMANDS; done Select words from a list and execute commands.
select循环也是一种遍历列表的方式创建一个可视化菜单,每个列表项都有一个数字编号与之对应,供用户选择使用;而用户只需要选择其编号即可; select是一种默认无限循环结构,因此,必须在循环体中为select提供退出循环的条件,通常可以使用break或exit命令实现; 通常情况下,select循环会和case一起使用,已进行合理的取值判断; 在脚本中实现的格式: select VAR_NAME in LIST ; do COMMANDS done
写一个脚本,显示以/bin/bash为默认shell的用户的ID信息; #!/bin/bash
select I in $(awk -F : '//bin/bash$/{print $1}' /etc/passwd) quit ; do case $I in quit) exit ;; *) echo "The UID of $I is $(id -u $I)" ;; esac done
bash脚本编程——函数 f(x)=y f --> function
对于bash来说,函数就是由命令和语句结构构成的能够实现特定功能的集合; 为什么要用函数? 在bash脚本的编写过程中,有可能会出现重复且不做任何改变的代码内容,如果这类内容完全依靠原始代码来书写的话,不易于排错和优化;因此,我们可以选择将此类代码封装在函数中,在适当的场景中可以重复调用执行; 像此类被封装起来的代码块,通常称其为模块,也可以称为函数; function --> 函数 注意: 1.想要使用函数,必须在使用前定义出来; 2.如果在某个bash脚本中包含了函数体,默认函数体中的各命令和语句不会执行的;只有在调用函数名的时候,才会执行函数体中的命令和语句; 3.通常需要重复执行的代码块或命令集,可以封装成函数; 4.被调用的函数只能在调用函数的shell中被执行; 定义函数的方法: 函数由两部分组成: 函数名 + 函数体 函数名:调用函数时所使用的字符串标识;在一个执行环境中,函数名不允许重复定义; 函数体:能够实现特定独立功能的shell命令或结构化语句块; 定义的语法: 语法一: function func_name { func_body } 语法二: func_name() { func_body } 注意:在语法二的格式中,func_name和()之间绝对不能有空白字符存在; 注意:函数可以在脚本中被定义,也可以在当前shell中通过交互式环境定义; 函数的使用: 函数在定义的时候,其函数体中包含的所有命令或结构化语句都不会被执行;只有在函数被调用时,才能执行其函数体中的各命令和语句; 调用方式:在命令行会脚本中,通过直接给出函数名的方式进行函数调用; 通常可以将常用的函数存放在专门用于保存函数的文件中;如果想要调用这个文件中已经被定义保存的函数,只需要在命令行或脚本中,使用source命令(.命令)加载文件内容到当前shell中,然后再直接使用函数名调用函数即可; 函数的撤销: unset命令 格式:# unset func_name 注意:可以使用set命令查看当前已经定义并生效的函数; 函数的返回值: 两种返回值: 函数的执行结果返回值: 1.在函数体中添加的命令有标准输出; 2.在函数体中使用echo或printf命令强制输出返回信息; 函数的执行状态返回值: 1.默认情况下,其状态返回值为函数体中最后一条命令的状态返回值; 2.自定义状态返回值(退出码): return命令 return: return [n] Return from a shell function. n:0-255 (1,127为系统保留的状态码,尽量不用) 0:表示无错误返回; 1-255:表示有错误返回; 注意:在函数被调用执行时,一旦遇到return命令,则不会再继续执行函数体中其他的后续命令,立刻结束此次函数的调用执行; 函数的生命周期: 一般来讲,从函数被调用时开始,直到函数体中所有的命令和结构化语句全部执行完成或者遇到return命令,函数的调用结束; 函数的实参: 对应bash函数来说,没有形参,只有实参; bash函数的实参是使用$1,$2...位置变量来提供数据的; func_name pos1 pos2 ... 可以使用$@或$*表示全部的参数列表; 可以使用$#计算参数的个数; 注意:为函数提供参数时使用的位置变量,是调用函数名时在函数名后面的对应位置上的参数信息;与脚本的位置参数不是一回事; 变量: 函数被调用时,必须在某特定的shell中被调用,因此,函数中可以基础并识别出环境变量和由调用函数shell定义的本地变量; 在函数中还可以定义局部变量;而局部变量仅在函数的生命周期内有效;在结束函数执行之前,应该撤销所有该函数定义的局部变量; 局部变量的定义方法: local VAR_NAME=VALUE 变量的替换方式: 前提:定义环境变量: export MYVAR=qhdlink
#!/bin/bash
testvar() { local MYVAR=chinalink echo "Internal function: $MYVAR" } echo "Global variables: $MYVAR" MYVAR=link echo "External function,$MYVAR" testvar
函数的递归调用: 广义:在一个函数中调用另一个函数; 狭义:在函数体中调用函数自身; 直接调用: func1(){ func1 } 间接调用: func2(){ func1 }
func1(){ func2 }
函数直接递归调用示例1: 计算某个数字的阶乘; 利用for循环: #!/bin/bash
fact=1 if [ $1 -eq 0 ] ; then echo "0! is $fact" elif [ $1 -eq 1 ] ; then echo "1! is $fact" else for I in $(seq $1) ; do let fact=$[fact*$I] done echo "${1}! is $fact" fi
fact(){ if [ $1 -eq 0 ] || [ $1 -eq 1 ] ; then echo 1 else echo "$[$1*$(fact $[$1-1])]" fi }
echo "${1}! is $(fact $1)"
示例2: 斐波那契数列(黄金分割数列)
1 1 2 3 5 8 13 21 34 55 ... N=N-1 + N-2
#!/bin/bash
fabonacci(){ if [ $1 -eq 1 ] || [ $1 -eq 2 ] ; then echo -n "1 " else echo -n "$[$(fabonacci $[$1-1])+$(fabonacci $[$1-2])] " fi }
for I in $(seq $1) ; do fabonacci $I done echo
示例三: 汉诺塔
#!/bin/bash
step=0 move(){ let step++ echo "$step: move disk $1 $2 --> $3" }
hanoi(){ if [ $1 -eq 1 ] ; then move $1 $2 $4 else hanoi "$[$1-1]" $2 $4 $3 move $1 $2 $4 hanoi "$[$1-1]" $3 $2 $4 fi }
hanoi $1 A B C