第八章 SHELL脚本编程基础

168 阅读15分钟

@[TOC](第八章 SHELL脚本编程基础)

实验一:建立脚本并检查

⽬的

建立脚本。

前提

linux系统,centos6、centos7或ubuntu,连接网络。

命令介绍

1、创建shell脚本

第一步:使用文本编辑器来创建文本文件
 	第一行必须包括shell声明序列:#!
 		示例:#!/bin/bash
	添加注释
 		注释以#开头
第二步:运行脚本
 	给予执行权限,在命令行上指定脚本的绝对或相对路径
	直接运行解释器,将脚本作为解释器程序的参数运行

脚本规范

1、第一行一般为调用使用的语言
2、程序名,避免更改文件名为无法找到正确的文件
3、版本号
4、更改后的时间
5、作者相关信息
6、该程序的作用,及注意事项
7、最后是各版本的更新简要说明

【例1】编写⽣成脚本基本格式的脚本,包括作者,联系⽅式,版本,时间,描述等

[root@magedu ~]# vim .vimrc
set ignorecase
set cursorline
set autoindent
autocmd BufNewFile *.sh exec ":call SetTitle()"
func SetTitle()
 if expand("%:e") == 'sh'
 call setline(1,"#!/bin/bash") 
 call setline(2,"#") 
 call setline(3,"#********************************************************************") 
 call setline(4,"#Author: w") 
 call setline(5,"#QQ: 1****") 
 call setline(6,"#Date: ".strftime("%Y-%m-%d"))
 call setline(7,"#FileName: ".expand("%"))
 call setline(8,"#URL: http://www.magedu.com")
 call setline(9,"#Description: The test script") 
 call setline(10,"#Copyright (C): ".strftime("%Y")." All rights reserved")
 call setline(11,"#********************************************************************") 
 call setline(12,"") 
 endif
endfunc
autocmd BufNewFile * normal G

【例2】建⽴脚本

[root@Magedu ~]# vim hello.sh
#!/bin/bash 
echo "hello world"

2、脚本调试

检测脚本中的语法错误
bash -n /path/to/some_script
调试执行
bash -x /path/to/some_script

【例3】测试脚本

[root@localhost ]# bash -n hello.sh
[root@localhost ]# bash -x hello.sh
+ echo 'hello world'
hello world

如果有错:

[root@Magedu ~]# vim hello.sh
#!/bin/bash 
echo hello world"
root@localhost ]# bash -n hello.sh
hello.sh: line 2: unexpected EOF while looking for matching `"'
hello.sh: line 3: syntax error: unexpected end of file
[root@localhost ]# bash -x hello.sh
hello.sh: line 2: unexpected EOF while looking for matching `"'
hello.sh: line 3: syntax error: unexpected end of file
#提示:缺少一个"

【例4】运⾏脚本:

添加执行权限
chmod a+x hello.sh
直接运行bash 脚本名称
bash hello.sh

【例5】PATH变量的使⽤。

[root@localhost data]# hello.sh
bash: hello.sh: command not found...
[root@localhost data]# ./hello.sh
hello world
[root@localhost data]# PATH=.:$PATH 	#把.加入PATH变量
[root@localhost data]# echo $PATH
.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost data]# hello.sh
hello world
[root@localhost data]# cd ..
[root@localhost /]# hello.sh
bash: hello.sh: command not found...
[root@localhost /]# PATH=/data:$PATH 	#把/data加入PATH变量
[root@localhost /]# echo $PATH
/data:.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost /]# hello.sh
hello world

【例6】hello.sh,带颜⾊。

[root@localhost data]# vim hello.sh 
#!/bin/bash 
echo -e "\033[31mhello world\033[0"

3、变量:

脚本中的变量
 Shell中变量命名法则:
 1、不能使程序中的保留字:例如if, for
 2、只能使用数字、字母及下划线,且不能以数字开头
 3、见名知义
 4、统一命名规则:驼峰命名法
 Shell中命名建议规则:
 1、变量名大写
 2、局部变量小写
 3、函数名小写
 4、用英文名字,并体现出实际作用

【例7】关键字,不能⽤作变量

[root@localhost ~]# type while
while is a shell keyword

【例8】统⼀命名规则,根据公司规范执⾏

studentName
STUDENT_NAME
3.1、局部变量:
 变量赋值:name=‘value’
 可以使用引用value
 (1) 可以是直接字串:name=“root"
 (2) 变量引用:name="$USER"
 (3) 命令引用:name=`COMMAND` 
 			  name=$(COMMAND)
 变量引用:${name} 或者 $name
 " " 弱引用,其中的变量引用会被替换为变量值
 ' ' 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
 显示已定义的所有变量:set
 删除变量:unset name

【例9】变量的定义和读取$

[root@localhost ~]# NAME=CHEN
[root@localhost ~]# echo $NAME
CHEN
[root@localhost ~]# echo "$NAME"
CHEN
[root@localhost ~]# echo '$NAME'
$NAME

单引号、双引号、反向单引号区别:

