SHELL脚本编程之基础篇

成功不易,加倍努力!

      • 1 编程基础
        • 1.1 程序组成
        • 1.2 程序编程风格
        • 1.3 编程语言
        • 1.4 编程逻辑处理方式
      • 2 shell 脚本语言的基本结构
        • 2.1 shell 脚本的用途
        • 2.2 shell脚本基本结构
        • 2.3 创建shell脚本过程
        • 2.4 脚本注释规范
        • 2.5 脚本调试
        • 2.6 变量
        • 2.7 格式化输出 printf
        • 2.8 算术运算
        • 2.9 逻辑运算
        • 2.10 条件测试命令
        • 2.11 关于() 和 {}
        • 2.12 组合测试条件
        • 2.13 使用read命令来接受输入
      • 3 bash的配置文件
        • 3.1 按生效范围划分两类
        • 3.2 shell登录两种方式分类
        • 3.3 按功能划分分类
        • 3.4 编辑配置文件生效
        • 3.5 Bash 退出任务
      • 4 流程控制
        • 4.1 条件选择
      • 5 SHELL脚本编程进阶

1 编程基础

Linus:Talk is cheap, show me the code

1.1 程序组成

程序:算法+数据结构
数据:是程序的核心
算法:处理数据的方式
数据结构:数据在计算机中的类型和组织方式

1.2 程序编程风格

  • 面向过程语言
    • 做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理
    • 问题规模小,可以步骤化,按部就班处理
    • 以指令为中心,数据服务于指令
    • C,shell
  • 面向对象语言
    • 一种认识世界、分析世界的方法论。将万事万物抽象为各种对象
    • 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
    • 对象是类的具象,是一个实体
    • 问题规模大,复杂系统
    • 以数据为中心,指令服务于数据
    • java,C#,python,golang等

1.3 编程语言

计算机:运行二进制指令

编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言

  • 低级编程语言:
    机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写
    汇编:用一些助记符号替代机器指令,称为汇编语言
    • 如:ADD A,B
    • 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中
    • 汇编语言写好的程序需要汇编程序转换成机器指令
    • 汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言
  • 高级编程语言:
    • 编译:高级语言–>编译器–>机器代码文件–>执行,如:C,C++ (一桌菜) 执行效率高
    • 解释:高级语言–>执行–>解释器–>机器代码,如:shell,python,php,JavaScript,perl (吃火锅)开发效率高

1.4 编程逻辑处理方式

三种处理逻辑

  • 顺序执行
  • 选择执行
  • 循环执行

2 shell 脚本语言的基本结构

2.1 shell 脚本的用途

  • 自动化常用命令
  • 执行系统管理和故障排除
  • 创建简单的应用程序
  • 处理文本或文件

2.2 shell脚本基本结构

shell脚本编程:是基于过程式、解释执行的语言

编程语言的基本结构:

  • 各种系统命令的组合
  • 数据存储:变量、数组
  • 表达式:a + b
  • 控制语句:if

shell脚本:包含一些命令或声明,并符合一定格式的文本文件

格式要求:首行shebang机制

#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl 

2.3 创建shell脚本过程

第一步:使用文本编辑器来创建文本文件
第一行必须包括shell声明序列:#!

示例: #!/bin/bash
添加注释,注释以#开头

第二步:加执行权限
给予执行权限,在命令行上指定脚本的绝对或相对路径

第三步:运行脚本
直接运行解释器,将脚本作为解释器程序的参数运行

2.4 脚本注释规范

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

2.5 脚本调试

只检测脚本中的语法错误,但无法检查出命令错误,但不真正执行脚本

bash -n /path/to/some_script

调试并执行

bash -x /path/to/some_script

范例:

[20:39:57 root@centos7 scripts]#cat -A  f1.sh
#!/bin/bash$

echo line1$
cat > test.txt <<EOF$
aaa$
bbb$
ccc$
EOF $
echo line2$
[20:52:19 root@centos7 scripts]#bash -n f1.sh
f1.sh: line 18: warning: here-document at line 13 delimited by end-of-file (wanted `EOF`)

[20:43:17 root@centos7 scripts]#bash -x f1.sh
+ echo line1
line1
+ cat
+ echo line2
line2

总结:脚本错误常见的有三种

  • 语法错误,会导致后续的命令不继续执行,可以用bash-n检查错误,提示的出错行数不一定是准确的
  • 命令错误,后续的命令还会继续执行,用bash -n 无法检查出来 ,可以使用 bash -x 进行观察
  • 逻辑错误:只能使用 bash -x 进行观察

注意:别名在脚本中无效

2.6 变量

2.6.1 变量

变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据

2.6.2 变量类型

变量类型:

  • 内置变量,如:PS1,PATH,UID,HOSTNAME,BASHPID,PPID,HISTSIZE
  • 用户自定义变量

不同的变量存放的数据不同,决定了以下

  1. 数据存储方式
  2. 参与的运算
  3. 表示的数据范围

变量数据类型:

  • 字符
  • 数值:整型、浮点型,bash 不支持浮点数

2.6.3 编程语言分类

