shell高级用法

1.if语句
1.1.回顾

  • 在运行脚本前,我们一般先对脚本的语法进行检查,如果脚本有bug,我们再调试脚本;
  • (1)语法检查:bash -n 脚本路径
  • (2)脚本调试:bash -x 脚本路径
1.2.过程式编程语言的执行流程
  • 顺序执行
  • 选择执行
  • 循环执行
  • 选择执行
1.2.1.&&,||
command1 && command2:如果command1正确,也执行command2;如果command1不正确,不执行command2 command1 || command2:如果command1正确,不执行command2;如果command1不正确,执行command2

1.2.2.if语句
  • if单分支语句;
  • if多分支语句
  • if嵌套语句
  • (1)if 多分支语句格式(注意:多个分支只会执行一个,执行首先为真的分支)
if[ 条件1 ]; then 条件1为真执行这里的代码 elif [ 条件2 ]; then 条件2为真执行这里的代码 elif [ 条件3 ]; then 条件3为真执行这里的代码 ...... elif [ 条件n ]; then 条件n为真执行这里的代码 else 如果所有的条件都不满足时,执行这里的代码 fi

  • (2)if嵌套语句格式
if [ 条件 ]; then if [ 条件 ]; then 执行这里的代码 fi fi if [ 条件 ]; then if [ 条件 ]; then 执行这里的代码 fi else if [ 条件 ]; then 执行这里的代码 fi fi

1.3.示例
  • 1.通过脚本参数传递一个文件路径给脚本,判断此文件的类型;
#!/bin/bash # if [ $# -lt 1 ]; then echo "A argument is needed." exit 1 fiif ! [ -e $1 ]; then echo "No such file or directory." exit 2 fiif [ -f $1 ]; then echo "Common file" elif [ -d $1 ]; then echo "Directory" elif [ -L $1 ]; then echo "Symbolic link" elif [ -b $1 ]; then echo "block special file" elif [-c $1 ]; then echo "character special file" elif [-S $1 ]; then echo "Socket file" else echo "Unknow" fi

  • 2.写一个脚本
    (1)传递一个参数给脚本,此参数为用户名 ;
    (2)根据其ID号来判断用户类型:0:管理员 ,1-499:系统用户,500+:为登入用户, 输出用户是那种类型的用户;
#!/bin/bash #(1)传递一个参数给脚本,此参数为用户名 # (2)根据其ID号来判断用户类型:0:管理员 ,1-999:系统用户,1000+:为登入用户if [ $# -lt 1 ]; then echo "A argument is needed." exit 1 fi # 如何输入的用户名存在 # 注意通过命令来判断成功与否不能加[] if id -u $1 &>/dev/null; then userid=$(id -u $1) if [ $userid -eq 0 ]; then echo "Administrator" elif [ $userid -gt 1 -a $userid -lt 499 ]; then echo " System user" elif [ $userid -ge 500 ]; then echo "Login user" fi else echo "NO such user." fi

  • 3.写一个脚本
    (1)列出如下菜单给用户
    disk) show disks info
    mem)show memory info
    cpu)show cpu info
    *)quit
    (2)提示用户给出自己的选择,而后显示对应其选择的相应系统信息
#!/bin/bash cat << EOF disk) show disks info mem)show memory info cpu)show cpu info *)quit EOFread -p "Please input your option:" option# 如果用户输入的是大写,直接将大写转变为小写 option=$(echo $option | tr [A-Z] [a-z])if [ $option == "disk" ]; then fdisk -l elif [ $option == "mem" ]; then free -m elif [ $option == "cpu" ]; then lscpu elif [ $option == "*" ]; then exit 1 else echo "Wrong option" fi

2.循环语句
2.1.bash的循环分类:
  • for循环
  • while循环
  • until循环
2.1.1.for 循环 1.两种格式
  • 第一种格式:遍历列表
  • 第二种格式:控制变量
(1)遍历列表
for 变量 in 列表; do 循环体 done

  • 进入条件:只要列表中有元素,即可进入循环
  • 退出循环:列表中的元素遍历完成
  • 列表生成的方式
