众所周知,面向过程的语言基本上都有如下三种流程控制结构:

  • 顺序结构

  • 选择结构(if、case)

  • 循环结构(for、while、until)

因为默认法则为顺序结构,逐条执行各语句,所以本篇文章我们不讲顺序结构,只讲选择结构和循环结构


一、选择结构

1.if循环

(1).单分支if语句

if 判断条件; then
    statement1
    statement2
    ...
fi

例:获取一个用户的UID,如果它为0,那么就输出一行字符串"Admin"

#!/bin/bash
# Author:soy sauce
# Date:2015-10-31 16:04
# Description:Judge the UID
# Version 1.0

NAME=root

if [ `id -u $NAME` -eq 0 ]; then
    echo "Admin."
fi

[root@soysauce scripts]# ./user.sh 
Admin.

(2).双分支if语句

if 判断条件; then
    statement1
    statement2
    ...
else
    statement3
    statement4
    ...
fi

例:给定一个用户,获取其密码警告期限;而后判断用户密码可使用期限是否已经小于警告期限

#!/bin/bash
# Author:soy sauce
# Date:2015-10-31 16:54
# Description:passwd for warning
# Version 1.0

User=user1
File=/etc/shadow

Maxday=`grep $User $File | cut -d: -f5`
Lastday=`grep $User $File | cut -d: -f3`
Warnday=`grep $User $File | cut -d: -f6`
Second=`date +%s`
Today=$[$Second/86400]
Useday=$[$Today-$Lastday]
Freeday=$[$Maxday-$Useday]

if [ $Freeday -lt $Warnday ]; then
    echo "Warning."
else
    echo "Ok."
fi

[root@soysauce scripts]# ./warning.sh 
Ok.

(3).多分支的if语句

if 判断条件1; then
    statement1
    ...
elif 判断条件2; then
    statement2
    ...
elif 判断条件3; then
    statement3
    ...
else
    statement4
    ...
fi

例:传递一个参数(单字符就行)给脚本,如参数为q、Q、quit或Quit,就退出脚本;否则,就显示用户的参数

#!/bin/bash
# Author:soy sauce
# Date:2015-10-31 20:26
# Description:Quit the shell script v2
# Version 1.0
if [ $1 = 'q' ];then
    echo "quiting"
    exit 0
elif [ $1 = 'Q' ];then
    echo "quiting"
    exit 0  
elif [ $1 = 'quit' ];then
    echo "quiting"
    exit 0 
elif [ $1 = 'Quit' ];then
    echo "quiting"
    exit 0  
else
    echo $1
    exit 5
fi

[root@soysauce scripts]# ./string.sh xyz
xyz
[root@soysauce scripts]# ./string.sh q
Quiting...
[root@soysauce scripts]#

2.case...in esac

case SWITCH in 
value1)
    statement
    ...
    ;;
value2)
    statement
    ...
    ;;
*)
    statement
    ...
    ;;
esac

例:写一个脚本,可以接受选项及参数,而后能获取每一个选项,及选项的参数,并能根据选项及参数做出特定的操作,比如:

adminusers.sh --add tom,jerry --del tom,blair -v|--verbose -h|--help

#!/bin/bash
# Author:soy sauce
# Date:2015-11-22 13:04
# Description:add delete user
# Version 1.0

DEBUG=0
ADD=0
DEL=0

Adduser() {  
for I in $AddUserList; do
   if id $I &> /dev/null; then
       echo "$I exists."
   else
       useradd $I
       echo "$I" | passwd -stdin $I &> /dev/null
       [ $DEBUG -eq 1 ] && echo "Add $I finished."
   fi
done
}

Deluser() {  
for I in $DelUserList; do
   if ! id $I &> /dev/null; then
echo "$I not exists."
   else
       userdel -r $I
       [ $DEBUG -eq 1 ] && echo "Delete $I finished."
   fi
done
}

if [ $# -lt 1 ]; then
    echo "Usage: `basename $0` --add USER1,USER2...|--del USER1,USER2...|-v|--verbose|-h|--help"
    exit 4
fi

for I in `seq 1 $#`; do
 if [ $# -ge 1 ]; then 
  case $1 in
  -v|--verbose)
    DEBUG=1
    shift
    ;;
  -h|--help)
    echo "Usage: `basename $0` --add USER1,USER2...|--del USER1,USER2...|-v|--verbose|-h|--help"
    exit 0
    ;;
  --add)
    ADD=1
    AddUser=$2
    shift 2
    ;;
  --del)
    DEL=1
    DelUser=$2
    shift 2
    ;;
  *)
    echo "Usage: `basename $0` --add USER1,USER2...|--del USER1,USER2...|-v|--verbose|-h|--help"
    exit 6
    ;;
  esac
 fi