静态和动态语言

  • 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
  • 动态编译语言:不用事先声明,可随时改变类型,如:bash,Python

强类型和弱类型语言

  • 强类型语言:不同类型数据操作,必须经过强制转换才同一类型才能运算,如java , c# ,python
    如:以下python代码
    print(‘magedu’+ 10) 提示出错,不会自动转换类型
    print(‘magedu’+str(10)) 结果为magedu10,需要显示转换类型
  • 弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
    如:bash ,php,javascript

2.6.4 Shell中变量命名法则

  • 不能使程序中的保留字:如:if, for
  • 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”,和主机名相反
  • 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM,不要用拼音
  • 统一命名规则:驼峰命名法, studentname,大驼峰StudentName 小驼峰studentName
  • 变量名大写:STUDENT_NAME
  • 局部变量小写
  • 函数名小写

2.6.5 变量定义和引用

变量的生效范围等标准划分变量类型

  • 普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
  • 环境变量:生效范围为当前shell进程及其子进程
  • 本地变量:生效范围为当前shell进程中某代码片断,通常指函数

变量赋值:
name='value'

value 可以是以下多种形式

直接字串:name='root'  
变量引用:name="$USER"  
命令引用:name=`COMMAND` 或者 name=$(COMMAND)

注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量会随着脚本结束,也会自动删除

变量引用:

$name
${name} 

弱引用和强引用

  • "$name " 弱引用,其中的变量引用会被替换为变量值
  • '$name ’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

注意:输出时保留原格式用双引号“ ”

范例:变量的各种赋值方式

[21:06:28 root@centos7 scripts]#TITLE=cto
[21:12:35 root@centos7 scripts]#echo $TITLE
cto
[21:12:41 root@centos7 scripts]#echo I AM $TITLE
I AM cto
[21:14:38 root@centos7 scripts]#echo "I AM $TITLE"
I AM cto
[21:14:53 root@centos7 scripts]#echo 'I AM $TITLE'
I AM $TITLE

[21:15:00 root@centos7 scripts]#NAME=$USER
[21:16:15 root@centos7 scripts]#echo $NAME
root
[21:16:24 root@centos7 scripts]#USER=`whoami`
[21:17:11 root@centos7 scripts]#echo $USER
root

[21:17:25 root@centos7 scripts]#FILE=`ls /tmp`
[21:18:57 root@centos7 scripts]#ls /tmp
ks-script-wUthWl            vmware-root_620-2697598252
vmware-root_618-2697467179  vmware-root_639-3988031840
vmware-root_619-4013919989  yum.log
[21:19:03 root@centos7 scripts]#echo $FILE
ks-script-wUthWl vmware-root_618-2697467179 vmware-root_619-4013919989 vmware-root_620-2697598252 vmware-root_639-3988031840 yum.log

[21:21:30 root@centos7 etc]#FILE=/etc/*
[21:22:01 root@centos7 etc]#echo $FILE
/etc/adjtime /etc/aliases /etc/aliases.db /etc/alternatives /etc/anacrontab

[21:40:01 root@centos7 ~]#seq 10
1
2
3
4
5
6
7
8
9
10
[21:25:28 root@centos7 ~]#NUM=`seq 10`
[21:39:38 root@centos7 ~]#echo $NUM
1 2 3 4 5 6 7 8 9 10
[21:40:07 root@centos7 ~]#echo "$NUM"
1
2
3
4
5
6
7
8
9
10

[21:40:48 root@centos7 ~]#NAMES="
> cui
> liu
> zhang
> li
> "
[21:41:49 root@centos7 ~]#echo $NAMES
cui liu zhang li
[21:42:04 root@centos7 ~]#echo "$NAMES"
cui
liu
zhang
li

范例:

[21:53:38 root@centos7 ~]#NAME=cui
[22:06:00 root@centos7 ~]#AGE=23
[22:06:28 root@centos7 ~]#echo $NAME $AGE
cui 23
[22:06:47 root@centos7 ~]#echo $NAME$AGE
cui23
[22:06:53 root@centos7 ~]#echo $NAME_$AGE
23
[22:07:19 root@centos7 ~]#echo ${NAME}_$AGE
cui_23

显示已定义的所有变量:
set
删除变量:
unset

范例:显示系统信息

[22:44:54 root@centos7 ~]#vim systeminfo.sh
[22:46:56 root@centos7 ~]#bash systeminfo.sh
--------------------Host systeminfo----------------
HOSTNAME:centos7
IPADDR:10.0.0.7
OSVERSION:CentOS Linux release 7.7.1908 (Core)
KERNEL:3.10.0-1062.el7.x86_64
CPU: Intel(R) Core(TM) i5-4210M CPU @ 2.60GHz
MEMORY:1.8G
DISK:sda:8:0:0:200G:0:disk:
---------------------------------------------------
[22:46:58 root@centos7 ~]#cat systeminfo.sh
#!/bin/bash

RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"
$GREEN--------------------Host systeminfo----------------$END
echo -e "HOSTNAME:$RED`hostname`$END"
echo -e "IPADDR:$RED`ifconfig eth0 |grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}'|head -n1`$END"
echo -e "OSVERSION:$RED`cat /etc/redhat-release`$END"
echo -e "KERNEL:$RED`uname -r`$END"
echo -e "CPU:$RED`lscpu |grep 'Model name'|tr -s ' '|cut -d : -f2`$END"
echo -e "MEMORY:$RED`free -h |grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e "DISK:$RED`lsblk |grep '^sd' |tr -s ' ' : |cut -d " " -f4`$END"
$GREEN---------------------------------------------------$END

2.6.6 环境变量

环境变量:

  • 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
  • 一般只在系统配置文件中使用,在脚本中较少使用

变量声明和赋值:

#声明并赋值
export name=VALUE
declare -x name=VALUE
#或者分两步实现
name=VALUE
export name

变量引用:

$name
${name}

显示所有环境变量:

env
printenv
export
declare -x

删除变量:

unset name

bash内建的环境变量

PATH
SHELL
USER
UID
HOME
PWD
SHLVL #shell的嵌套层数,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_   #下划线 表示前一命令的最后一个参数

2.6.7 只读变量

只读变量:只能声明定义,但后续不能修改和删除,即常量

声明只读变量:

readonly name
declare  -r name

查看只读变量:

readonly [-p]
declare -r

2.6.8 位置变量

位置变量:在bash shell中内置的变量, 在脚本代码中调用通过命令行传递给脚本的参数

$1, $2, ... 对应第1个、第2个等参数,shift [n]换位置
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
注意:$@ $* 只在被双引号包起来的时候才会有差异

清空所有位置变量
set –

范例:

[root@centos7 scripts]#cat arg.sh

echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${10}"
echo "11st arg is ${11}"

echo "The number of arg is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"
set --

echo "All args are $@"

[root@centos7 scripts]#bash /data/scripts/arg.sh {a..z}
1st arg is a
2st arg is b
3st arg is c
10st arg is j
11st arg is k
The number of arg is 26
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
The scriptname is arg.sh
All args are

范例:删库跑路之命令rm的安全实现

[root@centos7 ~]#cat /data/scripts/rm.sh 
#!/bin/bash
WARNING_COLOR="echo -e \E[1;31m"
END="\E[0m"
DIR=/tmp/`date +%F_%H-%M-%S`
mkdir $DIR
mv  $*  $DIR
${WARNING_COLOR} Move $* to $DIR $END

[root@centos7 ~]#chmod +x /data/scripts/rm.sh
[root@centos7 ~]#alias rm=/data/scripts/rm.sh

[root@centos7 ~]#touch b c d
[root@centos7 ~]#rm b c d
 Move b c d to /tmp/2020-04-02_14-48-57 
[root@centos7 ~]#tree /tmp
/tmp
├── 2020-04-02_14-48-57
│   ├── b
│   ├── c
│   └── d

2.6.9 退出状态码变量

当我们浏览网页时,有时会看到下图所显示的数字,表示网页的错误信息,我们称为状态码,在shell脚本中也有相似的技术表示程序执行的相应状态。

进程执行后,将使用变量$?存状态码的相关数字,不同的值反应成功或失败 $? 0-255

$?的值为0        #代表成功
$?的值是1到255   #代表失败

用户可以在脚本中使用以下命令自定义退出状态码
exit [n]

注意

  • 脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
  • 如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

2.6.10 展开命令行
展开命令执行顺序

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

防止扩展

反斜线(\)会使随后的字符按原意解释

加引号来防止扩展

单引号(’’)防止所有扩展
双引号(”“)也可防止扩展,但是以下情况例外:$(美元符号)

变量扩展

`` : 	反引号,命令替换
\:	反斜线,禁止单个字符扩展
!:	叹号,历史命令替换

2.6.11 脚本安全和 set

set 命令:可以用来定制 shell 环境
$- 变量
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
-o option 显示,打开或者关闭选项
显示选项:set -o
打开选项:set -o 选项
关闭选项:set +o 选项
-x 当执行命令时,打印命令及其参数,类似 bash -x

2.7 格式化输出 printf

格式:

printf "指定的格式" "文本1" ”文本2“……

常用格式替换符

替换符  功能
%s      字符串
%f      浮点格式
%b      相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%c      ASCII字符,即显示对应参数的第一个字符
%d,%i   十进制整数
%o      八进制值
%u      不带正负号的十进制值
%x      十六进制值(a-f)
%X      十六进制值(A-F)
%%      表示%本身

说明:%s 中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,- 表示左对齐

常用转义字符

转义符  功能
\a      警告字符,通常为ASCII的BEL字符
\b      后退
\f      换页
\n      换行
\r      回车
\t      水平制表符
\v      垂直制表符
\       表示\本身

范例:

[root@centos7 ~]#printf "%s\n" 1 2 3 4
1
2
3
4
[root@centos7 ~]#printf "%f\n" 1 2 3 4
1.000000
2.000000
3.000000
4.000000