1、由单引号('),强引用,其中的变量会被替换未变量值。 2、由双引号("),弱引用,其中的变量不会被替换未变量值,而保持原字符串。 3、反向单引号(`)括起来的字符串被shell解释为命令行,在执行时,shell首先执行该命令行,并以它的标准输出结果取代整个反引号(包括两个反引号)部分。

【例10】多个变量的定义和读取$

[root@localhost ~]# g1=duan
[root@localhost ~]# g2=zhao
[root@localhost ~]# echo $g1$g2
duanzhao
[root@localhost ~]# echo group leader 1 is $g1
group leader 1 is duan
[root@localhost ~]# echo group leader 2 is $g2
group leader 2 is zhao

【例11】显⽰$100,⼀百美元

[root@localhost ~]# echo $100
00
[root@localhost ~]# echo '$100'
$100
[root@localhost ~]# echo \$100
$100

说明:$为转移符。

【例12】""移符。单数时等待输⼊,⽤Ctrl+c退出。双数时,显⽰出来⼀半。

[root@localhost ~]# echo "\"
> ^C
[root@localhost ~]# echo "\\"
\
[root@localhost ~]# echo "\\\"
> ^C
[root@localhost ~]# echo "\\\\"

【例13】区别变量名和变量,⽤{}或"分隔。

[root@localhost ~]# num=1
[root@localhost ~]# echo No$num
No1
[root@localhost ~]# echo $numNo
[root@localhost ~]# echo ${num}No
1No
[root@localhost ~]# echo ${num}
1
[root@localhost ~]# echo $"num"No
numNo
[root@localhost ~]# echo "$num"No
1No

【例14】变量赋值,m指向新的赋值,n不变

[root@localhost ~]# m=10
[root@localhost ~]# n=$m
[root@localhost ~]# echo $m
10
[root@localhost ~]# echo $n
10
[root@localhost ~]# m=20
[root@localhost ~]# echo $m
20
[root@localhost ~]# echo $n
10

【例15】输出时保留原格式

[root@localhost ~]# who
root :0 2019-05-14 10:04 (:0)
root pts/0 2019-05-14 15:27 (:0)
root pts/1 2019-05-14 16:15 (192.168.23.1)
[root@localhost ~]# userinfo=`who`
[root@localhost ~]# echo $userinfo
root :0 2019-05-14 10:04 (:0) root pts/0 2019-05-14 15:27 (:0) root pts/1 2019-05-14 16:15
(192.168.23.1)
[root@localhost ~]# echo "$userinfo"
root :0 2019-05-14 10:04 (:0)
root pts/0 2019-05-14 15:27 (:0)
root pts/1 2019-05-14 16:15 (192.168.23.1)

【例16】备份脚本,变量的应⽤。

[root@localhost data]# vim backup.sh
#!/bin/bash
SOURCE="/data/script37"
DEST=/data/backup-script`date +%F`
echo -e "\e[1;31mStarting backup...\e[0m"
sleep 3
cp -av $SOURCE $DEST
echo -e "\e[1;31mBackup is finished.\e[0m"

[root@localhost data]# bash backup.sh
Starting backup...
‘/data/script37’ -> ‘/data/backup-script2019-05-14’
Backup is finished.

[root@localhost data]# rm -fr backup-script2019-05-14/ 	#删除

[root@localhost data]# vim backup.sh 	#将颜色定为变量
#!/bin/bash
SOURCE="/data/script37"
DEST=/data/backup-script`date +%F`
BEGINCOLOR="\e[1;34m"
ENDCOLOR="\e[0m"

echo -e "${BEGINCOLOR}Starting backup...${ENDCOLOR}"
sleep 3
cp -av $SOURCE $DEST
echo -e "${BEGINCOLOR}Backup is finished.${ENDCOLOR}"

[root@localhost data]# bash backup.sh 
Starting backup...
‘/data/script37’ -> ‘/data/backup-script2019-05-14/script37’
Backup is finished.

【例17】显⽰⾃定义变量,删除变量

[root@localhost data]# name=chen
[root@localhost data]# set|less

在这里插入图片描述

q键退出set|less
[root@localhost ~]# unset name 	#删除变量
[root@localhost data]# vim backup.sh

在这里插入图片描述 【例18】⼦进程不能使⽤⽗进程定义的普通变量。

[root@localhost ~]# name=chen
[root@localhost ~]# echo $name
chen
[root@localhost ~]# bash 	#开启子进程
[root@localhost ~]# $name
[root@localhost ~]# exit 	#退出子进程
exit
3.2、环境变量:
 变量声明、赋值:
 	export name=VALUE
 	declare -x name=VALUE
 变量引用:
 	$name, ${name}
 显示所有环境变量:
 	env
 	printenv
 	export
 	declare -x
 删除变量:
	unset name

【例19】⾃定义环境变量,⽗进程的变量可以传给⼦进程。

[root@localhost ~]# VAR=test
[root@localhost ~]# export VAR 	#自定义环境变量
[root@localhost ~]# echo $VAR
test
[root@localhost ~]# export 		#查看所有环境变量
declare -x DISPLAY="localhost:10.0"
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="localhost.localdomain"
declare -x LANG="en_US.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"
declare -x LOGNAME="root"
···
declare -x TERM="xterm"
declare -x USER="root"
declare -x
XDG_DATA_DIRS="/root/.local/share/flatpak/exports/share/:/var/lib/flatpak/exports/share/:/u
sr/local/share/:/usr/share/"
declare -x XDG_RUNTIME_DIR="/run/user/0"
declare -x XDG_SESSION_ID="2"
declare -x VAR="test"		#刚刚添加的变量

[root@localhost ~]# bash 	#开启子进程
[root@localhost ~]# echo $VAR 	#父进程的变量可以传给子进程。
test
[root@localhost ~]# exit 	#退出子进程
exit

【例20】⽗进程与⼦进程建⽴关系。

[root@localhost data]# vim father.sh 
#!/bin/bash
echo "srart father.sh"
echo "father pid is $BASHPID"
/data/son.sh

[root@localhost data]# vim son.sh 
#!/bin/bash
echo "srarting son.sh"
echo "son.sh pid is $BASHPID"
sleep 100

[root@localhost data]# chmod +x father.sh	#给文件加执行权限
[root@localhost data]# chmod +x son.sh
[root@localhost data]# /data/father.sh 		#执行脚本
srart father.sh
father pid is 3228
srarting son.sh
son.sh pid is 3229
^C

【例21】⽗进程与⼦进程建⽴关系,普通变量不能传递。

[root@localhost data]# vim father.sh 
#!/bin/bash
name=father
echo "srart father.sh"
echo "name=$name"
echo "father pid is $BASHPID"
/data/son.sh

[root@localhost data]# vim son.sh 
#!/bin/bash
echo "srarting son.sh"
echo "son.sh pid is $BASHPID"
echo "name=$name"
sleep 100

[root@localhost data]# /data/father.sh
srart father.sh
name=father
father pid is 3353
srarting son.sh
son.sh pid is 3354
name=
^C

【例22】⽗进程与⼦进程建⽴关系,环境变量,可以传递。

[root@localhost data]# vim father.sh 
#!/bin/bash
export name=father
echo "srart father.sh"
echo "name=$name"
echo "father pid is $BASHPID"
/data/son.sh

[root@localhost data]# vim son.sh 
#!/bin/bash
echo "srarting son.sh"
echo "son.sh pid is $BASHPID"
echo "name=$name"
sleep 100

[root@localhost data]# /data/father.sh 
srart father.sh
name=father
father pid is 3353
srarting son.sh
son.sh pid is 3354
name=father
^C

【例23】"( )"开启⼦进程

[root@localhost data]# (name=mage;echo $name)
mage
[root@localhost data]# echo $name

[root@localhost data]# (name=mage;echo $name);echo $name
mage
[root@localhost data]# name=wang;(name=mage;echo $name);echo $name
mage
wang
[root@localhost data]# name=wang;(echo $name);echo $name
wang
wang
[root@localhost data]# name=wang;echo $BASHPID;(echo $BASHPID;sleep 100);echo $name
2864
3561
^C
[root@localhost data]# pstree -p	#同时列出每个进程的PID

在这里插入图片描述

3.3、只读变量:
只读变量:只能声明,但不能修改和删除
 声明只读变量:
 	readonly name
 	declare -r name
 查看只读变量:
 	readonly -p

【例24】声明,只读变量,不能修改。

[root@localhost data]# pi=3.14
[root@localhost data]# readonly pi
[root@localhost data]# echo pi
pi
[root@localhost data]# pi=3.1415
bash: pi: readonly variable
[root@localhost data]# unset pi
bash: unset: pi: cannot unset: readonly variable
[root@localhost data]# exit 	#退出,就没了

【例25】系统信息,脚本。

[root@localhost data]# vim systeminfo.sh 
BEGINCOLOR="\e[1;35m"
ENDCOLOR="\e[0m"

echo -e "My hostname is ${BEGINCOLOR}`hostname`$ENDCOLOR"
echo -e "IP address is ${BEGINCOLOR}`ifconfig ens33 |grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}'|head -n1`$ENDCOLOR"
echo -e "OS version is ${BEGINCOLOR}`cat /etc/redhat-release`$ENDCOLOR"
echo -e "Kernel version is ${BEGINCOLOR}`uname -r`$ENDCOLOR"
echo -e "CPU type is ${BEGINCOLOR}`lscpu|grep "Model name" |cut -d: -f2 |tr -s " "`$ENDCOLOR"
echo -e "Memtotol is ${BEGINCOLOR}`cat /proc/meminfo |head -n1 |grep -Eo '[0-9]+.*'`$ENDCOLOR"
echo -e "Disk space is ${BEGINCOLOR}`lsblk |grep 'sda\>'|grep -Eo '[0-9]+[[:upper:]]'`$ENDCOLOR"

[root@localhost data]# bash systeminfo.sh 
My hostname is localhost.localdomain
IP address is 192.168.23.118
OS version is CentOS Linux release 7.5.1804 (Core) 
Kernel version is 3.10.0-862.el7.x86_64
CPU type is Intel(R) Core(TM) i5-3320M CPU @ 2.60GHz
Memtotol is 1827396 kB
Disk space is 200G

【例26】所有只读变量。-i 声明为整数;-a 声明未数组;-f 声明未函数;-r 声明未只读

[root@localhost data]# readonly
declare -r BASHOPTS="checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:login_shell:progcomp:promptvars:sourcepath"
declare -ir BASHPID="2361"
declare -r BASH_COMPLETION_COMPAT_DIR="/etc/bash_completion.d"
declare -ar BASH_REMATCH='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-
redhat-linux-gnu")'
declare -ir EUID="0"
declare -ir PPID="2359"
declare -r SHELLOPTS="braceexpand:emacs:hashall:histexpand:history:interactivecomments:monitor"
declare -ir UID="0"
3.4、位置变量:
位置变量:在脚本代码中调用通过命令行传递给脚本的参数
 	$1, $2, ... 对应第1、第2等参数,shift [n]换位置
	$0 命令本身
 	$* 传递给脚本的所有参数,全部参数合为一个字符串
	$@ 传递给脚本的所有参数,每个参数为独立字符串
	$# 传递给脚本的参数的个数
	 注意:$@ $* 只在被双引号包起来的时候才会有差异
 	set -- 清空所有位置变量

【例27】位置变量脚本。

[root@localhost data]# vim arg.sh
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${10}"

echo "all args are $*"
echo "all args are $@"
echo "arg number is $#"
echo "scriptname is `basename $0`"

[root@localhost data]# bash arg.sh a b c d
1st arg is a
2st arg is b
3st arg is c
10st arg is 
all args are a b c d
all args are a b c d
arg number is 4
scriptname is arg.sh
3.5、特殊变量:
特殊变量:$? , $0 , $*, $@ , $# , $$

【例28】特殊变量,进程编号,⽤tree命令查询

[root@localhost ~]# echo $$ 	#进程名称
2361
[root@localhost ~]# echo $BASHPID 	#当前所在进程
2361
[root@localhost ~]# echo $PPID 	#当前父进程
2359

在这里插入图片描述 centos7,第一个进程。

在这里插入图片描述 centos6,第一个进程。

【例29】"$_"是什么意思?第⼆个变量。

[root@localhost data]# echo a b
a b
[root@localhost data]# echo $_
b
[root@localhost data]# alias ls
alias ls='ls --color=auto'
[root@localhost data]# ls
3 5 7 9 backup-script2019-05-14 correct.txt father.sh script37
4 6 8 backup-script backup.sh falt.txt hello.sh son.sh
[root@localhost data]# echo $_
--color=auto

说明:ls是别名,显⽰出第⼆段内容。

4、退出状态:

进程使用退出状态来报告成功或失败0 代表成功,1255代表失败$? 变量保存最近的命令退出状态

bash自定义退出状态码
exit [n]:自定义退出状态码

【例30】退出状态$?。

[root@localhost ~]# ping -c1 -w1 hostdown &> /dev/null 
[root@localhost ~]# echo $?
2

[root@localhost data]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@localhost data]# echo $?
0
[root@localhost data]# grep Root /etc/passwd
[root@localhost data]# echo $?
1