done

AddUserList=`echo "$AddUser"|tr  ',' ' '`
DelUserList=`echo "$DelUser"|tr  ',' ' '`
 
[ $ADD -eq 1 ] && Adduser 
[ $DEL -eq 1 ] && Deluser

[root@soysauce scripts]# ./manageuser.sh --add user1,user2,user3 --del user1 -v 
Add user1 finished.
Add user2 finished.
Add user3 finished.
Delete user1 finished.



二、循环结构

1.for循环

(1).for...do;done

for 变量 in 列表; do
  循环体
done

例:编写一个脚本,能够接受一个参数:--add: 添加指定用户,逗号隔开;--del:删除指定用户,逗号隔开,其它:退出;

如adddel.sh --add tom,jerry,hive

#!/bin/bash
# Author:soy sauce
# Date:2015-10-31 22:06
# Description:Add user1-10
# Version 1.0

UserList=`echo $2 | tr ',' ' ' `

if [ $# -ne 2 ]; then
    echo "Usage:`basename $0` --add USER1,USER2,USER3...|--del USER1,USER2,USER3..."
    exit 8
fi

for I in $UserList; do
  if [ $1 == '--add' ]; then 
     if id $I &> /dev/null; then
            echo "$I exists."
     else
            useradd $I
            echo "$I" | passwd --stdin $I &> /dev/null
            echo "Add $I finished."
     fi
  elif [ $1 == '--del' ]; then
      if ! id $I &> /dev/null; then
            echo "$I not exist."
      else
            userdel -r $I
            echo "Add $I finished."
      fi
  else
            echo "Usage:`basename $0` --add|--del"
            exit 8
  fi
done

[root@soysauce scripts]# ./adddel.sh --add user1,user2,user3
Add user1 finished.
Add user2 finished.
Add user3 finished.
[root@soysauce scripts]# tail -3 /etc/passwd
user1:x:500:500::/home/user1:/bin/bash
user2:x:501:501::/home/user2:/bin/bash
user3:x:502:502::/home/user3:/bin/bash

(2).for((变量初始值;循环条件;变量修正))

例:计算从1加到100的和

#!/bin/bash
# Author:soy sauce
# Date:2015-11-22 15:16
# Description:calculator 1 to 100
# Version 1.0

declare -i SUM=0

for((I=1;I<=100;I++)); do
    let SUM+=$I
done
 
echo "The sum is $SUM."

[root@soysauce scripts]# ./cal.sh 
The sum is 5050.

2.while

(1).while...do;done

while CONDITION; do
  statment
  ...
done

例:写一个脚本,让用户选择想显示的信息,知道用户输入quit之后才退出

#!/bin/bash
# Author:soy sauce
# Date:2015-11-24 19:05
# Description:show some information
# Version 1.0

cat << EOF
d|D) show disk usages.
m|M) show memory usages.
s|S) show swap usages.
quit) quit.
EOF

echo -en  "\e[31mPlease input your choice:\e[0m " 
read Choice

while [ $Choice != 'quit' ]; do
   case $Choice in
   d|D)
       /bin/df -hP
       ;;
   m|M)
       free -m | sed -n "1,2p"
       ShowMem
       ;;
   s|S)
       free -m | sed -n "1p;4p"
       ;;
   quit)
       exit 0
       ;;
      *)
       echo "Unknown command."
   esac
   echo -en "\e[31mPlease input your choice again:\e[0m " 
   read Choice
done

[root@soysauce ~]# ./showmenu.sh 
d|D) show disk usages.
m|M) show memory usages.
s|S) show swap usages.
quit) quit.
Please input your choice: d                        # 显示磁盘使用情况
Filesystem            Size  Used Avail Use% Mounted on
/dev/hda1              20G  5.3G   14G  29% /
tmpfs                 249M     0  249M   0% /dev/shm
Please input your choice again: q
Unknown command.
Please input your choice again: s                    # 显示交换分区使用情况
             total       used       free     shared    buffers     cached
Swap:            0          0          0
Please input your choice again: M                    # 显示内存使用情况
             total       used       free     shared    buffers     cached
Mem:           497        491          6          0         68         71
./showmenu.sh: line 24: ShowMem: command not found
Please input your choice again: quit                 # 退出

(2)、while : do;done

while :;do
  statement
  ...
done

while true:;do
  statement
  ...
done

例:此脚本能于同一个repo文件中创建多个Yum源的指向,用户输入quit则退出

#!/bin/bash
# Author:soy sauce
# Date:2015-11-30 20:00
# Description:repo file
# Version 1.0