#.2f 表示保留两位小数
[root@centos7 ~]#printf "%.2f\n" 1 2 3 4
1.00
2.00
3.00
4.00
[root@centos7 ~]#printf "(%s)" 1 2 3 4;echo
(1)(2)(3)(4)
[root@centos7 ~]#printf " (%s) " 1 2 3 4;echo ""
 (1)  (2)  (3)  (4) 
[root@centos7 ~]#printf "(%s)\n" 1 2 3 4
(1)
(2)
(3)
(4)
[root@centos7 ~]#printf "%s %s\n" 1 2 3 4
1 2
3 4
[root@centos7 ~]#printf "%s %s%s\n" 1 2 3 4
1 23
4 

[root@centos7 ~]#printf "%-10s %-10s %-4s %s \n" 姓名 性别  年龄 体重 小明 男 20 70 小红 女 15 50
姓名     性别     年龄 体重 
小明     男        20   70 
小红     女        15   50 

[root@centos7 ~]#VAR="welcome to magedu";printf "\033[31m%s\033[0m\n" $VAR
welcome
to
magedu
[root@centos7 ~]#VAR="welcome to magedu";printf "\033[31m%s\033[0m\n" "$VAR"
welcome to magedu

2.8 算术运算

shell 支持算术运算,但只支持整数,不支持小数
bash中的算术运算

+
-
*
/
% 取模,即取余数,示例:9%4=1,5%3=2
** 乘方

乘法符号有些场景中需要转义
实现算术运算:

(1) let var=算术表达式
(2) var=$[算术表达式]
(3) var=$((算术表达式))
(4) var=$(expr arg1 arg2 arg3 ...)
(5) declare –i var = 数值
(6) echo '算术表达式' | bc 

内建的随机数生成器变量:

$RANDOM   取值范围:0-32767

范例:

#生成 0 - 49 之间随机数
echo $[$RANDOM%50]

#随机字体颜色
[root@centos7 ~]#echo -e "\e[1;$[RANDOM%7+31]mhello\e[0m"
hello

增强型赋值:

+= i+=10 	相当于 i=i+10
-= i-=j   	相当于 i=i-j
*=
/=
%=
++ i++,++i 	 相当于 i=i+1
-- i--,--i  	 相当于 i=i-1

格式:

let varOPERvalue

范例:

[root@centos7 ~]#i=10
[root@centos7 ~]#let i+=20
[root@centos7 ~]#echo $i
30
[root@centos7 ~]#j=20
[root@centos7 ~]#let i*=j
[root@centos7 ~]#echo $i
600

范例:

#自增,自减
let var+=1
let var++
let var-=1
let var--

[root@centos7 ~]#i=1; let j=i++ ; echo "i=$i , j=$j"
i=2 , j=1
[root@centos7 ~]#i=1; let j=++i ; echo "i=$i , j=$j"
i=2 , j=2

范例:

[root@centos7 ~]#expr 2 * 3
expr: syntax error
[root@centos7 ~]#expr 2 \* 3
6

[root@centos7 ~]#echo "scale=3;20/3"|bc
6.666

范例:面试题,算出所有人的年龄之和

[root@centos7 scripts]#cat nianling.txt 
xiaoming=20
xiaohong=18
xiaoqiang=22
[root@centos7 scripts]#cut -d"=" -f2 nianling.txt|tr '\n' + |grep -Eo ".*[0-9]" |bc
60
[root@centos7 scripts]#grep -Eo "[0-9]+" nianling.txt|tr '\n' + |grep -Eo ".*[0-9]" |bc
60

2.9 逻辑运算

true, false
1, 0
与:&:和0相与,结果为0,和1相与,结果保留原值
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或:|:和1相或结果为1,和0相或,结果保留原值

1 或 1 = 1
1 或 0 = 1
 0 或 1 = 1
 0 或 0 = 0 

非:!
! 1 = 0 ! true
! 0 = 1 ! false
异或:^
异或的两个值,相同为假,不同为真。两个数字X,Y异或得到结果Z,Z再和任意两者之一X异或,将得出另一个值Y
同性相吸

1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0 

范例:

#x,y值互换的两种办法
[root@centos7 ~]#x=10;y=20;temp=$x;x=$y;y=$temp;echo x=$x,y=$y
x=20,y=10
[root@centos7 ~]#x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo x=$x,y=$y
x=20,y=10

短路运算

  • 短路与
    CMD1 短路与 CMD2
    • 第一个CMD1结果为 0 (假 ),总的结果必定为0,因此不需要执行CMD2
    • 第一个CMD1结果为 1 (真),第二个CMD2必须要参与运算,才能得到最终的结果
  • 短路或
    CMD1 短路或 CMD2
    • 第一个CMD1结果为1 (真),总的结果必定为1,因此不需要执行CMD2
    • 第一个CMD1结果为0 (假 ),第二个CMD2 必须要参与运算,才能得到最终的结果

2.10 条件测试命令

条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成
测试过程
若真,则状态码变量 $? 返回0
若假,则状态码变量 $? 返回1

条件测试命令

  • test EXPRESSION
  • [ EXPRESSION ] #和test 等价,建议使用 [ ]
  • [[ EXPRESSION ]]