[root@localhost data]# ping www.baidu.com
PING www.a.shifen.com (220.181.112.244) 56(84) bytes of data.
64 bytes from 220.181.112.244 (220.181.112.244): icmp_seq=1 ttl=50 time=6.17 ms
^C
--- www.a.shifen.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 6.176/6.354/6.532/0.178 ms
[root@localhost data]# echo $?
0

[root@localhost data]# ping 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
^C
--- 192.168.2.1 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1000ms

[root@localhost data]# echo $?
1

【例31】⾃定义,退出状态$?,的返回值。

[root@localhost data]# vim exit.sh
#!/bin/bash
ls
error
hostname
exit 10

[root@localhost data]# bash exit.sh
3 6 9 backup-script2019-05-14 exit.sh father.sh sendfile.sh
4 7 arg.sh backup.sh f1.sh hello.sh son.sh
5 8 backup-script correct.txt falt.txt script37 systeminfo.sh
exit.sh: line 13: error: command not found
localhost.localdomain
[root@localhost data]# echo $?
10

5、算术运算:

bash中的算术运算:help let
 +, -, *, /, %取模(取余), **(乘方),乘法符号有些场景中需要转义
 实现算术运算:
 (1) let var=算术表达式
 (2) var=$[算术表达式]
 (3) var=$((算术表达式))
 (4) var=$(expr arg1 arg2 arg3 ...)
 (5) declare –i var = 数值
 (6) echo ‘算术表达式’ | bc