1)直接取出 例如: user1 user2 user3 2)整数列表 例如 {1..100} 例如`seq 1 2 100`注意:中间的2表示步长 3)返回列表的命令 `ls /var/log/` 4)glob 例如: /var/log/* 5)变量引用,如:@* ,

示例
  • 示例1:
#!/bin/bash for username in user1 user2 user3; do if id $username &>/dev/null; then echo "$username is already exists." else useradd $username echo "Add &username success" done

  • 示例:2:求100内所有正整数之和;
#!/bin/bash # declare -i sum=0for i in {1..100}; do sum=$[ $sum+$i ] doneecho "$sum"

示例3:列出/var/log/目录下所有文件的类型;
#!/bin/bash # for option in /var/log/*; doif [ -f $option ]; then echo "$option is Common file" elif [ -d $option ]; then echo "$option is Directory" elif [ -L $option ]; then echo "$option is Symbolic link" elif [ -b $option ]; then echo "$option is block special file" elif [-c $option ]; then echo "$option is character special file" elif [-S $option ]; then echo "$option is Socket file" else echo "Unknow" fi done

练习
  • 练习1:分别求100以内所有偶数之和,奇数之和;
#!/bin/bash # declare -i oven=0 declare -i odd=0for i in {1..100}; do count=$[ $i % 2 ]if [[ $count == 0 ]]; then oven=$[ $oven + $i ] else odd=$[ $odd + $i ] fi doneecho "oven is $oven" echo "odd is $odd"

  • 练习2:计算当前用户的id之和;
#!/bin/bash # declare -i idcount=0 userRow=$(wc -l /etc/passwd|cut -d" " -f1)# 这里不能使用{},作为列表,用seq:for i in `seq 1 $userRow`; do for i in $(seq 1 $userRow); do userName=$(cut -d: -f1 /etc/passwd|sed -n "$i"p) idcount=$[ $idcount + $(id -u $userName) ] doneecho "$idcount"

  • 练习3:通过脚本参数传递一个目录,计算所有的文本文件的行数之和,并说明文件的总数;
#/bin/bash # if [[ $# < 1 ]]; then echo "A argument is needed." exit 1 fideclare -i fileCounts=0 declare -i rowsCounts=0# 文件数 fileCounts=$( find $1 -type f | wc -l )for i in `seq 1 $fileCounts`; do #这里的egrep其实可以不要,也可以取出行数 rowCount=$(find $1 -type f -exec wc -l {} \; | cut -d" " -f1 | sed -n "$i"p) rowsCounts=$[ $rowsCounts + $rowCount ] done echo "fileCounts is $fileCounts , rowsCounts is $rowsCounts"

(2)控制变量
for((i=1; i<10; i++)); do 进入循环代码 done

2.1.2.while 循环 格式
while CONDITION ; do 循环体 循环控制变量的修正表达式 done

  • 进入条件:CONDITION测试为“真”
  • 退出条件:CONDITION测试为“假”
示例
  • 示例1:求100以内所有正整数之和;
#!/bin/bash # declare -i sum=0 declare -i i=1while [ $i -le 100 ]; do sum=$[ $sum+$i ] let i++ doneecho "sum is $sum"

2.1.3.until循环 格式
until CONDITION ; do 循环体 循环控制变量 done

  • 进入条件:CONDITION测试为“假”
  • 退出条件:CONDITION测试为“真”
示例
  • 示例1:求100以内所有正整数之和
#!/bin/bash # declare -i sum=0 declare -i i=1until [ $i -gt 100 ]; do sum=$[ $sum+$i ] let i++ done echo "sum to $sum"

练习
  • 练习1:分别使用while for until实现,求100以内所有的偶数之和,100以内奇数之和;
#!/bin/bash # declare -i oven=0 declare -i odd=0#for循环 for i in{1..100}; do count=$[ $i%2 ] if [ $count -eq 0 ]; then oven=$[ $oven+$i ] else odd=$[ $odd+$i ] fi doneecho "odd is $odd" echo "oven is $oven" oven=0 odd=0 i=1#while循环 while [ $i -le 100 ]; do count=$[ $i%2 ] if [ $count -eq 0 ]; then let oven+=$i else let odd+=$i fi let i++ doneecho "odd is $odd" echo "oven is $oven" oven=0 odd=0 i=1#until循环 until [ $i -gt 100 ]; do count=$[ $i%2 ] if [ $count -eq 0 ]; then let oven+=$i else let odd+=$i fi let i++ doneecho "odd is $odd" echo "oven is $oven"

  • 练习2:创建9个用户,user101-user109;密码同用户名, 如果这些用户存在就删除,不存在就创建;
#!/bin/bashfor i in `seq 1 10`; do if id user10"$i" &> /dev/null; then userdel -r user10"$i" else useradd user10"$i" echo "user10$i" | passwd --stdin user10"$i" &> /dev/null echo "create user user10$i" fi done

  • 练习3:打印九九乘法表
#!/bin/bash # 注意:这里的echo 的 -n选项表示不换行 for((i=1; i<9; i++)); do for((j=1; j<=$i; j++)); do echo -n "$j X $i = $[ $j*$i ]" done echo done

  • 练习4:打印逆序的九九乘法表
#!/bin/bash #declare -i k=9 while [ $k -ge 1 ]; do for i in `seq 1 $k| sort -n -r`; do echo -n "$i X $k = $[ $i*$k ]" done echo "" let k-- done

3.循环控制语句
  • continue:跳出本次循环,进入下一轮循环;
  • break:跳出整个循环;
  • sleep:程序睡眠一个时间段;
死循环:条件为true,永远为死循环
3.1.continue语句 格式
while[ 条件1 ]; do 满足条件执行这里的代码 if [ 条件2 ]; then # 跳出当前循环进入下一轮循环 continue fi 满足条件执行这里的代码 done

示例:
  • 求100以内所有的偶数之和;
#/bin/bash # declare -i evensum=0 for i in {1..100}; do if [ $[ $i%2 ] -eq 1 ]; then continue fi let evensum+=$i done echo "evensum is $evensum"

#/bin/bash # declare -i evensum=0 declare -i i=0 while [ $i -lt 100 ]; do let i++ if [ $[ $i%2 ] -eq 1 ]; then continue fi let evensum+=$i done echo "evensum is $evensum"

3.2.break :直接跳出整个循环 格式
while [条件1]; do 执行这里的代码 if [条件2]; then break fi 执行这里的代码 done

  • 死循环:如何创建死循环
while true; do 循环体 done

  • 退出方式:某个测试条件满足的时候,让循环体执行break命令就是
示例:
  • 计算100以内奇数之和
#/bin/bash # description # author declare -i oddsum=0 declare -i i=1 while true; do let oddsum=$oddsum+$i let i=$i+2 if [ $i -gt 100 ]; then break fi done echo "oddsum is $oddsum"

3.3.sleep命令 示例:
  • 每隔3秒钟到系统上获取已经登入的用户的信息,其中,如果sb用户登入了系统,则给QQ发送邮件,主题为“sb user is login”,并退出;
#/bin/bash # while true; do if who | grep "^logstash\>" &> /dev/null; then break; fi sleep 3 doneecho "$(date +"%F %T")logstash logged on" >> /tmp/users.log

#/bin/bash # until who | grep "^logstash\>" &> /dev/null; do sleep 3 done echo "$(date +"%F %T")logstash logged on" >> /tmp/users.log

4.while和for循环的特殊用法
4.1.while循环的特殊用法(遍历文件的行) 格式
while read VARIABLE; do 循环体 done < /PATH/TO/FILE

意思是:依次读取/PATH/TO/FILE文件中的每一行,且将其赋值给VARIABLE变量
示例:
  • 找出ID号为偶数的用户,显示其用户名、ID、及默认的shell;
#/bin/bash # while read line; doID=$(echo $line|cut -d: -f3) if [ $[ $ID%2 ] -eq 0 ]; then username=$(echo $line | cut -d: -f1) shell=$(echo $line | cut -d: -f7) echo "username is $username, ID is $ID , shell is $shell" fi done < /etc/passwd

4.2.for循环的特殊用法 格式
for((控制变量初始化;条件判断表达式;控制变量修正语句));do 循环体 done

示例:
  • 示例1:1到100的和
#/bin/bash # declare -i sum=0for((i=1; i<=100; i++)); do let sum+=$i done echo "sum is $sum"

  • 示例2:打印九九乘法表
#/bin/bash # for((i=1; i<=9; i++)); do for((j=1; j<=i; j++)); do echo -ne "${j}X${i}=$[ ${j}*${i} ]\t" done echo done

5. case语句
语法格式:
case $VARAIBLE in PAT1) 分支1 ; ; PAT2) 分支2 ; ; ... *) 分支n ; ; esac

case支持glob风格的通配符:
*:任意长度的任意字符; ?:任意单个字符; []:任意单个字符; a|b:a或b;

注意:
  • (1)每一个分支的两个分号一定不能少,如果没有两个分号,那么每一个case 的分支都会执行
  • (2)虽然这里的PAT可以只用模式匹配,但是只是支持glob模式的匹配,有:* ? [ ] [^ ] a|b 这几种方式
示例:显示一个菜单给用户
cpu)display cpu information mem)display memory information disk)display disk information quit)quit

  • (1)提示用户给出自己的选择
  • (2)正确的选择给出相应的信息,否则,提示用户,让用户重新选择正确的选项,直到用户选择正确为止
#!/bin/bash # cat << EOF cpu)display cpu information mem)display memory information disk)display disk information quit)quit ============================= EOFread -p "Enter your option:" option# 这里的while,通过read命令循环输入option,使得循环可以继续进行 while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ]; do echo "please input cpu , mem , disk , quit " read -p "Enter your option:" option doneif [ "$option" == "cpu" ]; then lscpu elif [ "$option" == "mem" ]; then free -m elif [ "$option" == "disk" ]; then fdisk -l /dev/sd[a-z] elif [ "$option" == "quit" ]; then echo "quit" exit 0 fi

  • 将上述题目if else示例改写为case的判断,会使得代码的可读性增强
#/bin/bash # cat << EOF cpu)display cpu information mem)display memory information disk)display disk information quit)quit ============================= EOFread -p "Enter your option:" option# 这里的while,通过read命令循环输入option,使得循环可以继续进行 while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ]; do echo "please input cpu , mem , disk , quit " read -p "Enter your option:" option donecase $option in cpu) lscpu ; ; mem) free -m ; ; disk) fdisk -l /dev/sd[a-z] ; ; *) echo "quit" exit 0 ; ; esac

示例:写一个服务框架脚本; $lockfile,值/var/lock/subsys/SCRIPT_NAME
  • (1)此脚本可接受start,stop,restart,status四个参数之一;
  • (2)如果参数非此四者,则提示使用帮助后退出;
  • (3)start,则创建lockfile,并显示启动;stop则删除lockfile,并显示停止;restart,则先删除此文件再创建此文件,而后显示重启完成;status,如果lockfile存在,则显示running,否则,则显示为stopped.
#!/bin/bash # # chkconfig: - 50 50 # description: test service script prog=$(basename $0) lockfile=/var/lock/subsys/$prog case $1 in start) if [ -f $lockfile ]; then echo "$prog is running yet." else touch $lockfile [ $? -eq 0 ] && echo "start $prog finshed." fi ; ; stop) if [ -f $lockfile ]; then rm -f $lockfile [ $? -eq 0 ] && echo "start $prog finshed." else echo "$prog is running yet." fi ; ; restart) if [ -f $lockfile ]; then rm -f $lockfile touch $lockfile echo "start $prog finshed." else touch -f $lockfile echo "start $prog finshed." fi ; ; status) if [ -f $lockfile ]; then echo "$prog is running yet." else echo "$prog is stopped." fi ; ; *) echo "Usage:$prog {start|stop|restart|status}" esac

6.函数:代码重用
  • 模块化编程
  • 结构化编程
  • 注意:定义函数的代码段不会自动执行,在调用时执行,所谓调用函数,在代码中给定函数名称即可,函数名出现的任何位置,在代码执行时候,都会自动替换为函数代码;
6.1.语法格式: 语法一:
function f_name{ 函数体 }

语法二:
f_name(){ 函数体 }

  • 函数的生命周期:每次被调用时,被创建,返回时,终止
  • 其状态返回结果为函数体中运行的最后一条命令的状态结果
  • 自定义状态返回值,需要使用:return
return [0-255] 0: 成功 1-255:失败

6.2.示例:
  • 示例1:给定一个用户名,取得用户名的ID号,和默认的shell
#!/bin/bash #userinfo() { if id "$username" &> /dev/null; then grep "^$username\>" /etc/passwd | cut -d: -f3,7 else echo "No such user." fi }username=$1 userinfousername=$2 userinfo

练习: 【shell高级用法】写一个服务框架脚本名为create_file,接受start 、stop、 restart、 status 四个参数之一(这次用函数实现)
  • (1):如果参数非此四者,则提示使用帮助后退出
  • (2):如果传递start参数,则在/var/log目录下创建一个与脚本名相同的文件,并显示启动。
  • (3):如果传递stop参数,就在/var/log目录下删除一个与脚本名相同的文件 ,并显示停止。
  • (4):如果传递restart参数,就先删除再创建此文件,而后显示重启完成。
  • (5):如果传递status参数, 如果文件存在,则显示running, 否则显示为stopped
#!/bin/bash # # chkconfig: - 50 50 # description: test service script # prog=$(basename $0) FILE=/var/log/$progstart() { if [ -f $FILE ]; then echo "$prog is running yet." else touch $FILE [ $? -eq 0 ] && echo "start $prog OK." fi }stop() { if [ -f $FILE ]; then rm -f $FILE [ $? -eq 0 ] && echo "stop $prog finished." else echo "$prog is not running." fi } status() { if [ -f $FILE ]; then echo "$prog is running" else echo "$prog is stopped." fi }usage() { echo "Usage: $prog {start|stop|restart|status}" }case $1 in start) start ; ; stop) stop ; ; restart) stop start ; ; status) status ; ; *) usage exit 1 ; ; esac

6.3.函数的返回值分为两类 1.函数的执行结果返回值
  • (1):使用echo或printf命令进行输出
  • (2):函数体中调用的命令的执行结果
2.函数的退出状态码
  • (1):默认取决于函数体中执行的最后一条命令的退出状态码
  • (2):自定义:return (相当于脚本中的exit)
6.4.函数可以接受参数
  • 在调用函数的时候可以给函数传递参数;
  • 在函数体中,可以使用$1, $2...引用传递给函数的参数,还可以使用$* 或$@调用所有的参数,$#表示传递的参数的个数;
  • 在调用函数时候,在函数名后面以空白字符分割给定参数列表即可,例如:functionName arg1 arg2 arg3 arg4...
示例:
  • 示例1:添加10个用户,添加用户的功能使用函数实现,用户名作为参数传递给函数。这里有一个注意点,就是如果直接return $? ,在函数外面是不能拿到值的。
#!/bin/bash # # 5: user existsaddusers() { if id $1 &> /dev/null; then return 5 else useradd $1 retval=$? return $retval fi }for i in {1..10}; do addusers ${1}${i} # 在这里需要注意的是:每次函数的放回结果虽然可以使用$?来引用,但是在后面多次用到的时候,一定要将其使用一个变量保存起来,这里使用一个retval=$?保存了函数执行之后的状态码,以后为了规范起见,所有的函数返回的状态码都使用一个变量将其保存起来,如: variable=$? ,这样就不怕以后引用$?是出现错误。 retval=$? if [ $retval -eq 0 ]; then echo "Add user ${1}${i} finished." elif [ $retval -eq 5 ]; then echo "user ${1}${i} exists." else echo "Unkown Error." fi done

练习:
  • 练习1:写一个脚本:要求
  • (1)使用函数实现ping一个主机来测试主机的在线状态,主机地址通过参数传递给函数
  • (2)主程序:测试192.168.7.130 - 192.168.7.139 范围内的各个主机的在线状态
  • (3)如果主机在线,那么打印黄色的Online字符串,如果主机不在线打印红色的Offline字符串
#!/bin/bash # if [ $# -lt 2 ]; then echo "请输入两个主机地址作为IP地址的检测范围" exit 1 fi START=$1 END=$2# 检测IP的范围 Ping(){ ping -c1 -w1 192.168.7.13$1 &> /dev/null &&echo -e "\E[1; 33m 192.168.7.13$1 Online\033[0m" || echo -e "\E[1; 31m 192.168.7.13$1 Offline\033[0m" }for i in `seq $1 $2`; do Ping $i done

  • 练习2:写一个脚本 :要求
    打印NN乘法表,使用函数实现, 例如:给脚本传递了一个11,那么就是打印11 X 11 的乘法表
#!/bin/bash # 注意:这里的echo 的 -n选项表示不换行 for((i=1; i<=$1; i++)); do for((j=1; j<=$i; j++)); do echo -n "$j X $i = $[ $j*$i ]" done echo done

7.变量作用域
  • 局部变量:作用域是函数的生命周期,在函数结束时被自动销毁。定义局部变量的方法:local 变量=值
  • 本地变量:作用域是运行脚本的shell进程的生命周期,因此,其作用域范围是当前的shell脚本程序文件
示例
#!/bin/bash # name=tom# 记得以后为了避免函数中的变量与本地变量同名,如果同名,会使得具备变量修改本地变量的值,使得本地变量的指针直接指向函数中变量的存储空间。为了避免同名产生的错误,以后在函数中定义的变量都使用局部变量,在变量名前面加上一个local关键字 setname() { local name=jerry echo "Function: $name" }setname echo "Shell: $name"

8.递归
  • bash shell中和其他编程语言一样可以使用递归。那什么是递归:程序调用自身技巧称为递归( recursion)
示例:
  • 写一个脚本,给脚本传递一个数值,脚本输出这个数值的阶层。例如:5!= 120
#!/bin/bashfunc() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(func $[$1-1])] fi }func $1

    推荐阅读