条件判断
if
是最基本的条件判断关键字,其用法如下:
if TEST-COMMANDS; then CONSEQUENT-COMMANDS; fi
其中, test commands
命令可以被替换为 [ commands ]
:
> [ 2 -eq 2 ]; echo $?
0 # 返回 0 表示为真
> [ 2 -eq 1 ]; echo $?
1
> test 2 -gt 2; echo $?
1
> test 2 -gt 1; echo $?
0
# 当然也可以用 [[ expr ]]
# 这种用法支持更多的符号
> [[ 2 < 2 ]]; echo $? # test 和 [] 中只能用 -lt 表示小于
1
> [[ 2 = 2 ]]; echo $? # 等价于 ==, 判断是否相等
0
[info] 提示:
test
,/usr/bin/test
,[
, 和/usr/bin/[
都是等价命令,]
只是该命令的一个参数.
条件判断一般被用于判断文件的属性与状态、当前 shell
中是否开启了某个选项、比较字符串、比较参数,其用法如下表所示:
表达式 | 含义 | ||
---|---|---|---|
[ -a FILE ] | 判断文件是否存在 | ||
[ -b FILE ] | 判断文件是否为设备文件 | ||
[ -c FILE ] | 判断文件是否为字符设备文件 | ||
[ -d FILE ] | 判断路径是否存在 | ||
[ -e FILE ] | 判断文件是否存在,必须为常规文件 | ||
[ -r FILE ] | 判断当前用户对该文件是否可读 | ||
[ -x FILE ] | 判断当前用户对该文件是否可执行 | ||
[ -w FILE ] | 判断当前用户对该文件是否可写 | ||
[ -L FILE ] | 判断当前文件是否为符号链接 | ||
[ -p FILE ] | 判断当前用户对该文件是 FIFO |
||
[ -S FILE ] | 判断当前用户对该文件是 SOCKET |
||
[ FILE1 -nt FILE2 ] | True ifFILE1has been changed more recently thanFILE2, or ifFILE1exists andFILE2does not. | ||
[ FILE1 -ot FILE2 ] | True ifFILE1is older thanFILE2, or isFILE2exists andFILE1does not. | ||
[ FILE1 -ef FILE2 ] | True ifFILE1andFILE2refer to the same device and inode numbers. | ||
[ -o OPTIONNAME ] | 如果 shell 中开启了 OPTIONNAME 选项则返回真 |
||
[ -z STRING ] | 字符串为空或长度为 0 则返回真 |
||
[ -n STRING ] or [ STRING ] | 字符串非空则返回真 | ||
[ STRING1 OP STRING2 ] | “OP”用于字典顺序比较字符串,包括 == 、 != 、 >= 、 <= |
||
[ ARG1 OP ARG2 ] | "OP"用于整数比较,包括 -gt 、 -ge 、 -lt 、 -le 、 -eq 、 -ne |
||
[ ! EXPR ] | EXPR的值取反. | ||
[ ( EXPR ) ] | 判断EXPR的返回值. | ||
[ EXPR1 -a EXPR2 ] | 等价于 [[ EXPR1 && EXPR2 ]] |
||
[ EXPR1 -o EXPR2 ] | 等价于 `[[ EXPR | EXPR ]]` |
应用案例
- 判断文件是否存在:
> cat 7.sh
#!/usr/bin/bash
echo "This scripts checks the existence of the messages file."
echo "Checking..."
if [ -f $1 ]
then
echo "$1 is an exists regular file."
fi
# 这里先不用 elif
if [ -a $1 ]
then
echo "$1 is an exists file."
fi
echo "...done."
> bash 7.sh ~/t1.awk
This scripts checks the existence of the messages file.
Checking...
/home/remilia/t1.awk is an exists regular file.
/home/remilia/t1.awk is an exists file.
...done.
> bash 7.sh ~/.. # 指向父目录的特殊文件
This scripts checks the existence of the messages file.
Checking...
/home/remilia/.. is an exists file.
...done.
- 判断
shell
是否启用了重定向:
if [ -o noclobber ]
then
echo "Your files are protected against accidental overwriting using redirection."
fi
# 注意考虑在 child shell 中不能继承父 shell 的环境变量。
# 若果你父 shell 中设置了停用重定向
# 然后通过 bash ./script 的方式运行上述脚本
# 可能得不到你期望的结果
- 判断上一条命令的返回状态:
# 可以拆开写
> grep $USER /etc/passwd
remilia:x:1000:1000:Remilia Scarlet,,,:/home/remilia:/bin/bash
> if [ $? -ne 0 ]; then echo "not a local account"; fi
# 也可以直接写在 if 后面
> if ! grep $USER /etc/passwd > /dev/null; then echo "not a local account"; fi
- 数值比较
> num= `wc -l work.txt` ; echo $num
201
> if [ "$num" -gt "15" ]; then echo ; echo "you've worked hard enough for today."; echo ; fi
you've worked hard enough for today.
- 字符串比较
# 可以直接使用比较符
> if [ `whoami` != root ]; then echo "how dare you?"; fi
how dare you?
# 支持 shell 元字符
> if [[ $gender == f* ]]; then echo "Pleasure to meet you, Madame."; fi
Pleasure to meet you, Madame.
更复杂的条件判断需要使用 if/elif/else
结构,其用法如下:
if TEST-COMMANDS; then
CONSEQUENT-COMMANDS;
elif MORE-TEST-COMMANDS; then
MORE-CONSEQUENT-COMMANDS;
else ALTERNATE-CONSEQUENT-COMMANDS;
fi
更复杂的应用案例
- 判断体重是否合适:
if [ ! $# == 2 ]; then
echo "Usage: $0 weight_in_kilos length_in_centimeters"
exit
fi
weight="$1"
height="$2"
idealweight=$[$height - 110]
if [ $weight -le $idealweight ] ; then
echo "You should eat a bit more fat."
else
echo "You should eat a bit more fruit."
fi
- 查看文件的信息:
#!/bin/bash
FILENAME="$1"
echo "Properties for $FILENAME:"
if [ -f $FILENAME ]; then
echo "Size is $(ls -lh $FILENAME | awk '{ print $5 }')"
echo "Type is $(file $FILENAME | cut -d":" -f2 -)"
echo "Inode number is $(ls -i $FILENAME | cut -d" " -f1 -)"
echo "$(df -h $FILENAME | grep -v Mounted | awk '{ print "On",$1", \
which is mounted as the",$6,"partition."}')"
else
echo "File does not exist."
fi
> bash filepro.sh abc
Properties for abc:
Size is 31
Type is ASCII text
Inode number is 3487604
On /dev/mapper/vgkubuntu-root which is mounted as the / partition.
- 判断今年是否为闰年:
#!/usr/bin/bash
year= `date +%Y`
echo `date`
if [ $[ $year % 400 ] -eq 0 ]; then
echo "This is a leap year. February has 29 days."
exit
elif [ $[ $year % 4 ] -eq 0 -a $[ $year % 100 ] -ne 0 ]; then
echo "This is a leap year. February has 29 days."
else
echo "This is not a leap year."
fi
- 检查路径是否存在,不存在则创建:
> dir=~/new_dir
> if ! test -d $dir; then mkdir -p $dir; fi
# 也可以用条件运算符
> [ ! -d ~/ccc ] && mkdir /ccc
> [ -d ~/ccc ] || mkdir /ccc
- 内存空间报警:
#!/usr/bin/bash
mem_used= `free -m | grep '^Mem:' | awk '{ print $3 }'`
mem_total= `free -m | grep '^Mem:' | awk '{ print $2 }'`
mem_percent=$[ mem_used * 100 / mem_total ]
war_file=/tmp/mem_war.txt
if [ $mem_percent -ge $1 ]; then
echo " `date +%F-%H` memory:${mem_percent}%" > $war_file
fi
if [ -f $war_file ]; then
cat $war_file
fi
- 批量创建用户并初始化密码:
#!/usr/bin/bash
read -p "Please input number: " num
if [[ ! "$num" =~ ^[1-9][0-9]*$ ]]; then
exit
fi
read -p "Please input prefix: " prefix
if [ -z $prefix ]; then
exit
fi
for i in `seq $num`
do
username=$prefix$i
# echo $username
useradd username
echo "123" | passwd $username
if [ $? -eq 0 ]; then
echo "$username has been created."
fi
done
- 磁盘空间报警:
#!/bin/bash
space= `df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`
alertvalue="80"
if [ "$space" -ge "$alertvalue" ]; then
echo "At least one of my disks is nearly full!" | mail -s "daily diskcheck" root
else
echo "Disk space normal" | mail -s "daily diskcheck" root
fi
- 用
case
实现磁盘空间报警:
#!/bin/bash
space= `df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`
case $space in
[1-6]*) # 匹配条件可以用正则
Message="All is quiet."
;;
[7-8]*)
Message="Start thinking about cleaning out some stuff. There's a partition that is $space % full."
;;
9[1-8])
Message="Better hurry with that new disk... One partition is $space % full."
;;
99)
Message="I'm drowning here! There's a partition at $space %!"
;;
*)
Message="I seem to be running with an nonexistent amount of disk space..."
;;
esac
echo $Message | mail -s "disk report `date` " anny
- 用
case
实现服务控制:
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status anacron
;;
restart)
stop
start
;;
condrestart)
if test "x `pidof anacron` " != x; then
stop
start
fi
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
- 执行指定的程序,不存在则安装:
#!/usr/bin/bash
command1=nginx
if command -v command1 &>/dev/null; then
: # 等价于 true
else
sudo apt install command1
fi
- 工具箱:
#!/usr/bin/bash
menu () {
clear
cat <<- EOF
##################################
# h. help #
# f. disk partition #
# d. filesystem mount #
# m. memory #
# u. system load #
# q. exxit #
##################################
EOF
}
trap "" HUB INT
menu
while true
do
read -p "Switch Case:" action
case "$action" in
h)
menu
;;
f) fdisk -l ;; # 也可以写在一行
d) df -Th ;;
m) free -m ;;
u) uptime ;;
q)
# exit
break
;;
"") continue ;;
*) echo error ;;
esac
done
循环控制
for 循环
语法:
for NAME [in LIST ]; do COMMANDS; done
如果省略 [in LIST ]
则用 $@
(参数列表)替换这部分, 就相当于是在遍历参数. for
循环可以写在一行, 也可以写成多行:
# 查询 /sbin 路径下的所有纯文本文件
> for i in `ls /sbin` ; do file /sbin/$i | grep ASCII; done
# 删除 html 文件的前 25 行和后 21 行并转换为 php
LIST="$(ls *.html)"
for i in "$LIST"; do
NEWNAME=$(ls "$i" | sed -e 's/html/php/')
cat beginfile > "$NEWNAME"
cat "$i" | sed -e '1,25d' | tac | sed -e '1,21d'| tac >> "$NEWNAME"
cat endfile >> "$NEWNAME"
done
# tac 命令用于翻转文件的行
# 类似 C 语言风格的写法
for((i=1;i<=5;i++));do
echo "这是第 $i 次调用";
done;
while 循环
语法:
while CONTROL-COMMAND; do CONSEQUENT-COMMANDS; done
只要满足条件就会重复执行命令, 例如这个计算平均数脚本:
#!/bin/bash
SCORE="0"
AVERAGE="0"
SUM="0"
NUM="0"
while true; do
echo -n "Enter your score [0-100%] ('q' for quit): "; read SCORE;
if (("$SCORE" < "0")) || (("$SCORE" > "100")); then
echo "Be serious. Common, try again: "
elif [ "$SCORE" == "q" ]; then
echo "Average rating: $AVERAGE%."
break
else
SUM=$[$SUM + $SCORE]
NUM=$[$NUM + 1]
AVERAGE=$[$SUM / $NUM]
fi
done
echo "Exiting."
while
可以配合重定向和 read
一起使用, 因此 while
更适合处理文件:
#!/usr/bin/bash
echo "UserName" "Password"
while read line
do
if [ -z "$line" ]; then
# echo "PASS"
continue
fi
USERNAME= `echo $line | awk '{ print $1 }'`
PASSWORD= `echo $line | awk '{ print $2 }'`
echo -e "$USERNAME \t $PASSWORD"
done < data.txt
until 循环
语法:
until TEST-COMMAND; do CONSEQUENT-COMMANDS; done
与 while
循环相反, until
循环在满足条件后就跳出, 不满足条件则重复执行命令.
break 与 continue
break N
可以指定要跳出几层, continue
用法与 C
语言中完全相同, 例如:
# 把文件名中的大写字母全换成小写
#!/bin/bash
LIST="$(ls)"
for name in "$LIST"; do
if [[ "$name" != *[[:upper:]]* ]]; then
continue
fi
ORIG="$name"
NEW= `echo $name | tr 'A-Z' 'a-z'`
mv "$ORIG" "$NEW"
echo "new name for $ORIG is $NEW"
done
用 select 构建菜单选项
语法:
select WORD [in LIST]; do RESPECTIVE-COMMANDS; done
看起来非常像 for
的语法, select
默认使用 PS3
变量作为提示符, 在循环体中可以通过 $REPLY
获得你选择的序号:
#!/bin/bash
echo "This script can make any of the files in this directory private."
echo "Enter the number of the file you want to protect:"
PS3="Your choice: "
QUIT="QUIT THIS PROGRAM - I feel safe now."
touch "$QUIT"
select FILENAME in *;
do
case $FILENAME in
"$QUIT")
echo "Exiting."
break
;;
*)
echo "You picked $FILENAME ($REPLY)"
chmod go-rwx "$FILENAME"
;;
esac
done
rm "$QUIT"
用 shift 进行参数偏移
用法: shift N
, 表示偏移 N
个参数, 前 N
个参数会被丢弃, 但要注意如果参数不足会发生死循环.
#!/bin/bash
if [ $# -lt 1 ]; then
echo "Usage: $0 package(s)"
exit 1
fi
while (($#)); do
echo $1
shift
done