bash有内建的随机数生成器变量:$RANDOM(0-32767)
 示例:生成 0 - 49 之间随机数
 echo $[$RANDOM%50]

【例32】算数let var=算术表达式

[root@localhost ~]# i=10
[root@localhost ~]# j=20
[root@localhost ~]# echo $i+$j
10+20
[root@localhost ~]# sum=$i+$j
[root@localhost ~]# echo $sum
10+20
[root@localhost ~]# let sum=$i+$j 	#算术表达式
[root@localhost ~]# echo $sum
30
[root@localhost ~]# let sum=i+j 	#算术表达式
[root@localhost ~]# echo $sum
30

【例33】var=[算术表达式]var=[算术表达式]和var=((算术表达式))

[root@localhost ~]# sum=$[i+j]
[root@localhost ~]# echo $sum
30
[root@localhost ~]# sum=$((i+j))
[root@localhost ~]# echo $sum
30

【例34】 var=$(expr arg1 arg2 arg3 ...),"*"需要转义。

[root@localhost ~]# expr 2 +3
expr: syntax error 	#语法错误
[root@localhost ~]# expr 2 + 3
5
[root@localhost ~]# expr 2 * 3
expr: syntax error
[root@localhost ~]# expr 2 \* 3
6
[root@localhost ~]# expr i \* 3
expr: non-integer argument
[root@localhost ~]# expr $i \* 3
30

【例35】 declare –i var = 数值

[root@localhost ~]# declare -i x=i+j
[root@localhost ~]# echo $x
30

