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
fi

if ! [ -e $1 ];then
    echo "No such file or directory."
    exit 2
fi

if [ -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
EOF

read -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=0

for i in {1..100}; do
    sum=$[ $sum+$i ]
done

echo "$sum"  

示例3:列出/var/log/目录下所有文件的类型;

#!/bin/bash
# 
for option in /var/log/*; do

    if [ -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=0

for i in {1..100}; do
    count=$[ $i % 2 ]

    if [[ $count == 0 ]]; then
        oven=$[ $oven + $i ]
    else
        odd=$[ $odd + $i ]
    fi
done

echo "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) ]
done

echo "$idcount"
  • 练习3:通过脚本参数传递一个目录,计算所有的文本文件的行数之和,并说明文件的总数;
#/bin/bash
#
if [[ $# < 1 ]];then
    echo "A argument is needed."
    exit 1
fi

declare -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=1

while [ $i -le 100 ]; do
    sum=$[ $sum+$i ]
    let i++
done

echo "sum is $sum"

2.1.3.until循环

格式
until CONDITION ; do 
循环体 
循环控制变量 
done 
  • 进入条件:CONDITION测试为“假”
  • 退出条件:CONDITION测试为“真”
示例
  • 示例1:求100以内所有正整数之和
#!/bin/bash
#
declare -i sum=0
declare -i i=1

until [ $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
done

echo "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++
 done

echo "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++
done

echo "odd is $odd"
echo "oven is $oven"
  • 练习2:创建9个用户,user101-user109;密码同用户名, 如果这些用户存在就删除,不存在就创建;
#!/bin/bash

for 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
done

echo "$(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; do

        ID=$(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=0

for((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
=============================
EOF

read -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
done

if [ "$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
=============================
EOF

read -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
done

case $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
userinfo

username=$2
userinfo
练习:

写一个服务框架脚本名为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/$prog

start() {
    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 exists

addusers() {
    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/bash

func() {
    if [ $1 -eq 0 -o $1 -eq 1 ]; then
        echo 1
    else
        echo $[$1*$(func $[$1-1])]
    fi
}

func $1

你可能感兴趣的:(shell高级用法)