if语句
if单分支语句
语法格式:
if 条件测试 then 命令序列 fi
或
if 条件测试;then 命令序列 fi
if单分支语句,当条件测试成立时,执行命令序列,否则不执行任何操作
[root@localhost ifdemo]# vim i #!/bin/bash # 自动创建用户账号脚本 read -p "请输入用户名:" uname read -s -p "请输入账户密码:" passwd # 判断用户是否输入了用户名 if [ ! -z "$uname" ] then # 判断是否输入了密码 if [ ! -z "$passwd" ];then # 只有当输入了用户名和密码之后才能执行创建用户和修改密码的指令 useradd "$uname" echo "$passwd" | passwd --stdin "$uname" fi fi [root@localhost ifdemo]# chmod +x i [root@localhost ifdemo]# ./i 请输入用户名:test1 请输入账户密码:Changing password for user test1. passwd: all authentication tokens updated successfully. # 不输入用户名或密码,将不执行任何操作 [root@localhost ifdemo]# ./i 请输入用户名: 请输入账户密码:[root@localhost ifdemo]#
if双分支语句
语法格式:
if 条件测试;then 命令序列1 else 命令序列2 fi
同单分支语句一样,then可以换行写,也可以写在条件测试后面。
If双分支语句,当条件测试成立时,执行命令序列1;当条件测试不成立时,执行命令序列2。
对上例的脚本进行更改,使用双分支语句:
[root@localhost ifdemo]# vim i #!/bin/bash # 自动创建用户脚本 read -p "请输入用户名:" uname read -s -p "请输入密码:" passwd if [[ -z "$uname" || -z "$passwd" ]];then echo "用户名或密码不能为空" exit else useradd "$uname" echo "$passwd" | passwd --stdin "$uname" fi [root@localhost ifdemo]# bash i 请输入用户名:test2 请输入密码:用户名或密码不能为空 [root@localhost ifdemo]# bash i 请输入用户名: 请输入密码:用户名或密码不能为空 [root@localhost ifdemo]# bash i 请输入用户名:test2 请输入密码:Changing password for user test2. passwd: all authentication tokens updated successfully.
if多分支语句
语法格式:
if 条件测试;then 命令序列1 elif 条件测试2;then 命令序列2 elif 条件测试3 命令序列3 ... else 命令序列n fi
多分支语句流程图:
编写脚本对用户输入的成绩分数进行评级:
#!/bin/bash # 对用户输入的考试成绩进行评级 # <60 不及格 # <=70 及格 # <=80 良好 # <=90 优秀 # <=100 学霸 # 其他分数有误 read -p "请输入考试分数:" score if [ $score -lt 60 ];then echo "不及格" elif [ $score -le 70 ];then echo "及格" elif [ $score -le 80 ];then echo "良好" elif [ $score -le 90 ];then echo "优秀" elif [ $score -le 100 ];then echo "学霸" else echo "输入分数有误" fi [root@localhost ifdemo]# vim i [root@localhost ifdemo]# bash i 请输入考试分数:49 不及格 [root@localhost ifdemo]# bash i 请输入考试分数:60 及格 [root@localhost ifdemo]# bash i 请输入考试分数:73 良好 [root@localhost ifdemo]# bash i 请输入考试分数:80 良好 [root@localhost ifdemo]# bash i 请输入考试分数:92 学霸
case语句
case语句用于检查、判断变量的取值,效果类似于if多分支语句,case语句中命令序列最后一行(除*模式外)必须以双分号(;;)结尾。
语法格式:
case 变量名 in 模式1) 命令序列1;; 模式2) 命令序列2;; ... *) 默认命令序列 esac
编写脚本根据用户输入的内容输出响应的结果:
[root@localhost casedemo]# vim ca #!/bin/bash # 根据用户输入的内容,输出对应的数据 read -p "Are you sure?[y/n]:" sure case $sure in # 匹配多个值,可以使用|分隔 y|Y|yes|YES) echo "you enter $sure, OK";; n|N|no|NO) echo "you enter $sure, OVER";; *) echo "you enter $sure is error." esac [root@localhost casedemo]# bash ca Are you sure?[y/n]:y you enter y, OK [root@localhost casedemo]# bash ca Are you sure?[y/n]:Y you enter Y, OK [root@localhost casedemo]# bash ca Are you sure?[y/n]:yes you enter yes, OK [root@localhost casedemo]# bash ca Are you sure?[y/n]:s^Hn you enter n is error. [root@localhost casedemo]# bash ca Are you sure?[y/n]:n you enter n, OVER [root@localhost casedemo]# bash ca Are you sure?[y/n]:NO you enter NO, OVER
for循环语句
3.1 基本用法
语法格式:
for 变量名 in 值列表 do 命令序列 done
或
for ((expr1;expr2;expr3)) do 命令序列 done
先来看下for循环语句的基本使用:
[root@localhost fordemo]# vim #!/bin/bash for i in aa bb cc dd do echo $i Done [root@localhost fordemo]# bash aa bb cc dd
[root@localhost fordemo]# vim #!/bin/bash for ((i=0; i<5;i++)) do echo $i done [root@localhost fordemo]# bash 0 1 2 3 4
如果变量没有定义取值范围,那么这个循环执行多少次呢?
如果变量没有定义取值范围,则默认取值为$@,即所有的位置变量的值
[root@localhost fordemo]# vim #!/bin/bash # 不定义变量的取值范围 for i do echo $i Done [root@localhost fordemo]# bash a b c d e f g a b c d e f g
Shell支持seq和{}自动生成数字序列,并且使用{}还可以自动生成字母序列。for循环语句可以对{}或seq扩展后的数据列表进行循环。
# 使用seq生成1~10的数字序列 [root@localhost ifdemo]# seq 10 1 2 3 4 5 6 7 8 9 10 # 使用{}生成1~10的数字序列 [root@localhost ifdemo]# echo {1..10} 1 2 3 4 5 6 7 8 9 10 # 使用{}生成10~1的数字序列 [root@localhost ifdemo]# echo {10..1} 10 9 8 7 6 5 4 3 2 1 # 使用{}生成1~10并且步长为3的数字序列 [root@localhost ifdemo]# echo {1..10..3} 1 4 7 10 # 使用{}生成a~d的字母序列 [root@localhost ifdemo]# echo {a..d} a b c d # seq -s可以指定分隔符默认分隔符为\n(换行符) [root@localhost ifdemo]# seq -s' ' 10 1 2 3 4 5 6 7 8 9 10 [root@localhost ifdemo]# seq -s' ' 2 8 2 3 4 5 6 7 8 # 使用seq生成2~8并且步长为2的数字序列 [root@localhost ifdemo]# seq -s' ' 2 2 8 2 4 6 8
编写脚本判断1~5000之间,哪些年是闰年:
vim #!/bin/bash # 判断1~5000之间哪些年是闰年 # 是闰年的条件: # 能被4整除但不能被100整除或能被400整除 for i in {1..5000} do if [[ $[i%4] == 0 && $[i%100] != 0 || $[i%400] == 100 ]];then echo "$i 是闰年" fi done [root@localhost fordemo]# bash ... # 统计1~5000之间闰年有多少 [root@localhost fordemo]# bash | wc -l 1213
3.2 循环嵌套的使用
编写脚本打印如下图形:
[root@localhost fordemo]# vim #!/bin/bash for((i=1;i<=6;i++)) do for((j=1;j<=i;j++)) do echo -ne "\033[42m \033[0m" done echo done for x in {5..1} do for y in $(seq $x) do echo -ne "\033[42m \033[0m" done echo done
3.3 IFS
IFS(Internal Field Seprator)是Shell中的内部变量,用来决定项目列表或值列表的分隔符,IFS的默认值为空格、Tab制表符、换行符。使用for循环读取项目列表或值列表时,就会根据IFS的值判断列表中值的个数,最终决定循环的次数。
IFS多个值之间的关系是“或”关系,所以默认情况下,for循环在读取列表时,数据可以使用空格或Tab制表符或换行符对数据进行分隔。
因为空格、Tab制表符、换行符都属于ASCII码表中的控制字符,是不可以显示的内容,所以正常使用echo命令输出该变量的值时,是看不到内容的。
因为IFS的值中默认有一个换行符,而echo默认也是换行的,所以使用echo输出IFS变量时,将输出两个空白行
[root@localhost fordemo]# echo "$IFS" [root@localhost fordemo]#
若使用printf输出IFS变量时,因为printf默认是没有换行的,所以只会输出一个空白行。
[root@localhost fordemo]# printf "%s" "$IFS" [root@localhost fordemo]#
无论是使用echo还是printf输出IFS变量,在输出结果中都是无法显示地看到具体的内容的,但是可以使用od命令将数据转换为八进制后在查看。
[root@localhost fordemo]# printf "%s" "$IFS" | od -b 0000000 040 011 012 0000003 [root@localhost fordemo]#
上例的输出结果中:040表示空格,011表示Tab水平制表符,012表示换行。
下面列出部分ASCII码表,有兴趣的同学可以搜索查看更多的ASCII码。
八进制 | 十进制 | 十六进制 | 字符 | 描述 |
10 | 8 | 08 | Backspace | 退格 |
11 | 9 | 09 | Horizontal | Tab水平制表符 |
12 | 10 | 0A | New Line | 换行 |
15 | 13 | 0D | Return | 回车 |
40 | 32 | 20 | Space | 空格 |
60 | 48 | 30 | 0 | 数字0 |
61 | 49 | 31 | 1 | 数字1 |
... ... | ||||
101 | 65 | 41 | A | 大写字母A |
102 | 66 | 42 | B | 大写字母B |
... ... | ||||
141 | 97 | 61 | a | 小写字母a |
142 | 98 | 62 | b | 小写字母b |
... ... |
既然IFS是变量,那么其值自然是可以被修改的。因为IFS的原始值不容易设置,所以当需要修改IFS值时,最好提前备份其原始值。
# 备份IFS的默认值 [root@localhost fordemo]# OLD_IFS="$IFS" # 修改IFS的值为冒号(:) [root@localhost fordemo]# IFS=":" [root@localhost fordemo]# read -p "请输入3个数据:" x y z 请输入3个数据:11 22 33 # 此时的分隔符为冒号(:),再使用空格分隔数据就不行了,系统会把11 22 33看成是一个整体,复制给第一个变量x,其他变量的值为空 [root@localhost fordemo]# echo $x 11 22 33 [root@localhost fordemo]# echo $y $z [root@localhost fordemo]#
当输入数据使用冒号(:)分隔时,变量就能正确赋值了:
[root@localhost fordemo]# read -p "请输入3个数据:" x y z 请输入3个数据:a:b:c [root@localhost fordemo]# echo $x a [root@localhost fordemo]# echo $y b [root@localhost fordemo]# echo $z c
如果我们需要使用其他的特殊控制字符作为分隔符时,可以直接像设置冒号(:)/、分号(;)、逗号(,)等其他的可见字符一样直接赋值吗?答案是不可以的,我们来看下面的例子:
[root@localhost fordemo]# IFS="\t" [root@localhost fordemo]# read -p "请输入3个任意字符或数字:" a b c 请输入3个任意字符或数字:1 2 3 [root@localhost fordemo]# echo $a 1 2 3 [root@localhost fordemo]# echo $b $c [root@localhost fordemo]#
很明显直接将Tab制表符(\t)赋值给IFS是不可以的,此时系统会使用字母t作为分隔符:
[root@localhost fordemo]# read -p "请输入3个任意字符或数字:" a b c 请输入3个任意字符或数字:11t12t13 [root@localhost fordemo]# echo $a 11 [root@localhost fordemo]# echo $b 12 [root@localhost fordemo]# echo $c 13 [root@localhost fordemo]#
那么如果我就想使用特殊的控制字符作为分隔符就没有办法了吗?当然有,此时必须使用$’string’的方式进行设置:
注意:单引号不能换成双引号,否则还是使用字母t作为分隔符。
[root@localhost fordemo]# IFS=$'\t' [root@localhost fordemo]# read -p "请输入3个任意字符或数字:" a b c 请输入3个任意字符或数字:aa bb cc [root@localhost fordemo]# echo $a aa [root@localhost fordemo]# echo $b bb [root@localhost fordemo]# echo $c cc [root@localhost fordemo]#
下面列出的是常见的特殊控制字符:
控制字符 | 描述 |
\a | Bell响铃符 |
\b | Backspace退格符 |
\f | Form Feed换行符,光标仍旧停留在原来的位置 |
\n | New Line换行符,且光标移至行首 |
\r | Return光标移至行首,但不换行 |
\t | Horizontal Tab水平制表符 |
\v | Vertical Tab垂直制表符 |
\nnn | 任意八进制字符 |
3.4 IFS对for循环的影响
下面通过一个脚本,来看下IFS对for循环有哪些影响:
[root@localhost fordemo]# vim IFS_demo.sh #/bin/bash #IFS 对for循环影响的演示 echo -e "\033[32m案例1:为自定义IFS,对x=" a b c d"循环4次结束。\033[0m" x="a b c d" for i in $x do echo "I am $i ." done echo # 备份IFS默认值 OLD_IFS="$IFS" echo -e "\033[33m案例2:自定义IFS的分隔符为分号(;),对x="1 2 3 4"循环1次结束。\033[0m" IFS=";" x="1 2 3 4" for i in $x do echo "I am $i ." done echo echo -e "\033[34m案例3:自定义IFS的分隔符为分号(;),对x='Lucy;Lily;Tom;Tony'循环4次结束。\033[0m" IFS=";" x='Lucy;Lily;Tom;Tony' for i in $x do echo "I am $i ." done echo echo -e "\033[35m案例4:自定义多个分隔符(;.:),对x='Liming;Hanmeimei.Rose'循环4次结束。\033[0m" IFS=";.:" x='Liming;Hanmeimei.Rose' for i in $x do echo "I am $i ." done echo # 还原IFS的默认值 IFS="$OLD_IFS" [root@localhost fordemo]# chmod +x IFS_demo.sh [root@localhost fordemo]# . 案例1:为自定义IFS,对x= a b c d循环4次结束。 I am a . I am b . I am c . I am d . 案例2:自定义IFS的分隔符为分号(;),对x=1 2 3 4循环1次结束。 I am 1 2 3 4 . 案例3:自定义IFS的分隔符为分号(;),对x='Lucy;Lily;Tom;Tony'循环4次结束。 I am Lucy . I am Lily . I am Tom . I am Tony . 案例4:自定义多个分隔符(;.:),对x='Liming;Hanmeimei.Rose'循环4次结束。 I am Liming . I am Hanmeimei . I am Rose . [root@localhost fordemo]#
while循环语句
使用for循环可以实现循环次数固定的循环,而while循环的循环次数取决于条件判断的结果。While循环有可能随时结束,也有可能永不结束成为死循环。
基本用法
语法格式:
while 条件判断 do 命令序列 done
While循环中也可以没有条件判断而是用冒号(:)代替条件判断,当条件判断为冒号(:)时,则为死循环。
[root@localhost whiledemo]# vim w #!/bin/bash # while 循环基本使用 i=1 while [ $i -le 5 ] do echo $i # 改变变量i的值,防止死循环 let i++ done [root@localhost whiledemo]# chmod +x w [root@localhost whiledemo]# ./w 1 2 3 4 5
使用while死循环编写猜数字游戏脚本:
[root@localhost whiledemo]# vim gue #!/bin/bash # 使用while死循环实现猜数字小游戏 # 计算机随机生成1~20的数字 # 清屏 clear computerNum=$[RANDOM%20+1] # 定义猜测的总次数 total=0 while : do read -p "请输入您猜测的数字[1~20]:" num # 正则判断输入的是否有字母或符号等无效字符 [[ $num =~ [[:alpha:]] || $num =~ [[:punct:]] ]] && echo "无效的输入" && exit let total++ if [ $num -eq $computerNum ];then echo -e "\033[32m恭喜您第${total}次就猜对了,正是${num}\033[0m" exit elif [ $num -gt $computerNum ];then echo -e "\033[31m猜大了\033[0m" else echo -e "\033[31m猜小了\033[0m" fi # 如果猜测5次仍没有成功,脚本将推出 [[ $total -ge 5 ]] && echo "很遗憾,机会已用完,您猜测失败" && exit done [root@localhost whiledemo]# chmod +x gue [root@localhost whiledemo]# ./gue 请输入您猜测的数字[1~20]:10 猜小了 请输入您猜测的数字[1~20]:15 猜小了 请输入您猜测的数字[1~20]:18 猜大了 请输入您猜测的数字[1~20]:16 猜小了 请输入您猜测的数字[1~20]:17 恭喜您第5次就猜对了,正是17 [root@localhost whiledemo]#
通过read读取文件中的数据
首先回顾一下read命令的几个特性:
- 当定义的变量和输入的值数量相同时,则输入的值将依次分别赋值给定义的每个变量
[root@localhost whiledemo]# read key1 key2 key3 11 2 33 [root@localhost whiledemo]# echo $key1 11 [root@localhost whiledemo]# echo $key2 2 [root@localhost whiledemo]# echo $key3 33
- 当定义的变量数多于输入的值数量时,前面的变量将依次分别被赋值,超过输入的值的变量其值为空
[root@localhost whiledemo]# read key1 key2 key3 11 22 [root@localhost whiledemo]# echo $key1 11 [root@localhost whiledemo]# echo $key2 22 [root@localhost whiledemo]# echo $key3 [root@localhost whiledemo]#
- 当定义的变量少于输入的值数量时,根据变量和输入的值对应的位置分别依次赋值,多出来的输入的值将全部被赋值给最后一个变量
[root@localhost whiledemo]# read key1 key2 key3 aa bb cc dd ee ff gg [root@localhost whiledemo]# echo $key1 aa [root@localhost whiledemo]# echo $key2 bb [root@localhost whiledemo]# echo $key3 cc dd ee ff gg [root@localhost whiledemo]#
然后看如何结合while循环批量读取数据并通过read命令给变量赋值,基本格式如下:
while read line do 命令 done < 文件名
或
命令 | while read line do 命令 done
先创建一个测试文件
[root@localhost whiledemo]# vim hello the world welcome to beijing Hi Rick Let's go to play
结合while循环批量读取文件的内容
[root@localhost whiledemo]# while read line > do > echo -e "\033[34m$line\033[0m" > done < hello the world welcome to beijing Hi Rick Let's go to play [root@localhost whiledemo]#
将读取的值通过read命令赋值给两个变量,并使用while循环输出
[root@localhost whiledemo]# while read key1 key2 > do > echo -e "\033[31mkey1:$key1------key2:$key2\033[0m" > done < key1:hello------key2:the world key1:welcome------key2:to beijing key1:Hi------key2:Rick key1:Let's------key2:go to play [root@localhost whiledemo]#
当文件的数据分隔符不是空格时,可以使用IFS变量指定分隔符
[root@localhost whiledemo]# vim w #!/bin/bash # 循环读取文件中的数据 # 通过IFS自定义输入数据的分隔符 # 备份IFS默认值 OLD_IFS="$IFS" # 定义IFS分隔符为: IFS=":" while read user pass uid gid info home shell do echo -e "\033[35mUser name is:$user, home is $home, Shell is $shell\033[0m" done < /etc/passwd [root@localhost whiledemo]# chmod +x w [root@localhost whiledemo]# ./w User name is:root, home is /root, Shell is /bin/bash User name is:bin, home is /bin, Shell is /sbin/nologin User name is:daemon, home is /sbin, Shell is /sbin/nologin User name is:adm, home is /var/adm, Shell is /sbin/nologin User name is:lp, home is /var/spool/lpd, Shell is /sbin/nologin User name is:sync, home is /sbin, Shell is /bin/sync User name is:shutdown, home is /sbin, Shell is /sbin/shutdown ...
通过管道的方式传递数据
[root@localhost whiledemo]# df -h | grep / | while read name size other > do > echo "$name $size" > done /dev/sda1 20G devtmpfs 901M tmpfs 911M tmpfs 911M tmpfs 911M tmpfs 183M
5. until和select循环
5.1 until循环
until循环实现与while一样的功能。
语法格式:
until 条件判断 do 命令序列 done
与while语句相反,until循环语句只有当条件判断结果为真时才退出循环,而当条件判断结果为假时则执行循环体中的命令。
[root@localhost whiledemo]# vim un #!/bin/bash # until语句仅当条件判断为真时才退出循环 i=1 until [ $i -ge 5 ] do echo $i let i++ Done [root@localhost whiledemo]# chmod +x un [root@localhost whiledemo]# ./un 1 2 3 4 5.2 select循环
select 循环主要用于创建菜单选项。
语法格式
select 变量名 in 值列表 do 命令序列 done
使用select循环编写查看系统信息的脚本
[root@localhost whiledemo]# vim #!/bin/bash # 根据用户选择的菜单实现对应的功能 echo "请根据提示选择一个选项" select item in "CPU" "IP" "MEM" "exit" do case $item in "CPU") uptime;; "IP") ip a s;; "MEM") free -h;; "exit") exit;; *) echo "error";; esac done [root@localhost whiledemo]# chmod +x [root@localhost whiledemo]# ./ 请根据提示选择一个选项 1) CPU 2) IP 3) MEM 4) exit #? CPU error #? 1 15:53:13 up 3 days, 4:32, 2 users, load average: 0.04, 0.03, 0.05 #? 2 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:3a:b0:5d brd ff:ff:ff:ff:ff:ff inet 0.0.0.0/24 brd 0.0.0.0 scope global noprefixroute dynamic ens33 valid_lft 77476sec preferred_lft 77476sec inet6 fe80::20c:29ff:fe3a:b05d/64 scope link noprefixroute valid_lft forever preferred_lft forever #? 3 total used free shared buff/cache available Mem: 1.8G 118M 1.0G 9.5M 644M 1.5G Swap: 0B 0B 0B #? 4
6. 中断与退出控制
Shell中中断与退出控制语句有:continue、break、exit。
6.1 continue
continue命令可以结束单词循环,continue命令后面的语句不再执行,进而直接跳转到下一次循环。
[root@localhost whiledemo]# vim con #!/bin/bash # continue基本语法 for i in {1..5} do [ $i -eq 3 ] && continue echo $i done [root@localhost whiledemo]# bash con 1 2 4 5
6.2 break
break结束整个循环体,循环体中break后面的所有命令都不再执行,并且整体循环结束。
[root@localhost whiledemo]# vim break_demo.sh #!/bin/bash # break基本语法 for i in {1..5} do [ $i -eq 3 ] && break echo $i done echo "game over" [root@localhost whiledemo]# bash break_demo.sh 1 2 game over
6.3 exit
exit命令会直接结束整个脚本,exit后面也可以跟数字参数,表示脚本的退出状态,如果没有指定数字参数,则脚本的退出状态就是上一个命令的退出状态。
[root@localhost whiledemo]# vim exi #!/bin/bash # exit基本语法 for i in {1..5} do [ $i -eq 3 ] && exit echo $i done echo "game over" [root@localhost whiledemo]# bash exi 1 2 [root@localhost whiledemo]#