【例36】 ⽣成 0 - 49 之间随机数 echo [[RANDOM%50]

[root@localhost ~]# echo -e '\033[33mcentos\033[0m' 	#给centos加颜色
centos 
[root@localhost ~]# echo $[RANDOM%7+31] 	#生成随机数
36 
[root@localhost ~]# NUM=$[RANDOM%7+31];echo -e "\033[${NUM}mcentos\033[0m" 	#给centos加随机颜色
centos
5.1、赋值:
增强型赋值:
 +=, -=, *=, /=, %=
let varOPERvalue
 例如:let count+=3
 自加3后自赋值
自增,自减:
 let var+=1
 let var++
 let var-=1
 let var--

【例37】赋值

[root@localhost ~]# i=100
[root@localhost ~]# let i++
[root@localhost ~]# echo $i
101
[root@localhost ~]# let i--
[root@localhost ~]# echo $i
100
[root@localhost ~]# let i--
[root@localhost ~]# echo $i
99
[root@localhost ~]# i=100 ;let j=i++;echo j=$j,i=$i
j=100,i=101
5.2、逻辑运算:
true, false
 1, 011 = 1
 10 = 0
 01 = 0
 00 = 011 = 1
 10 = 1
 01 = 1
 00 = 0
非:!
 ! 1 = 0 ! true
 ! 0 = 1 ! false

【例38】true, false。

[root@localhost ~]# true
[root@localhost ~]# echo ?
?
[root@localhost ~]# echo $?
0
[root@localhost ~]# false
[root@localhost ~]# echo $?
1

【例39】与

[root@localhost ~]# echo $[8&12]
8
[root@localhost ~]# echo $[4&12]
4

【例40】或

[root@localhost ~]# echo $[4|12]
12
[root@localhost ~]# echo $[4|12]
12

【例41】⾮

[root@localhost ~]# ! true
[root@localhost ~]# echo $?
1
[root@localhost ~]# ! false
[root@localhost ~]# echo $?
0

短路运算

短路与
 第一个为0,结果必定为0
 第一个为1,第二个必须要参与运算
短路或
 第一个为1,结果必定为1
 第一个为0,第二个必须要参与运算
异或:^
 异或的两个值,相同为假,不同为真

【例42】异或

[root@localhost ~]# a=10;b=20
[root@localhost ~]# echo $a $b
10 20
[root@localhost ~]# a=10;b=20;a=$[a^b];b=$[a^b];a=$[a^b];echo $a $b
20 10
5.3、条件测试:
判断某需求是否满足,需要由测试机制来实现。专用的测试表达式需要由测试命令辅助完成测试过程
评估布尔声明,以便用在条件性执行中
 若真,则返回0
 若假,则返回1
测试命令:
 test EXPRESSION
 [ EXPRESSION ]
 [[ EXPRESSION ]]
 注意:EXPRESSION前后必须有空白字符

【例43】test=[]

[root@localhost ~]# help test
test: test [expr]
 Evaluate conditional expression.
 
 Exits with a status of 0 (true) or 1 (false) depending on
 the evaluation of EXPR. Expressions may be unary or binary. Unary
 expressions are often used to examine the status of a file. There
 are string operators and numeric comparison operators as well.

[root@localhost ~]# cat /etc/redhat-release |egrep -o '[0-9]+'|head -1 	#取出版本号
7
[root@localhost ~]# os=/etc/redhat-release |egrep -o '[0-9]+'|head -1
[root@localhost ~]# [$os =7] 	#[]必须两边有空格
-bash: [: missing `]'
[root@localhost ~]# [ $os =7 ]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ $os =!7 ]
[ $os =ping www.baidu.com ]
-bash: [: =ping: unary operator expected
[root@localhost ~]# echo $?
2

【例44】test=[[]]⽀持正则表达式

[root@localhost data]# touch f1.sh
[root@localhost data]# file=f1.sh ;[[ $file =~ \.sh$ ]]
[root@localhost data]# echo $?
0
[root@localhost data]# file=f1.shsh ;[[ $file =~ \.sh$ ]]
[root@localhost data]# echo $?
1
5.4、数值测试:
-v VAR 
 变量VAR是否设置
数值测试:
 -gt 是否大于
 -ge 是否大于等于
 -eq 是否等于
 -ne 是否不等于
 -lt 是否小于
 -le 是否小于等于
5.5、字符串测试:
= 是否等于
> ascii码是否大于ascii码
< 是否小于
!= 是否不等于
=~ 左侧字符串是否能够被右侧的PATTERN所匹配
注意: 此表达式一般用于[[ ]]中;扩展的正则表达式
 -z "STRING“ 字符串是否为空,空为真,不空为假
 -n "STRING“ 字符串是否不空,不空为真,空为假

注意:⽤于字符串⽐较时的⽤到的操作数都应该使⽤引号

【例45】-n "STRING“ 字符串是否不空,不空为真,空为假。建议加",不加容易出现语法错误。

[root@localhost data]# [ -n $centos ];echo $?
0
[root@localhost data]# [ -n "$centos" ];echo $?
1
[root@localhost data]# [ -n "" ];echo $?
1
[root@localhost data]# [ ];echo $?
1
[root@localhost data]# [ abc ];echo $?
0
[root@localhost data]# [ 0 ];echo $?
0
[root@localhost data]# [ -1 ];echo $?
0

说明:【】内有内容即为真。

【例46】数值测试:

[root@localhost data]# i=20;[ $i -gt 10 ] ;echo $?
0 i大于10,为真。
[root@localhost data]# i=20;[ $i -lt 10 ] ;echo $?
1 i小于10,为假。
[root@localhost data]# i=20;[ $i -ge 10 ] ;echo $?
0 i大于等于10,为真。
[root@localhost data]# i=20;[ $i -eq 10 ] ;echo $?
1 i等于10,为假。
5.6、⽂件存在性和类型测试
存在性测试
 -a FILE:同-e
 -e FILE: 文件存在性测试,存在为真,否则为假
存在性及类别测试
 -b FILE:是否存在且为块设备文件
 -c FILE:是否存在且为字符设备文件
 -d FILE:是否存在且为目录文件
 -f FILE:是否存在且为普通文件
 -h FILE 或 -L FILE:存在且为符号链接文件
 -p FILE:是否存在且为命名管道文件
 -S FILE:是否存在且为套接字文件

【例47】存在性及类别测试

[root@localhost data]# touch f1.sh
[root@localhost data]# [ -d /etc/ ];echo $?
0
[root@localhost data]# [ -d f1.sh ];echo $?
1
5.7、⽂件权限测试:
-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
5.8、⽂件特殊权限测试
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限
5.9、⽂件属性测试
文件大小测试:
 -s FILE: 是否存在且非空
文件是否打开:
 -t fd: fd 文件描述符是否在某终端已经打开
 -N FILE:文件自从上一次被读取之后是否被修改过
 -O FILE:当前有效用户是否为文件属主
 -G FILE:当前有效用户是否为文件属组
双目测试:
 FILE1 -ef FILE2: FILE1是否是FILE2的硬链接
 FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)
 FILE1 -ot FILE2: FILE1是否旧于FILE2
5.10、组合测试
第一种方式:
 EXPRESSION1 -a EXPRESSION2 并且
 EXPRESSION1 -o EXPRESSION2 或者
 ! EXPRESSION
 必须使用测试命令进行,[[ ]] 不支持
第二种方式:
 COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN
 COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE
 ! COMMAND 非
 如:[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]]

【例48】⽂件权限测试,只能⽤[],不能⽤[[]]。

[root@localhost data]# ll f1.sh 
-rw-r--r-- 1 root root 23 Mar 16 02:01 f1.sh
[root@localhost data]# [ -r f1.sh -o -w f1.sh ];echo $?
0
[root@localhost data]# [[ -r f1.sh -o -w f1.sh ]];echo $?
-bash: syntax error in conditional expression
-bash: syntax error near `-o'
[root@localhost data]# [ ! -r f1.sh ];echo $?
1
[root@localhost data]# [ -r f1.sh ];echo $?
0

【例49】组合测试

[root@localhost data]# [ -w f1.sh ] && echo f1.sh is writeable || echo f1.sh is not writeable
f1.sh is writeable

【例50】测试,主机是否开机?

[root@localhost data]# ping 192.168.23.118 -c1 -W1 && echo the host is up. ||echo the host is down.
PING 192.168.23.118 (192.168.23.118) 56(84) bytes of data.
64 bytes from 192.168.23.118: icmp_seq=1 ttl=64 time=0.124 ms

--- 192.168.23.118 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.124/0.124/0.124/0.000 ms
the host is up.
[root@localhost data]# ping 192.168.23.128 -c1 -W1 && echo the host is up. ||echo the host is down.
PING 192.168.23.128 (192.168.23.128) 56(84) bytes of data.

--- 192.168.23.128 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

the host is down.

#"&> /dev/null",不显示多余的内容、相当于丢到垃圾箱。

[root@localhost data]# ping 192.168.23.118 -c1 -W1 &> /dev/null && echo the host is up. ||echo the host is down.
the host is up.
[root@localhost data]# ping 192.168.23.128 -c1 -W1 &> /dev/null && echo the host is up. ||echo the host is down.
the host is down.

6、read命令:

使用read来把输入值分配给一个或多个shell变量
 -p 指定要显示的提示
 -s 静默输入,一般用于密码
 -n N 指定输入的字符长度N
 -d ‘字符’ 输入结束符
 -t N TIMEOUT为N秒
read 从标准输入中读取值,给每个单词分配一个变量。所有剩余单词都被分配给最后一个变量
read -p "Enter a filename: " FILE

【例51】read命令:

[root@localhost data]# echo a b c| { read x y z ; echo $x $y $z; }
a b c

[root@localhost data]# read -p "Are you OK?" ANS
Are you OK?ok
[root@localhost data]# echo $ANS
ok

【例52】read命令:yesorno.使⽤read来把输⼊值分配给⼀个或多个shell变量,-p 指定要显⽰的提⽰.

[root@localhost data]# vim yesorno.sh
#!/bin/bash
read -p "Do you agree? (yes or no): " ANSWER
[[ "$ANSWER" =~ ^[Yy]([Ee][Ss])?$ ]] && { echo ok ; exit; }
[[ "$ANSWER" =~ ^[Nn][Oo]?$ ]] && { echo Not ok ; exit; } 
echo "Your input is false."

[root@localhost data]# bash yesorno.sh
Do you agree? (yes or no): y
ok
[root@localhost data]# bash yesorno.sh
Do you agree? (yes or no): l
Your input is false.

【例53】使⽤read来把输⼊值分配给⼀个或多个shell变量,-s 静默输⼊,⼀般⽤于密码.

[root@localhost data]# read -s password
[root@localhost data]# echo $password
123456

【例54】使⽤read来把输⼊值分配给⼀个或多个shell变量,-n N 指定输⼊的字符长度N.

[root@localhost data]# read -n 3 name
abc[root@localhost data]# 

【例55】使⽤read来把输⼊值分配给⼀个或多个shell变量,-d ʻ字符ʼ 输⼊结束符.

[root@localhost data]# read -d 0 name
asdasda0[root@localhost data]# 

【例56】使⽤read来把输⼊值分配给⼀个或多个shell变量,-t N TIMEOUT为N秒.

[root@localhost data]# read -t 3 name
[root@localhost data]# 

7、[ ]和[[ ]] 的区别。

 [ ] 实际上是bash 中 test 命令的简写。即所有的 [ expr ] 等于 test expr。对 test 命令来说, 用
-eq 要进行数字比较,而你此时传入字符串,就报错了。
 
 [[ expr ]] 是bash中真正的条件判断语句,其语法更符合编程习惯 (比如 &&, || 的用法),虽然我认
为在 [[ ]] 中 故意传字符串给 -eq 也应该像 test 一样报错,但是显然bash实现中直接把非整数的字符串直
接转换成了 0 (你可以自行验证,在 [[ ]] 中的,任何需要整数,但是提供的确又是其他不能转换成整数的字
符串,都变成了0)。 这应该是bash实现中的没有对 [[ ]] 中 -eq 操作符两边的内容进行检查导致的。

8、判断语句()、 (( ))和{}的区别。

1、单⼩括号 ()

 ①命令组。括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括
号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
 ②命令替换。等同于`cmd`,shell扫描一遍命令行,发现了$(cmd)结构,便将$(cmd)中的cmd执行一次,得到
其标准输出,再将此输出放到原来命令。有些shell不支持,如tcsh。
 ③用于初始化数组。如:array=(a b c d)

2、双⼩括号 (( ))

 ①整数扩展。这种扩展计算是整数型的计算,不支持浮点型。((exp))结构扩展并计算一个算术表达式的值,
如果表达式的结果为0,那么返回的退出状态码为1,或者 是"假",而一个非零值的表达式所返回的退出状态码将
为0,或者是"true"。若是逻辑判断,表达式exp为真则为1,假则为0。
 ②只要括号中的运算符、表达式符合C语言运算规则,都可用在$((exp))中,甚至是三目运算符。作不同进位
(如二进制、八进制、十六进制)运算时,输出结果全都自动转化成了十进制。如:echo $((16#5f)) 结果为95
(16进位转十进制)
 ③单纯用 (( )) 也可重定义变量值,比如 a=5; ((a++)) 可将 $a 重定义为6
 ④常用于算术运算比较,双括号中的变量可以不使用$符号前缀。括号内支持多个表达式用逗号分开。 只要括
号中的表达式符合C语言运算规则,比如可以直接使用for((i=0;i<5;i++)), 如果不使用双括号, 则为for i in
`seq 0 4`或者for i in {0..4}。再如可以直接使用if (($i<5)), 如果不使用双括号, 则为if [ $i -lt 5
]。(1)单小括号,(cmd1;cmd2;cmd3) 新开一个子shell顺序执行命令cmd1,cmd2,cmd3, 各命令之间用分号隔开,
最后一个命令后可以没有分号。
(2)单大括号,{ cmd1;cmd2;cmd3;} 在当前shell顺序执行命令cmd1,cmd2,cmd3, 各命令之间用分号隔开,
最后一个命令后必须有分号, 第一条命令和左括号之间必须用空格隔开。

3、⼤括号、花括号 {}

 ①大括号拓展。(通配(globbing))将对大括号中的文件名做扩展。在大括号中,不允许有空白,除非这个空白
被引用或转义。第一种:对大括号中的以逗号分割的文件列表进行拓展。如 touch {a,b}.txt 结果为a.txt
b.txt。第二种:对大括号中以点点(..)分割的顺序文件列表起拓展作用,如:touch {a..d}.txt 结果为
a.txt b.txt c.txt d.txt
 # ls {ex1,ex2}.sh 
 ex1.sh ex2.sh 
 # ls {ex{1..3},ex4}.sh 
 ex1.sh ex2.sh ex3.sh ex4.sh 
 # ls {ex[1-3],ex4}.sh 
 ex1.sh ex2.sh ex3.sh ex4.sh 
 ②代码块,又被称为内部组,这个结构事实上创建了一个匿名函数 。与小括号中的命令不同,大括号内的命令
不会新开一个子shell运行,即脚本余下部分仍可使用括号内变量。括号内的命令间用分号隔开,最后一个也必须
有分号。{}的第一个命令和左括号之间必须要有一个空格。

实验二:建里条件脚本

目的

建立条件脚本。

前提

linux系统,centos6、centos7或ubuntu,连接网络。

命令介绍

1、条件选择if语句

选择执行:
注意:if语句可嵌套
 单分支
 if 判断条件;then
 条件为真的分支代码
 fi
 双分支
 if 判断条件; then
 条件为真的分支代码
 else
 条件为假的分支代码
 fi
 
多分支
if 判断条件1; then
 条件1为真的分支代码
elif 判断条件2; then
 条件2为真的分支代码
elif 判断条件3; then
 条件3为真的分支代码
else
 以上条件都为假的分支代码
fi
逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句

【例57】成绩判断

[root@localhost data]# vim score.sh
#!/bin/bash
read -p "please input your score: " SCORE
if [[ ! $SCORE =~ ^[0-9]+$ ]]; then
echo "please input a digit!"
exit
elif [ $SCORE -lt 60 ];then
echo "no pass!"
elif [ $SCORE -lt 80 ];then
 echo "so so!"
elif [ $SCORE -le 100 ];then
 echo "very good!"
else
echo "bug!"
fi
[root@localhost data]# bash score.sh
please input your score: k
please input a digit!
[root@localhost data]# bash score.sh
please input your score: 65
so so!
[root@localhost data]# bash score.sh
please input your score: 101
bug!

【例58】鸡兔同笼,头37,脚94.

[root@localhost data]# vim chook_rabbit.sh
#!/bin/bash
read -p "please input the head number: " head
read -p "please input the foot number: " foot
rabbit=$[foot/2-head]
chook=$[head-rabbit]
echo "rabbit:" $rabbit
echo "chook:" $chook
[root@localhost data]# bash chook_rabbit.sh
please input the head number: 37
please input the foot number: 94
rabbit: 10
chook: 27

【例59】busybox

[root@localhost data]# vim busybox.sh
#!/bin/bash
FILE=`basename $0`
if [ "$FILE" = 'rm' ];then
echo rm
elif [ "$FILE" = 'cp' ];then
 echo cp
elif [ "$FILE" = 'mv' ];then
 echo mv
else 
echo busybox
fi
[root@localhost data]# bash busybox.sh
busybox

2、条件判断:case语句

case 变量引用 in
PAT1)
 分支1
 ;;
PAT2)
 分支2
 ;;
...
*)
 默认分支
 ;;
esac

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

【例60】case的应⽤。

[root@localhost data]# help case
case: case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
 Execute commands based on pattern matching.
 
 Selectively execute COMMANDS based upon WORD matching PATTERN. The
 `|' is used to separate multiple patterns.
 
 Exit Status:
 Returns the status of the last command executed.
[root@localhost data]# vim menu.sh
#!/bin/bash
cat << eof
1:大盘鸡
2:宫保鸡丁
3:烤鸭
4:龙虾
5:退出
eof
read -p "choose a number:" menu
case $menu in
1|2)
echo price is 50.
;;
3)
 echo price is 100.
;;
4)
 echo price is 200.
;;
*)
exit
esac
[root@localhost data]# bash menu.sh
1:大盘鸡
2:宫保鸡丁
3:烤鸭
4:龙虾
5:退出
choose a number:4
price is 200.

3、bash如何展开命令⾏

把命令行分成单个命令词
展开别名
展开大括号的声明({})
展开波浪符声明(~)
命令替换$() 和 ``)
再次把命令行分成命令词
展开文件通配(*、?、[abc]等等)
准备I/0重导向(<、>)
运行命令

4、防⽌扩展

反斜线(\)会使随后的字符按原意解释
 echo Your cost: \$5.00 
 Your cost: $5.00
加引号来防止扩展
 • 单引号(’’)防止所有扩展
 • 双引号(”“)也可防止扩展,但是以下情况例外:
 $(美元符号) 变量扩展
 ` ` (反引号) 命令替换
 \(反斜线) 禁止单个字符扩展
 !(叹号) 历史命令替换

【例61】扩展。

[root@localhost data]# history
 1 vim /etc/sysconfig/selinux
 2 systemctl stop firewalld
 3 systemctl disable firewalld
 4 yum install autofs -y
 5 ping www.baidu.com
 6 yum clean all
 7 ping www.baidu.com
 8 yum install autofs -y
 
[root@localhost data]# vim history.sh
#!/bin/bash

ls
hostname
history

[root@localhost data]# bash history.sh
3 8 backup.sh f1.sh menu.sh son.sh
4 9 busybox.sh falt.txt score.sh systeminfo.sh
5 arg.sh chook_rabbit.sh father.sh script36.zip yesorno.sh
6 backup-script correct.txt hello.sh script37
7 backup-script2019-05-14 exit.sh history.sh sendfile.sh
localhost.localdomain

说明:history未显⽰。

5、bash的配置⽂件。按⽣效范围划分,存在两类:

全局配置:
 /etc/profile
 /etc/profile.d/*.sh
 /etc/bashrc
个人配置:
 ~/.bash_profile
 ~/.bashrc
修改profile和bashrc文件后需生效
 1重新启动shell进程
 2 . 或source

【例62】.bashrc和.bash_profile

[root@localhost ~]# cat .bashrc 
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
	. /etc/bashrc
fi

[root@localhost ~]# cat .bash_profile 
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
	. ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin

export PATH

6、set 命令

$- 变量
 h:hashall,打开这个选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h
选项关闭
 i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的shell。所谓的交互式
shell,在脚本中,i选项是关闭的。
 m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等。
 B:braceexpand,大括号扩展
 H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的一
个历史命令,“!n”返回第 n 个历史命令
set 命令
 -u 在扩展一个没有设置的变量时,显示错误信息,等同set –o nounset
 -e 如果一个命令返回一个非0退出状态值(失败)就退出,等同set –o errexit

【例63】set 命令,-u 在扩展⼀个没有设置的变量时,显⽰错误信息,等同set –o nounset。

[root@localhost ~]# vim set.sh
#!/bin/bash

#set -u
set -e
echo $var
echo continue

[root@localhost ~]# bash set.sh

continue
[root@localhost ~]# vim set.sh
#!/bin/bash
set -u
set -e
echo $var
echo continue

[root@localhost ~]# bash set.sh
set.sh: line 14: var: unbound variable