[ $# -lt 1 ] && echo "Usage: `basename $0` repofile_name" && exit 3

RepoFile=/etc/yum.repos.d/$1

[ -e $RepoFile ] && echo "$RepoFile exists." && exit 4

while true; do
   read -p "Please input the repo id: " RepoID
   [ $RepoID == 'quit' ] && exit 5
   read -p "Please input the repo name: " RepoName
   read -p "Please input the baseurl: " BaseUrl
   echo -e "[$RepoID]\nname=${RepoName}\nbaseurl=${BaseUrl}\nenabled=1\ngpgcheck=0" >> $RepoFile
   continue
done

[root@soysauce scripts]# ./mkrepo.sh                 # 没有给参数则提示用法
Usage: mkrepo.sh repofile_name
[root@soysauce scripts]# ./mkrepo.sh test.repo        # 已经重名的repo文件
/etc/yum.repos.d/test.repo exists.
[root@soysauce scripts]# ./mkrepo.sh testyum.repo
Please input the repo id: testyum
Please input the repo name: testyum
Please input the baseurl: file:///root/test/testyum
Please input the repo id: quit
[root@soysauce scripts]# cat /etc/yum.repos.d/testyum.repo     # repo文件已生成
[testyum]
name=testyum
baseurl=file:///root/test/testyum
enabled=1
gpgcheck=0
[root@soysauce scripts]# yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * epel: mirrors.ustc.edu.cn
testyum                                                                                                    | 2.9 kB     00:00 ... 
repo id                                   repo name                                                                         status
base                                      CentOS-6 - Base - 163.com                                                          6,575
epel                                      Extra Packages for Enterprise Linux 6 - x86_64                                    11,886
extras                                    CentOS-6 - Extras - 163.com                                                           45
testyum                                   testyum                                                                              147
updates                                   CentOS-6 - Updates - 163.com                                                         671
repolist: 19,324

(3)、while read line do;done

while read LINE; do
    statement
    ...
done < /PATH/TO/SOMEFILE

例:显示/etc/passwd文件中默认shell为bash的用户的用户名

[root@soysauce scripts]# cat showbash.sh 
#!/bin/bash
# Author:soy sauce
# Date:2015-11-30 22:08
# Description:bash user
# Version 1.0

FILEPATH=/etc/passwd

while read LINE; do
  [ `echo $LINE | awk -F: '{print $7}'` == '/bin/bash' ] && echo $LINE | awk -F: '{print $1}'
done < $FILEPATH

[root@soysauce scripts]# ./showbash.sh 
root
nginx
user3
hadoop

3.until

until CONDITION; do
  statement
  ...
done

例:转换用户输入的字符为大写,如果用户输入的是quit就退出

#!/bin/bash
# Author:soy sauce
# Date:2015-11-24 19:10
# Description:translate string
# Version 1.0

read -p "Please input the string: " String

until [ $String == 'quit' ]; do
    echo $String | tr 'a-z' 'A-Z'
    read -p "Please input the string: " String
done

[root@soysauce ~]# ./translate.sh 
Please input the string: useradd
USERADD
Please input the string: chage
CHAGE
Please input the string: uname
UNAME
Please input the string: quit

循环结构总结:

  • for:适用于循环次数已知的场景,循环一遍自动退出

  • while:适用于循环次数未知的场景,要有退出条件,满足条件进入循环

  • until:适用于循环次数未知的场景,要有退出条件,满足条件就跳出循环



三、break & continue

1、continue:提前结束本轮循环,进入下一轮循环

例:写一个脚本,此脚本能于同一个repo文件中创建多个Yum源的指向,当用户输入repo id时,如果为quit,则退出脚本

#!/bin/bash
# Author:soy sauce
# Date:2015-11-30 20:00
# Description:repo file
# Version 1.0

[ $# -lt 1 ] && echo "Usage: `basename $0` repofile_name" && exit 3

RepoFile=/etc/yum.repos.d/$1

[ -e $RepoFile ] && echo "$RepoFile exists." && exit 4

while true; do
   read -p "Please input the repo id: " RepoID
   [ $RepoID == 'quit' ] && exit 5
   read -p "Please input the repo name: " RepoName
   read -p "Please input the baseurl: " BaseUrl
   echo -e "[$RepoID]\nname=${RopoName}\nbaseurl=${BaseUrl}\nenabled=1\ngpgcheck=0" >> $RepoFile
   continue                    # 此处提前结束本轮循环,而后进入下一循环
done

[root@soysauce scripts]# ./mkrepo.sh test.repo
Please input the repo id: testyum
Please input the repo name: testyum
Please input the baseurl: file:///root/test/testyum
Please input the repo id: quit
[root@soysauce scripts]# cat /etc/yum.repos.d/test.repo         # repo文件已然生成
[testyum]    
name=
baseurl=file:///root/test/testyum
enabled=1
gpgcheck=0
[root@soysauce scripts]# yum repolist                         # 可以看到testyum已然生效
Loaded plugins: fastestmirror
Repository 'testyum' is missing name in configuration, using id
Loading mirror speeds from cached hostfile
 * epel: mirror01.idc.hinet.net
testyum                                                                                                    | 2.9 kB     00:00 ... 
testyum/primary_db                                                                                         |  90 kB     00:00 ... 
repo id                                   repo name                                                                         status
base                                      CentOS-6 - Base - 163.com                                                          6,575
epel                                      Extra Packages for Enterprise Linux 6 - x86_64                                    11,886
extras                                    CentOS-6 - Extras - 163.com                                                           45
testyum                                   testyum                                                                              147
updates                                   CentOS-6 - Updates - 163.com                                                         670
repolist: 19,323

2、break:提前退出循环,不再执行循环体

例:此脚本能够为指定网卡创建别名使用格式如:mkethalias.sh -v|--verbose -i ethX -h

#!/bin/bash
# Author:soy sauce
# Date:2015-11-30 20:51
# Description:ethernet ip
# Version 1.0

DEBUG=0
ETHERNET=0

if [ $# -lt 1 ]; then
    echo "Usage: `basename $0` -v | --verbose | -i ethx"
    exit 7
fi

for I in `seq 1 $#`; do
 if [ $# -ge 1 ]; then
   case $1 in
   -v|--verbose)
   	DEBUG=1
	shift 1
   	;;
   -i)
   	ETH=$2
	ETHERNET=1
   	shift 2
   	;;
   -h)
   	echo "Usage: `basename $0` -v | --verbose | -i ethx"
   	exit 0
	;;
   *)
   	echo "Usage: `basename $0` -v | --verbose | -i ethx"
   	exit 5
	;;
   esac
  fi