注意:EXPRESSION前后必须有空白字符

2.10.1 变量测试

-v VAR 变量VAR是否设置
-R VAR 变量VAR是否设置并引用

示例:判断 NAME 变量是否定义

[ -v NAME ]

范例:

[root@centos7 scripts]#y=
[root@centos7 scripts]#test -v y
[root@centos7 scripts]#echo $?
0

#注意 [ ] 需要空格,否则会报下面错误
[root@centos7 scripts]#[-v y]
-bash: [-v: command not found
[root@centos7 scripts]#[ -v y ]
[root@centos7 scripts]#echo $?
0

2.10.2 数值测试

-eq 是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于

范例:

[root@centos7 scripts]#i=10
[root@centos7 scripts]#j=8
[root@centos7 scripts]#[ $i -lt $j ]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#[ $i -gt $j ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#[ i -gt j ]
-bash: [: i: integer expression expected

2.10.3 字符串测试

test和 [ ]用法
-z STRING 字符串是否为空,没定义或空为真,不空为假,
-n STRING 字符串是否不空,不空为真,空为假
   STRING   同上
STRING1 = STRING2 是否等于,注意 = 前后有空格
STRING1 != STRING2 是否不等于
> ascii码是否大于ascii码
< 是否小于
[[]] 用法,建议,当使用正则表达式或通配符使用,一般情况使用 [ ]
== 左侧字符串是否和右侧的PATTERN相同
 注意:此表达式用于[[ ]]中,PATTERN为通配符
=~ 左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配
 注意: 此表达式用于[[ ]]中;扩展的正则表达式

范例:

#判断字符串是否为空
[root@centos7 scripts]#unset str
[root@centos7 scripts]#[ -z "$str"]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#str=""
[root@centos7 scripts]#[ -z "$str"]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#str=" "
[root@centos7 scripts]#[ -z "$str" ]
[root@centos7 scripts]#echo $?
1

#判断字符串是否不空
[root@centos7 scripts]#[ -n "$str" ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#unset str
[root@centos7 scripts]#[ -n "$str" ]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#str=magedu
[root@centos7 scripts]#[  "$str" ]
[root@centos7 scripts]#echo $?
0

#判断字符串是否相等
[root@centos7 scripts]#str1=magedu
[root@centos7 scripts]#str2=mage
[root@centos7 scripts]#[ $str1 = $str2 ]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#str2=magedu
[root@centos7 scripts]#[ $str1 = $str2 ]
[root@centos7 scripts]#echo $?
0

范例:在比较字符串时,建议变量放在“ ”中

[root@centos7 scripts]#NAME="I love linux"
[root@centos7 scripts]#[ $NAME ]
-bash: [: love: binary operator expected
[root@centos7 scripts]#[ "$NAME" ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#[ I love linux ]
-bash: [: love: binary operator expected

范例:

#通配符
[root@centos7 scripts]#FILE=test.log
[root@centos7 scripts]#[[ "$FILE" == *.log ]]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#FILE=test.txt
[root@centos7 scripts]#[[ "$FILE" == *.log ]]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#[[ "$FILE" != *.log ]]
[root@centos7 scripts]#echo $?
0

#正则表达式
[root@centos7 scripts]#[[ "$FILE" =~ \.log$ ]]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#FILE=test.log
[root@centos7 scripts]#[[ "$FILE" =~ \.log$ ]]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#
[root@centos7 scripts]#N=100
[root@centos7 scripts]#[[ "$N" =~ ^[0-9]+$ ]]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#N=cui
[root@centos7 scripts]#[[ "$N" =~ ^[0-9]+$ ]]
[root@centos7 scripts]#echo $?
1

[root@centos7 scripts]#IP=1.2.3.4
[root@centos7 scripts]#[[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#IP=1.2.3.4567
[root@centos7 scripts]#[[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
[root@centos7 scripts]#echo $?
1

[root@centos7 scripts]#0-9|10-99|100-199|200-249|250-255^C
[root@centos7 scripts]#[[ $IP =~ (([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|2[0-5][0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|2[0-5][0-5])$ ]]
[root@centos7 scripts]#echo $?
1

#通配符
[root@centos7 scripts]#NAME="linux1"
[root@centos7 scripts]#[[ "$NAME" == linux* ]]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#[[ "$NAME" == "linux*" ]]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#NAME="linux*"
[root@centos7 scripts]#[[ "$NAME" == "linux*" ]]
[root@centos7 scripts]#echo $?
0
#结论:[[ == ]] == 右侧的 * 做为通配符,不要加“”,只想做为*, 需要加“” 或转义

2.10.4 文件测试

存在性测试

-a FILE:同 -e
-e FILE: 文件存在性测试,存在为真,否则为假
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件

范例:

[root@centos7 scripts]#[ -a /etc/nologin ]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#! [ -a /etc/nologin ]
[root@centos7 scripts]#echo $?
0

[root@centos7 scripts]#[ -d /etc ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#[ -d /etc/issue ]
[root@centos7 scripts]#echo $?
1

[root@centos7 scripts]#[ -L /bin ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#[ -L /bin/ ]
[root@centos7 scripts]#echo $?
1

文件权限测试:

-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限

注意最终结果由用户对文件的实际权限决定,而非文件属性决定

范例:

[root@centos7 scripts]#[ -w /etc/shadow ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#[ -x /etc/shadow ]
[root@centos7 scripts]#echo $?
1

[root@centos7 scripts]#[ -w nianling.txt ]
[root@centos7 scripts]#echo $?
0
[root@centos7 scripts]#chattr +i nianling.txt
[root@centos7 scripts]#lsattr nianling.txt
----i----------- nianling.txt
[root@centos7 scripts]#[ -w nianling.txt ]
[root@centos7 scripts]#echo $?
1
[root@centos7 scripts]#chattr -i nianling.txt
[root@centos7 scripts]#[ -w nianling.txt ]
[root@centos7 scripts]#echo $?
0

文件属性测试

-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

2.11 关于() 和 {}

(CMD1;CMD2;…)和 { CMD1;CMD2;…; } 都可以将多个命令组合在一起,批量执行

[root@centos8 ~]#man bash

( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境, 帮助参看:man bash

{ list; } 不会启子shell, 在当前shell中运行,会影响当前shell环境, 帮助参看:man bash 搜索{ list; }

范例:()和{ }

[root@centos7 ~]#name=mage;(echo $name;name=cui;echo $name);echo $name
mage
cui
mage
[root@centos7 ~]#name=mage;{ echo $name;name=cui;echo $name; };echo $name
mage
cui
cui

[root@centos7 ~]#umask
0022
[root@centos7 ~]#(umask 066;touch f1.txt)
[root@centos7 ~]#ll f1.txt 
-rw------- 1 root root 0 Apr  4 08:52 f1.txt
[root@centos7 ~]#umask
0022

[root@centos7 ~]#( cd /data;ls )
scripts
[root@centos7 ~]#pwd
/root
[root@centos7 ~]#{ cd /data;ls; }
scripts
[root@centos7 data]#pwd
/data

#()会开启子shell
[root@centos7 ~]#echo $BASHPID
1377
[root@centos7 ~]#( echo $BASHPID;sleep 100 )
2219
[root@centos7~]#sshd(884)─┬─sshd(1375)───bash(1377)───bash(2219)───sleep(2220)

#{ } 不会开启子shell
[root@centos7 data]#echo $BASHPID
1377
[root@centos7 data]#{ echo $BASHPID; }
1377

2.12 组合测试条件

2.12.1 第一种方式 [ ]

[ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为
真
[ ! EXPRESSION ] 取反

说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持

范例:

[root@centos7 data]#FILE=/data/scripts/test.sh
[root@centos7 data]#[ -f $FILE -a -x $FILE ]
[root@centos7 data]#echo $?
1
[root@centos7 data]#chmod +x /data/scripts/test.sh
[root@centos7 data]#ll /data/scripts/test.sh
-rwxr-xr-x 1 root root 422 Apr  1 16:38 /data/scripts/test.sh
[root@centos7 data]#[ -f $FILE -a -x $FILE ]
[root@centos7 data]#echo $?
0
[root@centos7 data]#chmod -x /data/scripts/test.sh
[root@centos7 data]#ll /data/scripts/test.sh
-rw-r--r-- 1 root root 422 Apr  1 16:38 /data/scripts/test.sh
[root@centos7 data]#[ -f $FILE -o -x $FILE ]
[root@centos7 data]#echo $?
0
[root@centos7 data]#[ -x $FILE ]
[root@centos7 data]#echo $?
1
[root@centos7 data]#[ ! -x $FILE ]
[root@centos7 data]#echo $?
0
[root@centos7 data]#! [ -x $FILE ]
[root@centos7 data]#echo $?
0

2.12.2 第二种方式 [[ ]]

COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2

COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND   #非,取反

范例:

[root@centos7 data]#test "$A" = "$B" || echo "strings are noequal"
strings are noequal
[root@centos7 data]#test "$A" -eq "$B" || echo "integers are noequal"
integers are noequal
[root@centos7 data]#[ "$A" -eq "$B" ] && echo "integers are equal"
#eq前后必须是数字
[root@centos7 data]#[ "A" -eq "B" ] && echo "integers are equal"          
-bash: [: A: integer expression expected

#-f 是否存在且具有执行权限
[root@centos7 data]#[ -f /bin/cat -a -x /bin/cat ] && echo "good luck"
good luck
[root@centos7 data]#ll /bin/cat
-rwxr-xr-x. 1 root root 54080 Aug 20  2019 /bin/cat

#-z 字符串是否为空,没定义或空为真
[root@centos7 data]#[ -z "$HOSTNAME" -o "$HOSTNAME"="localhost.localdomain" ] && hostname centos7-cui  
#若主机名没定义或者是默认值,则改名

#若用户不存在,则创建
[root@centos7 data]#id cui &> /dev/null || useradd cui
[root@centos7 data]#id zhang &> /dev/null || useradd zhang
[root@centos7 data]#getent passwd zhang
zhang:x:1001:1001::/home/zhang:/bin/bash
[root@centos7 data]#grep -q no_such_user /etc/passwd || echo "no such user"
no such user

范例:

[root@centos7 data]#[ -f "$FILE" ] && [[ "$FILE" =~ .*\.sh$  ]] &&  chmod +x $FILE
[root@centos7 data]#ll $FILE
-rwxr-xr-x 1 root root 422 Apr  1 16:38 /data/scripts/test.sh

#网络状态判断
[root@centos7 data]#ping -c1 -w1 172.16.0.1 &> /dev/null && echo '172.16.0.1 is up' || (echo '172.16.0.1 is unreachable'; exit 1)
172.16.0.1 is unreachable
[root@centos7 data]#ping -c1 -w1 10.0.0.7 &> /dev/null && echo '10.0.0.7 is up' || (echo '10.0.0.7 is unreachable'; exit 1)
10.0.0.7 is up

#网络状态判断更通用的写法
[root@centos7 data]#IP=10.0.0.7;ping -c1 -w1 $IP &> /dev/null && echo $IP is up || echo $IP is down
10.0.0.7 is up
[root@centos7 data]#IP=10.0.0.777;ping -c1 -w1 $IP &> /dev/null && echo $IP is up || echo $IP is down
10.0.0.777 is down

范例:网络状态判断

[root@centos7 data]#cat /data/scripts/ping.sh
#!/bin/bash
IP=10.0.0.7
ping -c1 -w1 $IP &> /dev/null && echo '$IP is up' || { echo '$IP is unreachable'; exit; }
echo "Scripts is finished"
[root@centos7 data]#bash /data/scripts/ping.sh
$IP is up
Scripts is finished

范例:磁盘空间的判断

[root@centos7 ~]#cat /data/scripts/disk_check.sh 
#!/bin/bash

WARNING=80
SPACE_USED=`df |grep "^/dev/sd" |grep -Eo "[0-9]+%" |tr -d % |sort -nr |head -1`
[ "$SPACE_USED" -ge $WARNING  ] && echo "DISK_USED is $SPACE_USED%,will be full" |mail -s "Disk Warning" [email protected]

范例:磁盘空间和Inode号的检查脚本

[root@centos7 ~]#cat /data/scripts/disk_check.sh 
#!/bin/bash

WARNING=80
SPACE_USED=`df |grep "^/dev/sd" |grep -Eo "[0-9]+%" |tr -d % |sort -nr |head -1`
INODE_USED=`df -i |grep "^/dev/sd" |grep -Eo "[0-9]+%" |tr -d % |sort -nr |head -1`
[ "$SPACE_USED" -ge $WARNING -o "$INODE_USED" -ge $WARNING ] && echo "DISK_USED is $SPACE_USED%,INODE_USED is $INODE_USED%will be full" |mail -s "Disk Warning" [email protected]

2.13 使用read命令来接受输入

使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量

格式:

read [options] [name ...]

常见选项:

-p   		指定要显示的提示
-s   		静默输入,一般用于密码
-n N 		指定输入的字符长度N
-d '字符'    输入结束符
-t N 		TIMEOUT为N秒

面试题: read和输入重定向

[root@centos7 scripts]#cat test.txt
1 2
[root@centos7 scripts]#read i j < test.txt ; echo i=$i j=$j
i=1 j=2

[root@centos7 scripts]#echo 1 2 | read x y ; echo x=$x y=$y
x= y=
[root@centos7 scripts]#echo 1 2 | ( read x y ; echo x=$x y=$y )
x=1 y=2
[root@centos7 scripts]#echo 1 2 | { read x y ; echo x=$x y=$y; }
x=1 y=2
#官方帮助说明
[root@centos7 scripts]#man bash  
Each command in a pipeline is  executed  as  a  separate process (i.e., in a subshell).

Pipelines:A pipeline is a sequence of one or more commands separated by one of
the control operators | or |&

范例:read -p 的用法之“你有钱吗”

[root@centos7 scripts]#cat read.sh
#!/bin/bash

read -p "Are you rish? yes or no :" ANSWER
[[ $ANSWER =~ ^[Yy]|[Yy][Ee][Ss]$ ]] && echo "土豪我们交朋友吧" || echo "玩什么玩,滚去学习!"

范例:实现运维工作菜单

[root@centos7 scripts]#cat work_menu.sh
#!/bin/bash

echo -en "\E[$[RANDOM%7+31];1m"
cat <<EOF
1)备份数据库
2)清理日志
3)软件升级
4)软件回滚
5)删库跑路
EOF
echo -en '\E[0m'

read -p "请输入上面数字1-5: " MENU
[ $MENU -eq 1 ] && ./etc_backup.sh
[ $MENU -eq 2 ] && echo "清理日志"
[ $MENU -eq 3 ] && echo "软件升级"
[ $MENU -eq 4 ] && echo "软件回滚"
[ $MENU -eq 5 ] && echo "删库跑路"

[root@centos7 scripts]#cat etc_backup.sh 
#!/bin/bash
SRC=/etc
DEST=/data/etc_backup_`date +%F_%H-%M-%S`
cp -a $SRC $DEST
[ $? -eq 0 ] && echo "备份完成" || echo "备份失败" 

3 bash的配置文件

bash shell的配置文件很多,可以分成下面类别

3.1 按生效范围划分两类

全局配置:

/etc/profile
/etc/profile.d/*.sh
/etc/bashrc

个人配置:

~/.bash_profile
~/.bashrc

3.2 shell登录两种方式分类

3.2.1 交互式登录

  • 直接通过终端输入账号密码登录
  • 使用 su - UserName 切换的用户

配置文件执行顺序:

/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc  #此文件执行两次
.bashrc
.bash_profile

注意:source不会开启子进程,用来配置当前环境,不用于运行脚本

注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序

3.2.2 非交互式登录

  • UserName
  • 图形界面下打开的终端
  • 执行脚本
  • 任何其它的bash实例

执行顺序:

/etc/profile.d/*.sh
/etc/bashrc
.bashrc

3.3 按功能划分分类

profile类和bashrc类

3.3.1 profile类

profile类为交互式登录的shell提供配置

全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile

功用:
(1) 用于定义环境变量
(2) 运行命令或脚本

3.3.2 Bashrc类

bashrc类:为非交互式和交互式登录的shell提供配置

全局:/etc/bashrc
个人:~/.bashrc

功用:
(1) 定义命令别名和函数
(2) 定义本地变量

3.4 编辑配置文件生效

修改profile和bashrc文件后需生效两种方法:

  1. 重新启动shell进程
  2. source|. 配置文件

3.5 Bash 退出任务

保存在~/.bash_logout文件中(用户),在退出登录shell时运行
功能:

  • 创建自动备份
  • 清除临时文件

4 流程控制

4.1 条件选择

4.1.1 选择执行 if 语句
格式:

if COMMANDS; then COMMANDS;[ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi

单分支

if 判断条件;then
   条件为真的分支代码
fi

双分支

if 判断条件; then
 条件为真的分支代码
else
 条件为假的分支代码
fi

多分支

if 判断条件1; then
 条件1为真的分支代码
elif 判断条件2; then
 条件2为真的分支代码
elif 判断条件3; then
 条件3为真的分支代码
...
else
 以上条件都为假的分支代码
fi

说明:

  • 多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
  • if 语句可嵌套

范例:身体质量指数(BMI)

[root@centos7 scripts]#cat bmi_if.sh
#!/bin/bash

"请输入身高(m为单位): " HIGH

if [[ ! "$HIGH" =~ ^[0-2]\.?[0-9]{,2}$ ]];then
    echo "输入错误的身高"
    exit 1
fi

read -p "请输入体重(kg为单位): " WEIGHT

if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]];then
    echo "输入错误的体重"
    exit 1
fi

BMI=`echo $WEIGHT/$HIGH^2|bc`

if [ $BMI -le 18 ];then
    echo "你太瘦了,多吃点吧伙计!"
elif [ $BMI -lt 24 ];then
    echo "身材太棒了,羡慕!"
else
    echo "小胖子少吃点吧,浪费粮食!"
fi

4.1.2 条件判断 case 语句
格式:

case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
case 变量引用 in
PAT1)
 分支1
 ;;
PAT2)
 分支2
 ;;
...
*)
 默认分支
 ;;
esac

case支持glob风格的通配符:

*: 任意长度任意字符
?: 任意单个字符
[]:指定范围内的任意单个字符
|:   或,如 a或b

范例:

[root@centos7 scripts]#cat case_yesorno.sh
#!/bin/bash

"Do you agree (yes/no)? " INPUT

INPUT=`echo $INPUT | tr 'A-Z' 'a-z'`

case $INPUT in
y|yes)
    echo "You input is YES"
    ;;
n|no)
    echo "You input is NO"
    ;;
*)
    echo "Input false,please input yes or no"
esac

#yes or no 加强版
[root@centos7 scripts]#cat case_yesorno.sh
#!/bin/bash

read -p "Do you agree (yes/no)? " INPUT

case $INPUT in
[Yy]|[Yy][Ee][Ss])
    echo "You input is YES"
    ;;
[Nn]|[Nn][Oo])
    echo "You input is NO"
    ;;
*)
    echo "Input false,please input yes or no"
esac

范例:运维菜单实现版本2

[root@centos7 scripts]#cat work_menu.sh 
#!/bin/bash

 "\E[$[RANDOM%7+31];1m"
cat <<EOF
请选择:
1)备份数据库
2)清理日志
3)软件升级
4)软件回滚
5)删库跑路
EOF
echo -en '\E[0m'

read -p "请输入上面数字1-5: " MENU

case $MENU in
1)
    ./etc_backup.sh
    ;;
2)
    echo "清理日志"
    ;;
3)
    echo "软件升级"
    ;;
4)
    echo "软件回滚"
    ;;
5)
    echo "删库跑路"
    ;;
*)
    echo "INPUT FALSE"
esac

5 SHELL脚本编程进阶

链接跳转:Linux进阶_shell脚本编程进阶

你可能感兴趣的:(Linux基础)