done


if !  ifconfig | grep "$ETH" &> /devnull; then 
   echo "Usage: `basename $0` -v | --verbose | -i ethx" 
   exit 9
fi


while true; do
  if  [[ $ALIAS == 'quit' ]]; then 
  exit 0
  fi

   read -p "Please input the alias for the $ETH: " ALIAS
     while ifconfig | grep "$ETH:$ALIAS" &> /devnull; do 
           echo "$ETH:$ALIAS exists."
           read -p "Please input the alias for the $ETH: " ALIAS
           continue
     done

   read -p "Please input your IP and NetMask[IP/Netmask]: " IPMASK
   break
done

   if [ $ETHERNET -eq 1 ]; then
   	ip addr add $IPMASK dev $ETH label $ETH:$ALIAS 
        IP=`[ $DEBUG -eq 1 ] && ip addr show | grep "$ETH:$ALIAS"`
        echo -e "\e[31m$IP\e[0m" 
   fi
   
[root@soysauce scripts]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:1b:6d:af brd ff:ff:ff:ff:ff:ff
    inet 172.16.1.110/24 brd 255.255.255.255 scope global eth0
    inet6 fe80::20c:29ff:fe1b:6daf/64 scope link 
       valid_lft forever preferred_lft forever
[root@soysauce scripts]# ./mkethalias.sh -i eth0 -v
Please input the alias for the eth0: 0
Please input your IP and NetMask[IP/Netmask]: 192.168.1.1/24
    inet 192.168.1.1/24 scope global eth0:0


四、函数

①函数是实现结构化编程的重要思想,其主要功能是为了实现代码复用;不能独立运行,只能被调用执行,并且可以被调用多次

②函数的两种定义方式

function FUNCNAME {
 statement
 ...
}


FUNCNAME() {
  statement
  ...
}

例:函数能够接受一个参数,参数为用户名,显示此用户的UID和Shell,用户输入q或Q才退出

#!/bin/bash
# Author:soy sauce
# Date:2015-12-01 13:16
# Description:Add user
# Version 1.0

function TestUser {
if id $1 &> /dev/null; then
   Uid=`grep "^$1\>" /etc/passwd | awk -F: '{print $3}'`
   Shell=`grep "^$1\>" /etc/passwd | awk -F: '{print $7}'`
   echo "The $1 shell is $Shell."
   echo "The $1 UID is $Uid."
   return 0
else
   echo "$1 not exists."
   return 1
fi
}

read -p "Please input the username: " CHOICE
until [ $CHOICE == 'q' ] || [ $CHOICE == 'Q' ]; do
   TestUser $CHOICE
   read -p "Please input the username again: " CHOICE
done

[root@soysauce scripts]# ./Show.sh 
Please input the username: root
The root shell is /bin/bash.
The root UID is 0.
Please input the username again: syz
syz not exists.
Please input the username again: q
[root@soysauce scripts]#