苦行僧DH

博客园 首页 新随笔 联系 订阅 管理

0 史上最全的Shell指南

ps: 你不会在第二个地方找到这么全的指南

1 shell简介

1.1 核心命令

1.1.1 筛选信息

shell快捷键

快捷键符号

命令执行:	
    !! 					执行上一条命令
    !num 				执行历史命令中的第num行命令
    Ctrl 关键字 		  执行内容匹配的命令
命令行切换
	Ctrl + A 			光标迅速回到行首
	Ctrl + E			光标迅速回到行尾
	Ctrl + k			删除光标到行尾内容
	Ctrl + u			删除光标到行首内容
	Ctrl + y			粘贴删除的内容
	Ctrl + c			临时终止命令行命令
	Esc + b				移动到当前单词的开头
	Esc + f				移动到当前单词的结尾

grep命令

负责从数据源中检索对应的字符串,行过滤

grep options 'keys' filename
OPTIONS:
    -i: 不区分大小写
    -v: 查找不包含指定内容的行,反向选择
    -w: 按单词搜索
    -o: 打印匹配关键字
    -c: 统计匹配到的次数
    -n: 显示行号
    -r: 逐层遍历目录查找
    -A: 显示匹配行及后面多少行	
    -B: 显示匹配行及前面多少行
    -C: 显示匹配行前后多少行
    -l:只列出匹配的文件名
    -L:列出不匹配的文件名
    -e: 使用正则匹配
    -E:使用扩展正则匹配
    ^key:以关键字开头
    key$:以关键字结尾
    ^$:匹配空行
    --color=auto :可以将找到的关键词部分加上颜色的显示
grep 高亮显示
centos7中已经为大家设置了,存放在/etc/profile.d/colorgrep.sh文件中,如若大家使用的系统中没有设置颜色输出,可以使用以下方法来自行设置

临时设置:
# alias grep='grep --color=auto'			//只针对当前终端和当前用户生效

永久设置:
1)全局(针对所有用户生效)
vim /etc/bashrc
alias grep='grep --color=auto'
source /etc/bashrc

2)局部(针对具体的某个用户)
vim ~/.bashrc
alias grep='grep --color=auto'
source ~/.bashrc
常用命令选项必知必会  示例:
# grep -i root passwd					忽略大小写匹配包含root的行
# grep -w ftp passwd 					精确匹配ftp单词
# grep -wo ftp passwd 					打印匹配到的关键字ftp
# grep -n root passwd 					打印匹配到root关键字的行好
# grep -ni root passwd 					忽略大小写匹配统计包含关键字root的行
# grep -nic root passwd					忽略大小写匹配统计包含关键字root的行数
# grep -i ^root passwd 					忽略大小写匹配以root开头的行
# grep bash$ passwd 					匹配以bash结尾的行
# grep -n ^$ passwd 					匹配空行并打印行号
# grep ^# /etc/vsftpd/vsftpd.conf		匹配以#号开头的行
# grep -v ^# /etc/vsftpd/vsftpd.conf	匹配不以#号开头的行
# grep -A 5 mail passwd 				匹配包含mail关键字及其后5行
# grep -B 5 mail passwd 				匹配包含mail关键字及其前5行
# grep -C 5 mail passwd 				匹配包含mail关键字及其前后5行

sort命令

信息排序

语法:
sort [options] file
将文件的每一行作为一个单位,从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

options:
-u :去除重复行
-r :降序排列,默认是升序
-o : 将排序结果输出到文件中  类似 重定向符号>
-n :以数字排序,默认是按字符排序
-t :分隔符
-k :第N列
-b :忽略前导空格。
-R :随机排序,每次运行的结果均不同。
文件内容
[root@localhost ~]# cat num.txt
9
8
6
8
4
7
2
1

内容升序
[root@localhost ~]# sort -n num.txt
1
2
4
6
7
8
8
9

内容降序
[root@localhost ~]# sort -r num.txt
9
9
8
7
6
4
2
1

其他实践
sort -nu num.txt				升序去重
sort -ru num.txt				降序去重
sort -nru num.txt				先升序后降序再去重
sort -R num.txt					随机排序
sort -nu num.txt -o /tmp/a.txt	升序去重后输出到一个文件

1.1.2 处理信息

cut命令

数据截取

Mandatory arguments to long options are mandatory for short options too.
  -b, --bytes=列表              只选中指定的这些字节
  -c, --characters=列表         只选中指定的这些字符
  -d, --delimiter=分界符        使用指定分界符代替制表符作为区域分界
  -f, --fields=LIST       select only these fields;  also print any line
                            that contains no delimiter character, unless
                            the -s option is specified
  -n                      with -b: don't split multibyte characters
      --complement              补全选中的字节、字符或域
  -s, --only-delimited          不打印没有包含分界符的行
      --output-delimiter=字符串 使用指定的字符串作为输出分界符,默认采用输入
                                的分界符
      --help            显示此帮助信息并退出
      --version         显示版本信息并退出

tr命令

字符转换、替换、删除

用法:tr [选项]... SET1 [SET2]
从标准输入中替换、缩减和/或删除字符,并将结果写到标准输出。

  -c, -C, --complement          首先补足SET1
  -d, --delete                  删除匹配SET1 的内容,并不作替换
  -s, --squeeze-repeats 		如果匹配于SET1 的字符在输入序列中存在连续的
                                重复,在替换时会被统一缩为一个字符的长度
  -t, --truncate-set1           先将SET1 的长度截为和SET2 相等
      --help            		显示此帮助信息并退出
      --version         		显示版本信息并退出
      
用法1:把commands命令输出做为tr输入进行处理
commands | tr  'string1'  'string2'

用法2:把文件中的内容输入给tr进行处理
tr  'string1'  'string2' < filename

用法3:把文件中的内容输入给tr进行处理,需要使用到选项
tr options 'string1' < filename

uniq命令

连续信息去重

Mandatory arguments to long options are mandatory for short options too.
  -c, --count           统计重复行次数
  -d, --repeated        只显示重复行
  -i, --ignore-case     忽略大小写
  -s, --skip-chars=N    avoid comparing the first N characters
  -u, --unique          only print unique lines
文件内容
[root@localhost ~]# cat uniq.txt
AA
aa
aa
bb
cc
cc
dd

去重演示
[root@localhost ~]# uniq uniq.txt
AA
aa
bb
cc
dd

其他演示
uniq -i uniq.txt			大小写不敏感去重
uniq -ic uniq.txt			大小写不敏感去重后计数
uniq -d uniq.txt			仅显示重复的内容
sort -n num.txt  | uniq		结合sort排序后去重

1.1.3 组合信息

paste命令

合并文件行内容输出到屏幕,不会改动源文件

Mandatory arguments to long options are mandatory for short options too.
  -d, --delimiters=列表 改用指定列表里的字符替代制表分隔符
  -s, --serial          不使用平行的行目输出模式,而是每个文件占用一行
      --help            显示此帮助信息并退出
      --version         显示版本信息并退出
文件内容
[root@localhost ~]# cat a.txt
hello
[root@localhost ~]# cat b.txt
world
888
999

内容合并
[root@localhost ~]# paste a.txt b.txt
hello   world
        888
        999
[root@localhost ~]# paste b.txt a.txt
world   hello
888
999

自定义分隔符后合并内容
[root@localhost ~]# paste -d'@' b.txt a.txt
world@hello
888@
999@

将一个文件所有内容一行输出
[root@localhost ~]# paste -s b.txt
world   888     999
[root@localhost ~]# paste -d'@' -s b.txt
world@888@999

xargs命令

命令结果传递

作用:
    xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。
    xargs 一般是和管道一起使用。

命令格式:
	somecommand |xargs -item  command

OPTIONS:
    -a file 从文件中读入作为sdtin
    -E flag flag必须是一个以空格分隔的标志,当xargs分析到含有flag这个标志的时候就停止。
    -p 当每次执行一个argument的时候询问一次用户。
    -n num 后面加次数,表示命令在执行的时候一次用的argument的个数,默认是用所有的。
    -t 表示先打印命令,然后再执行。
    -i 或者是-I,将xargs接收的每项名称,逐行赋值给 {},可以用 {} 代替。
    -r no-run-if-empty 当xargs的输入为空的时候则停止xargs,不用再去执行了。
    -d delim 分隔符,默认的xargs分隔符是回车,argument的分隔符是空格,这里修改的是xargs的分隔符。


注意:linux命令格式一般为
    命令    命令选项     参数
    上一个命令的输出就是下一个命令的参数  这句话结合命令语法  应该知道输出的内容在下一个命令的位置了吧。
从文件中读取内容
[root@localhost ~]# xargs -a num.txt
9 8 6 8 4 7 2 1

从文件中读取内容时,指定内容结束符号
[root@localhost ~]# xargs -a num.txt -E 4
9 8 6 8

从文件中读取内容时,询问用户是否显示,y显示,其他不显示
[root@localhost ~]# xargs -a num.txt -p
echo 9 8 6 8 4 7 2 1 ?...y
9 8 6 8 4 7 2 1
[root@localhost ~]# xargs -a num.txt -p
echo 9 8 6 8 4 7 2 1 ?...n

从文件中读取内容时,指定每行显示几个内容
[root@localhost ~]# xargs -a num.txt -n4
9 8 6 8
4 7 2 1

从文件中读取内容时,指定每行显示几个内容,没显示一行询问一下用户
[root@localhost ~]# xargs -a num.txt -n4 -p
echo 9 8 6 8 ?...y
9 8 6 8
echo 4 7 2 1 ?...y
4 7 2 1

默认xargs以空格为分隔符,可以通过-d来自定义分隔符
[root@localhost ~]# echo "ab cd ef g" | xargs
ab cd ef g
[root@localhost ~]# echo "nameXnameXnameXname" | xargs -dX
name name name name
[root@localhost ~]# echo "nameXnameXnameXname" | xargs
nameXnameXnameXname
-I 临时存储内容给一个对象,然后进行后续处理
[root@localhost ~]# xargs -a num.txt -n1 -I {} echo {}--bak
9--bak
8--bak
6--bak
8--bak
4--bak
7--bak
2--bak
1--bak

-I的综合运用,转移文件并改名
[root@localhost ~]# ls *.txt
a.txt  b.txt  num.txt  test1.txt  uniq.txt
[root@localhost ~]# ls *.txt | xargs -n1 -I {} cp {} /tmp/{}-bak
[root@localhost ~]# ls /tmp/*-bak
/tmp/a.txt-bak  /tmp/num.txt-bak    /tmp/uniq.txt-bak
/tmp/b.txt-bak  /tmp/test1.txt-bak

1.2 shell基础

1.2.1 shell简介

shell语言

shell定义

在计算机科学中,Shell就是一个命令解释器。
shell是位于操作系统和应用程序之间,是他们二者最主要的接口,shell负责把应用程序的输入命令信息解释给操作系统,将操作系统指令处理后的结果解释给应用程序。
一句话,shell就是在操作系统和应用程序之间的一个命令翻译工具。

1.2.2 shell实践

shell分类

简介

在不同的操作系统上,shell的表现样式是不一样的,它主要分为两类
图形界面shell
	图形界面shell就是我们常说的桌面  
命令行式shell
    windows系统:
    	cmd.exe	 命令提示字符
    linux系统:
    	sh / csh / ksh / bash(默认) / ...

查看系统的shell

查看当前系统的shell类型
[root@localhost ~]# echo $SHELL
/bin/bash

查看当前系统环境支持的shell
[root@localhost ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
更改默认的shell
chsh <用户名> -s <新shell>

[root@localhost ~]# su - python
[python@localhost ~]$ exit
登出
[root@localhost ~]# chsh python -s /bin/sh
Changing shell for python.
Shell changed.
[root@localhost ~]# su - python
上一次登录:二 6月  7 20:11:13 CST 2022pts/0 上
-sh-4.2$ exit
登出
[root@localhost ~]# chsh python -s /bin/bash
Changing shell for python.
Shell changed.
[root@localhost ~]# su - python
上一次登录:二 6月  7 20:11:35 CST 2022pts/0 上
[python@localhost ~]$ echo $SHELL
/bin/bash
安装shell
yum list | grep zsh
yum install -y zsh

查看效果
[root@localhost ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/bin/zsh

shell实践

命令行实践方式

描述:
	手工敲击键盘,在shell的命令行输入命令,按Enter后,执行通过键盘输入的命令,然后shell返回并显示命令执行的结果.
	
重点:
	逐行输入命令、逐行进行确认执行
直接找一个终端界面执行一些可执行的命令即可
[root@localhost ~]# whoami
root
[root@localhost ~]# pwd
/root
[root@localhost ~]# date +"%F %T"
2022-06-07 20:14:46

文件实现方式

描述:
	就是说我们把手工执行的命令a,写到一个脚本文件b中,然后通过执行脚本b,达到执行命令a的效果.
	
重点:
	按照文件内容的顺序执行。
找一个文件,将我们刚才执行成功的命令放到里面。

[root@localhost ~]# cat test
whoami
pwd
date +"%F %T"
[root@localhost ~]# /bin/bash test
root
/root
2022-06-07 20:16:20

1.2.3 shell脚本实践

脚本基础

shell脚本

当可执行的Linux命令或语句不在命令行状态下执行,而是通过一个文件执行时,我们称文件为shell脚本。

应用场景

重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。
 	自动化分析处理
 	自动化备份
 	自动化批量部署安装
 	等等... 

脚本创建

脚本创建工具:
	创建脚本的常见编辑器是	vi/vim.

脚本命名
	shell脚本的命名简单来说就是要有意义,方便我们通过脚本名,来知道这个文件是干什么用的。

脚本内容:
	各种可以执行的命令

脚本注释

单行注释:
	除了首行的#不是注释外,其他所有行内容,只要首个字符是#,那么就表示该行是注释
多行注释:
	多行注释有两种方法::<<! ... !  和 :<<字符 ... 字符

脚本实践

脚本示例1

脚本内容
[root@localhost ~]# cat get_netinfo.sh
#!/bin/bash
# 功能:获取当前主机的网卡设备信息
# 作者:wangshuji
# 版本:V0.1
# 联系:www.superopsmsb.com

# 获取ip地址信息
ifconfig eth0 | grep -w inet | awk '{print $2}' | xargs echo "IP: "

# 获取掩码地址信息
ifconfig eth0 | grep -w inet | awk '{print $4}' | xargs echo "NetMask: "

# 获取广播地址信息
ifconfig eth0 | grep -w inet | awk '{print $6}' | xargs echo "Broadcast: "

# 获取MAC地址信息
ifconfig eth0 | grep ether | awk '{print $2}' |xargs echo "MAC Address: "
执行脚本
[root@localhost ~]# /bin/bash get_netinfo.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c

脚本注释

更改脚本内容
[root@localhost ~]# cat get_netinfo.sh
#!/bin/bash
:<<!
功能:获取当前主机的网卡设备信息
作者:wangshuji
版本:V0.1
联系:www.superopsmsb.com
!
... ...

1.2.4 脚本执行

脚本执行

脚本执行方法

方法1:
	bash /path/to/script-name	或	/bin/bash /path/to/script-name	(强烈推荐使用)
	
方法2:
	/path/to/script-name		或	./script-name	(当前路径下执行脚本)
	
方法3:
	source script-name			或	. script-name	(注意“.“点号)
	
方法1变种:
	cat /path/to/script-name | bash
	bash /path/to/script-name
脚本执行说明
1、脚本文件本身没有可执行权限或者脚本首行没有命令解释器时使用的方法,我们推荐用bash执行。
	使用频率:☆☆☆☆☆
2、脚本文件具有可执行权限时使用。
	使用频率:☆☆☆☆
3、使用source或者.点号,加载shell脚本文件内容,使shell脚本内容环境和当前用户环境一致。
    使用频率:☆☆☆
    使用场景:环境一致性

执行示例

方法1:
[root@localhost ~]# /bin/bash get_netinfo.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c
方法2:
[root@localhost ~]# ./get_netinfo.sh
bash: ./get_netinfo.sh: 权限不够
[root@localhost ~]# ll get_netinfo.sh
-rw-r--r-- 1 root root 521 6月   7 20:41 get_netinfo.sh
[root@localhost ~]# chmod +x get_netinfo.sh
[root@localhost ~]# ./get_netinfo.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c
方法3:
[root@localhost ~]# source get_netinfo.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c
[root@localhost ~]# chmod -x get_netinfo.sh
[root@localhost ~]# ll get_netinfo.sh
-rw-r--r-- 1 root root 521 6月   7 20:41 get_netinfo.sh
[root@localhost ~]# source get_netinfo.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c
方法1变种:
[root@localhost ~]# cat get_netinfo.sh | bash
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c
[root@localhost ~]# bash < get_netinfo.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
MAC Address:  00:0c:29:23:23:8c

1.2.5 脚本调试

脚本调试

需求

	我们在编写脚本的时候,往往会受到各种因素的限制,导致脚本功能非常大或者内容有误,如果直接执行脚本的时候,因为脚本内容有误,导致脚本执行失败。
	所以我们需要在脚本执行的时候,保证脚本没问题,我们可以借助于多种脚本调试方式来验证脚本。

调试方式

-n		检查脚本中的语法错误
-v		先显示脚本所有内容,然后执行脚本,结果输出,如果执行遇到错误,将错误输出。
-x		将执行的每一条命令和执行结果都打印出来

简单实践

准备工作

准备备份文件
cp get_netinfo.sh get_netinfo-error.sh

设置错误文件
[root@localhost ~]# cat get_netinfo-error.sh
...
# 将最后一行末尾的"取消
ifconfig eth0 | grep ether | awk '{print $2}' |xargs echo "MAC Address: "
错误脚本执行效果
[root@localhost ~]# /bin/bash get_netinfo-error.sh
IP:  10.0.0.12
NetMask:  255.255.255.0
Broadcast:  10.0.0.255
get_netinfo-error.sh:行19: 寻找匹配的 `"' 是遇到了未预期的文件结束符
get_netinfo-error.sh:行20: 语法错误: 未预期的文件结尾

检查语法实践

检查语法实践

[root@localhost ~]# /bin/bash -n get_netinfo-error.sh
get_netinfo-error.sh:行19: 寻找匹配的 `"' 是遇到了未预期的文件结束符
get_netinfo-error.sh:行20: 语法错误: 未预期的文件结尾

检查语法调试

[root@localhost ~]# /bin/bash -v get_netinfo-error.sh
#!/bin/bash
:<<!
功能:获取当前主机的网卡设备信息
作者:wangshuji
版本:V0.1
联系:www.superopsmsb.com
!

# 获取ip地址信息
ifconfig eth0 | grep -w inet | awk '{print $2}' | xargs echo "IP: "
IP:  10.0.0.12

# 获取掩码地址信息
ifconfig eth0 | grep -w inet | awk '{print $4}' | xargs echo "NetMask: "
NetMask:  255.255.255.0

# 获取广播地址信息
ifconfig eth0 | grep -w inet | awk '{print $6}' | xargs echo "Broadcast: "
Broadcast:  10.0.0.255

# 获取MAC地址信息
ifconfig eth0 | grep ether | awk '{print $2}' |xargs echo "MAC Address:
get_netinfo-error.sh:行19: 寻找匹配的 `"' 是遇到了未预期的文件结束符
get_netinfo-error.sh:行20: 语法错误: 未预期的文件结尾

脚本跟踪实践

失败演示
[root@localhost ~]# /bin/bash -x get_netinfo-error.sh
+ :
+ ifconfig eth0
+ awk '{print $2}'
+ grep -w inet
+ xargs echo 'IP: '
IP:  10.0.0.12
+ ifconfig eth0
+ grep -w inet
+ awk '{print $4}'
+ xargs echo 'NetMask: '
NetMask:  255.255.255.0
+ ifconfig eth0
+ grep -w inet
+ xargs echo 'Broadcast: '
+ awk '{print $6}'
Broadcast:  10.0.0.255
get_netinfo-error.sh:行19: 寻找匹配的 `"' 是遇到了未预期的文件结束符
get_netinfo-error.sh:行20: 语法错误: 未预期的文件结尾
成功演示
[root@localhost ~]# /bin/bash -x get_netinfo.sh
+ :
+ ifconfig eth0
+ awk '{print $2}'
+ grep -w inet
+ xargs echo 'IP: '
IP:  10.0.0.12
+ ifconfig eth0
+ grep -w inet
+ awk '{print $4}'
+ xargs echo 'NetMask: '
NetMask:  255.255.255.0
+ ifconfig eth0
+ grep -w inet
+ awk '{print $6}'
+ xargs echo 'Broadcast: '
Broadcast:  10.0.0.255
+ ifconfig eth0
+ grep ether
+ awk '{print $2}'
+ xargs echo 'MAC Address: '
MAC Address:  00:0c:29:23:23:8c

1.2.6 脚本开发规范

开发规范

脚本规范

1、脚本命名要有意义,文件后缀是.sh
2、脚本文件首行是而且必须是脚本解释器
	#!/bin/bash
3、脚本文件解释器后面要有脚本的基本信息等内容
	脚本文件中尽量不用中文注释;
	尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰
	常见的注释信息:脚本名称、脚本功能描述、脚本版本、脚本作者、联系方式等
4、脚本文件常见执行方式:bash 脚本名
5、脚本内容执行:从上到下,依次执行
6、代码书写优秀习惯;
    1)成对内容的一次性写出来,防止遗漏。
如:()、{}、[]、''、``、""
    2)[]中括号两端要有空格,书写时即可留出空格[    ],然后再退格书写内容。
    3)流程控制语句一次性书写完,再添加内容
7、通过缩进让代码易读;(即该有空格的地方就要有空格)

规范解析

编写梳理

shell脚本开发规范重点:2-4-5
shell脚本开发小技巧:3-6-7

其他技巧

1 尽可能记忆更多的命令
2 掌握脚本的标准的格式
3 多看、多模仿、多思考

2 shell变量

2.1 变量基础

2.1.1 变量场景

数据存储

数据存储

所谓的数据存储,我们从三方面来理解这句话:
    1、数据保存到哪里	--	各种媒介,CPU、内存、磁盘、磁带、网盘...
    2、数据保存的效果	--	完整、安全、有效
    3、数据保存的单元	--	存储空间
	数据的存储空间默认不是一个整体,而是由一个个的存储单元组成,每一个存储单元都有一个唯一的整数编号,我们称这个编号为:地址
    存储单元的作用:存储数据+读写数据
    存储空间大小:1字节(B) = 8bit == 00000000
    地址特点:十六进制,例如:0x20000000
对于数据的存储来说,主要有两种样式:物理地址和逻辑地址。
    物理地址:内存或硬盘中真正存储数据的位置,也就是说通过磁盘设备查找的位置
    逻辑地址:用于查找物理地址的存储块地址叫逻辑地址。程序中用的地址一般都是逻辑地址
    逻辑地址包括两部分:起始值(十六进制)+偏移量(十六进制)
    数据表的描述主要是逻辑地址,因为程序一般存储的是逻辑地址。
数据一旦存储下来就不再发生变化了,而程序中可能会在很多场景中用到同一个数据,就会出现两个问题:
	1 物理地址人听得懂,机器看不懂 
		- 所以用逻辑地址找物理地址
	2 软件可以通过逻辑地址找到数据地址,但是软件不懂场景
		- 所以人用逻辑地址的别名来代指向同一个xx地址
		
应用程序中为了 多场景应用这个逻辑地址的别名,本质上就是“变量”。

变量场景

变量的本质

变量的本质其实就是 通过一个名称帮助程序快速找到内存中具体数据的地址。
	- 变量说白了就是指向xx值。

编程语言

编程语言在数据调用层面分类的话,可以分为两类:
	静态编译语言:
		使用变量前,先声明变量类型,之后类型不能改变,在编译时检查。
		如:java,c
	动态编译语言:
		不用事先声明,可随时改变类型。
		如:bash,Python
根据编程语言在使用变量的程度上,可以划分为强类型、弱类型语言:
    强类型语言:
    	不同类型数据操作,必须经过强制转换才同一类型才能运算。
    		如java , c# ,python
        示例:
            print('shuji' + 10) 提示出错,不会自动转换类型
            print('shuji' + str(10)) 结果为magedu10,需要显示转换类型
    弱类型语言:
    	语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;
    	参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用。
    	如:bash ,php,javascript
    	示例:
    		echo 'aaa'+222

2.1.2 量定义

这一节,我们从 变量定义、变量分类、小结 三个方面来学习。

变量定义

变量定义

变量包括三部分:
	变量名 - 不变的
	变量值 - 变化的
	赋值动作 - 变量名指向变量值
表现样式:
	变量名=变量值
	变量的全称应该成为变量赋值,简称变量,在工作中,我们一般只xx是变量,其实是是将这两者作为一个整体来描述了。准确来说,我们一般所说的变量其实指的是:变量名。

命名规范

1 名称有意义
2 名称细节
	命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
    中间不能有空格,可以使用下划线(_)。
    不能使用标点符号。
    不能使用bash里的关键字(可用help命令查看保留关键字)。
3 命名样式
	大驼峰HelloWorld,每个单词的首字母是大写
	小驼峰helloWorld,第一个单词的首字母小写,后续每个单词的首字母是大写
	下划线: Hello_World
	大小写字母: helloworld, HELLOWORLD
注意:
	对于开发人员来说,他们对于变量名的规范比较多,什么类、函数、对象、属性、命名空间等都有要求
	对于运维人员来说,记住一个词 -- 有意义。

变量分类

shell 中的变量分为三大类:
    本地变量		变量名仅仅在当前终端有效	
    全局变量		变量名在当前操作系统的所有终端都有效
    shell内置变量	shell解析器内部的一些功能参数变量
注意:
	这里的变量分类的特点仅仅是从字面上来理解的,因为在实际的操作的时候,还会涉及到环境优先级的问题
	所以生产中对于这三者的划分没有特别大的强制。

2.1.3 基本操作

这一节,我们从 变量查看、变量定义、变量移除、小结四个方面来学习。

变量查看

语法解析

基本格式
	$变量名

示例

查看默认的shell类型
[root@localhost ~]# echo $SHELL
/bin/bash

变量定义

普通语法解析

基本格式
	变量名=变量值
	
注意:
	= 两侧不允许有空格

示例

查看一个空值变量名
[root@localhost ~]#  echo $myname

定制变量实践
[root@localhost ~]# myname=shuji
[root@localhost ~]#  echo $myname
shuji
错误的定制变量命令
[root@localhost ~]#  echo $myage

[root@localhost ~]# myage = 18
bash: myage: 未找到命令
[root@localhost ~]#  echo $myage

类型变量定义

命令语法
	declare 参数 变量名=变量值
参数解析:
    -i 将变量看成整数 
    -r 使变量只读  readonly,==**该变量的值无法改变,并且不能为unset**==
    -x 标记变量为全局变量,类似于export
    -a	指定为索引数组(普通数组);查看普通数组
    -A 指定为关联数组;查看关联数组
注意:
	在生产场景中,这种方法比较鸡肋,使用频率 0-20次/3年
设定制定类型的变量值
[root@localhost ~]# declare -i mynum='shuzi'
[root@localhost ~]# echo $mynum
0
[root@localhost ~]# declare -i mynum='123456'
[root@localhost ~]# echo $mynum
123456
设定只读类型变量
[root@localhost ~]# declare -r myread1="aaa"
[root@localhost ~]# myread2=myread
[root@localhost ~]# readonly myread2

查看只读变量
[root@localhost ~]# declare -r | grep myread
declare -r myread1="aaa"
declare -r myread2="myread"
[root@localhost ~]# readonly -p | grep myread
declare -r myread1="aaa"
declare -r myread2="myread"

无法使用unset删除只读变量
[root@localhost ~]# unset myread1 myread2
bash: unset: myread1: 无法反设定: 只读 variable
bash: unset: myread2: 无法反设定: 只读 variable

借助于exit方式删除只读变量
[root@localhost ~]# exit
...... 重新登录后再次查看
[root@localhost ~]# declare -r | grep myread
[root@localhost ~]#

变量移除

语法解析

基本格式
	unset 变量名

示例

查看刚才定制的变量名
[root@localhost ~]#  echo $myname
shuji

移除变量名
[root@localhost ~]# unset myname
[root@localhost ~]#  echo $myname

[root@localhost ~]#

2.2 本地变量

2.2.1 本地变量分类

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

本地变量

所谓的本地变量就是:在当前系统的某个环境下才能生效的变量,作用范围小。

变量分类

本地变量按照变量值的生成方式包含两种:
	普通变量:
		自定义变量名和变量值
	命令变量:
		自定义变量名,而变量值是通过一条命令获取的

2.2.2 普通变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

变量分类

所谓的本地变量就是:在当前系统的某个环境下才能生效的变量,作用范围小。本地变量按照变量值的生成方式包含两种:
	普通变量:
		自定义变量名和变量值
	命令变量:
		自定义变量名,而变量值是通过一条命令获取的

基本格式

序号 样式 要点
方式一 变量名=变量值 变量值必须是一个整体,中间没有特殊字符
"=" 前后不能有空格
方式二 变量名='变量值' 原字符输出,我看到的内容,我就输出什么内容,
方式三 变量名="变量值" 如果变量值范围内,有可以解析的变量A,那么首先解析变量A,
将A的结果和其他内容组合成一个整体,重新赋值给变量B
习惯:
	数字不加引号,其他默认加双引号
	因为bash属于弱类型语言,默认会将所有内容当成字符串

变量定义

查看默认的空值变量
[root@localhost ~]# echo $name

方法1设定变量
[root@localhost ~]# name=shuji
[root@localhost ~]# echo $name
shuji

方法2设定变量
[root@localhost ~]# name='shuji1'
[root@localhost ~]# echo $name
shuji1

方法3设定变量
[root@localhost ~]# name="shuji2"
[root@localhost ~]# echo $name
shuji2

清理变量
[root@localhost ~]# unset name

作用区别演示

查看默认的空值变量
[root@localhost ~]# echo $name2

方法1设定变量的要点,变量值必须是一个整体
[root@localhost ~]# name2=shuji haoshuai
bash: haoshuai: 未找到命令
[root@localhost ~]# echo $name2
原因解析:
	空格是一个特殊符号,表示两条命令的隔开
	它将shuji 和 haoshuai当成两条命令了,所以报错信息是命令找不到
	
方法2设定变量
[root@localhost ~]# name2='shuji haoshuai'
[root@localhost ~]# echo $name2
shuji haoshuai

方法3设定变量
[root@localhost ~]# name2="shuji haoweiwu"
[root@localhost ~]# echo $name2
haoweiwu	

清理变量
[root@localhost ~]# unset name2
定制基础变量
[root@localhost ~]# name=shuji
[root@localhost ~]# echo $name
shuji

方法2设定变量
[root@localhost ~]# name2='dan-$name'
[root@localhost ~]# echo $name2
dan-$name

方法3设定变量
[root@localhost ~]# name2="shuang-$name"
[root@localhost ~]# echo $name2
shuang-shuji

2.2.3 命令变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

基本格式

定义方式一:
	变量名=`命令`
    注意:
    ` 是反引号

定义方式二:
	变量名=$(命令)
执行流程:
	1、执行 ` 或者 $() 范围内的命令
	2、将命令执行后的结果,赋值给新的变量名A

简单实践

命令变量实践

查看默认的空值变量
[root@localhost ~]# echo $myuser

方法1设定变量名
[root@localhost ~]# myuser=`whoami`
[root@localhost ~]# echo $myuser
root
查看默认的空值变量
[root@localhost ~]# echo $mydir

方法2设定变量名
[root@localhost ~]# mydir=$(pwd)
[root@localhost ~]# echo $mydir
/root

清理变量
[root@localhost ~]# unset mydir myuser

其他常见的实践

自动生成一系列数字
[root@localhost ~]# NUM=`seq 10`
[root@localhost ~]# echo $NUM
1 2 3 4 5 6 7 8 9 10

文件备份添加时间戳
[root@localhost ~]# touch file-a
[root@localhost ~]# cp file-a file-a-$(date +%F)
[root@localhost ~]# ls file-a*
file-a  file-a-2022-06-08

简单小综合实践

[root@localhost ~]# cat get_netinfo_v2.sh
#!/bin/bash
# 功能:获取当前主机的网卡设备信息
# 作者:wangshuji
# 版本:V0.2
# 联系:www.superopsmsb.com

# 定制基础变量
RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"

# 获取ip地址信息
IPDDR=$(ifconfig eth0 | grep -w inet | awk '{print $2}')
# 获取掩码地址信息
NETMAST=$(ifconfig eth0 | grep -w inet | awk '{print $4}')
# 获取广播地址信息
BROADCAST=$(ifconfig eth0 | grep -w inet | awk '{print $6}')
# 获取MAC地址信息
MACADDR=$(ifconfig eth0 | grep ether | awk '{print $2}')

# 打印网络基本信息
$GREEN---------主机网卡基本信息---------$END
echo -e  "HOSTNAME:     $RED `hostname` $END"
echo -e  "IP:           $RED $IPDDR $END"
echo -e  "NetMask:      $RED $NETMAST $END"
echo -e  "Broadcast:    $RED $BROADCAST $END"
echo -e  "MAC Address:  $RED $MACADDR $END"
$GREEN---------主机网卡基本信息---------$END

2.3 全局变量

2.3.1 基本操作

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

基本定义

全局变量是什么
	全局变量就是:在当前系统的所有环境下都能生效的变量。

基本语法

查看全局环境变量
	env			只显示全局变量,一般结合 grep 和管道符来使用
	printenv	效果与env等同
	
	export		查看所有的环境变量,包括声明的过程等信息,一般不用
	declare -x	效果与export类似
定义全局变量方法一:
	变量=值
	export 变量
定义全局变量方法二:(最常用)
	export 变量=值

查看全局变量

查看所有的全局变量
[root@localhost ~]# env
XDG_SESSION_ID=4
HOSTNAME=localhost
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
...

查看制定的全局变量
[root@localhost ~]# env | grep SHELL
SHELL=/bin/bash
定制本地变量
[root@localhost ~]# envtype=local
[root@localhost ~]# echo $envtype
local

从全局变量中查看
[root@localhost ~]# env | grep envtype
[root@localhost ~]#
结果显示:
	无法从全局变量中查看本地变量的名称
方法1定制全局变量
[root@localhost ~]# echo $envtype
local
[root@localhost ~]# env | grep envtype
[root@localhost ~]# export envtype
[root@localhost ~]# env | grep envtype
envtype=local

方法2定制全局变量
[root@localhost ~]# export myuser=root
[root@localhost ~]# env | grep myuser
myuser=root

鸡肋方法定制全局变量
[root@localhost ~]# declare -x mydir=/root
[root@localhost ~]# env | grep mydir
mydir=/root

清理全局变量
[root@localhost ~]# unset envtype myuser mydir

2.3.2 文件体系

这一节,我们从 变量文件、简单实践、小结 三个方面来学习。

变量文件

变量文件

在linux环境中,有很多目录下的文件都可以定制一些作用范围更广的变量,这些文件或文件所在的目录有:
	作用范围在制定的用户范围:
        ~/.bashrc
        ~/.bash_profile
    作用的范围在系统范围:
        /etc/profile
        /etc/profile.d/env_file_name

简单实践

bashrc 或 bash_profile 实践

查看未知的变量名
[root@localhost ~]# echo $NAME

定制变量名到文件中
[root@localhost ~]# echo NAME=shuji >> ~/.bashrc
[root@localhost ~]# source ~/.bashrc
[root@localhost ~]# echo $NAME
shuji

新开一个终端查看效果
[root@localhost ~]# echo $NAME
shuji

新开一个普通用户的终端查看效果
[root@localhost ~]# su - python
[python@localhost ~]$ echo $NAME

[python@localhost ~]$
清理.bashrc 文件里的变量,然后清除当前环境下的变量名
unset NAME

profile实践

查看未知的变量名
[root@localhost ~]# echo $PROFILE

定制变量名到文件中
[root@localhost ~]# echo PROFILE=shuji >> /etc/profile
[root@localhost ~]# source /etc/profile
[root@localhost ~]# echo $PROFILE
shuji

新开一个终端查看效果
[root@localhost ~]# echo $PROFILE
shuji

新开一个普通用户的终端查看效果
[root@localhost ~]# su - python
[python@localhost ~]$ echo $PROFILE
shuji
[python@localhost ~]$

2.3.3 嵌套shell

这一节,我们从 export原理、嵌套实践、小结 三个方面来学习。

export原理

原理解析

用户登录时:
	用户登录到Linux系统后,系统将启动一个用户shell。
	在这个shell中,可以使用shell命令或声明变量,也可以创建并运行 shell脚本程序。

运行脚本时:
	运行shell脚本程序时,系统将创建一个子shell。	此时,系统中将有两个shell
		- 一个是登录时系统启动的shell,另一个是系统为运行脚本程序创建的shell。
		当一个脚本程序运行完毕,它的脚本shell将终止,可以返回到执行该脚本之前的shell。
意义解读
	从这种意义上来说,用户可以有许多 shell,每个shell都是由某个shell(称为父shell)派生的。
在子shell中定义的变量只在该子shell内有效。如果在一个shell脚本程序中定义了一个变量,当该脚本程序运行时,这个定义的变量只是该脚本程序内的一个局部变量,其他的shell不能引用它,要使某个变量的值可以在其他shell中被改变,可以使用export命令对已定义的变量进行输出。 

	export命令将使系统在创建每一个新的shell时定义这个变量的一个拷贝。这个过程称之为变量输出。

实践解读

	当前父shell中定义变量中,分为局部变量和全局变量,不同点是:
		- 局部变量只能作用于本父shell,子shell无法继续使用
		- 如果使用了export将局部变量定义为全局变量,那么子shell创建的时候会继承父shell的全局变量

嵌套实践

简单实践

查看父shell的脚本
[root@localhost ~]# cat father.sh
#!/bin/bash
# 定制全局变量
export _xing='王'
_name="书记"
_age="42"
echo "父shell信息: $_xing$_name,$_age"
sleep 3

# 调用child.sh进行验证,最好放在同一目录下
/bin/bash child.sh
echo "父shell信息: $_xing$_name,$_age"
查看子shell的脚本
#!/bin/bash
# 显示父shell的全局变量
echo "子shell信息: $_xing$_name,$_age"

# 同名变量 子shell 的优先级高于父shell,但是不会传递给父shell
_xing="王胖胖"
echo "子shell修改后的信息: $_xing"
执行测试效果
[root@localhost ~]# /bin/bash father.sh
父shell信息: 王书记,42
子shell信息: 王,
子shell修改后的信息: 王胖胖
父shell信息: 王书记,42

2.4 内置变量

2.4.1 脚本相关

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

脚本相关的变量解析

序号 变量名 解析
1 $0 获取当前执行的shell脚本文件名
2 $n 获取当前执行的shell脚本的第n个参数值,n=1..9,
当n为0时表示脚本的文件名,如果n大于9就要用大括号括起来$
3 $# 获取当前shell命令行中参数的总个数
4 $? 获取执行上一个指令的返回值(0为成功,非0为失败)

简单实践

实践1 - $0 获取脚本的名称

[root@localhost ~]# cat get_name.sh
#!/bin/bash
# 获取脚本的名称
echo "我脚本的名称是: file.sh"
echo "我脚本的名称是:$0"

实践2 - $n 获取当前脚本传入的第n个位置的参数

[root@localhost ~]# cat get_args.sh
#!/bin/bash
# 获取指定位置的参数
echo "第一个位置的参数是: $1"
echo "第二个位置的参数是: $2"
echo "第三个位置的参数是: $3"
echo "第四个位置的参数是: $4"

实践3 - $# 获取当前脚本传入参数的数量

[root@localhost ~]# cat get_number.sh
#!/bin/bash
# 获取当前脚本传入的参数数量
echo "当前脚本传入的参数数量是: $#"

实践4 - $? 获取文件执行或者命令执行的返回状态值

[root@localhost ~]#  bash nihao
bash: nihao: No such file or directory
[root@localhost ~]#  echo $?
127

[root@localhost ~]#  ls
get_name.sh get_args.sh get_number.sh
[root@localhost ~]#  echo $?
0

2.4.2 字符串相关

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

字符串相关的变量解析

字符串计数
	${#file}			获取字符串的长度

字符串截取	
	- 语法为${var:pos:length}   表示对变量var从pos开始截取length个字符,pos为空标示0
    ${file:0:5}			从0开始,截取5个字符
    ${file:5:5}			从5开始,截取5个字符
    ${file::5}			从0开始,截取5个字符
    ${file:0-6:3}		从倒数第6个字符开始,截取之后的3个字符
    ${file: -4}			返回字符串最后四个字节,-前面是"空格"

简单实践

字符串实践

定制字符串内容
[root@localhost ~]# string_context="dsjfdsafjkldjsklfajkdsa"
[root@localhost ~]# echo $string_context
dsjfdsafjkldjsklfajkdsa
获取字符串长度
[root@localhost ~]# echo ${#string_context}
23
从0开始,截取5个字符
[root@localhost ~]# echo ${string_context:0:5}
dsjfd

从5开始,截取5个字符
[root@localhost ~]# echo ${string_context:5:5}
safjk	

从0开始,截取5个字符
[root@localhost ~]# echo ${string_context::5}
dsjfd

从倒数第6个字符开始,截取之后的3个字符
[root@localhost ~]# echo ${string_context:0-6:3}
ajk

返回字符串最后四个字节,-前面是"空格"
[root@localhost ~]# echo ${string_context: -4}
kdsa	

2.4.3 默认值相关

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

语法解读

格式一:${变量名:-默认值}	
	变量a如果有内容,那么就输出a的变量值
	变量a如果没有内容,那么就输出默认的内容

格式二:${变量名+默认值}	
	无论变量a是否有内容,都输出默认值

实践1 - 有条件的默认值

购买手机的时候选择套餐:
	如果我输入的参数为空,那么输出内容是 "您选择的套餐是: 套餐 1"
	如果我输入的参数为n,那么输出内容是 "您选择的套餐是: 套餐 n"	
[root@localhost ~]# select_default_value.sh
#!/bin/bash
# 套餐选择演示
a="$1"
echo "您选择的手机套餐是: 套餐 ${a:-1}"

实践2 - 强制默认值

国家法律强制规定:
	不管我说国家法定结婚年龄是 多少岁,都输出 国家法定结婚年龄(男性)是 22 岁	
[root@localhost ~]# froce_default_value.sh
#!/bin/bash
# 默认值演示示例二
a="$1"
echo "国家法定结婚年龄(男性)是 ${a+22} 岁"

2.4.4 其他相关

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

脚本相关的变量解析

序号 变量名 解析
1 $_ 在此之前执行的命令或脚本的第一个内容
2 $@ 传给脚本的所有参数
3 $* 是以一个单字符串显示里所有向脚本传递的参数,与位置参数不同,参数可超过9个
4 $$ 是脚本运行的当前进程的ID号,作用是方便以后管理它杀掉他
5 $! 前一条命令进程的ID号,作用是方便以后管理它杀掉他

简单实践

实践1 - 其他变量的作用

[root@localhost ~]# cat get_other.sh
#!/bin/sh
echo "脚本执行命令的第一个内容: $_"
echo "传递给当前脚本的所有参数是: $@"
echo "单字符串显示所有参数: $*"
echo "当前脚本执行时候的进程号是: $$"
sleep 5 &
echo "上一条命令执行时候的进程号是: $!"

实践2 - $$ 获取当前的进程号

查看当前的进程号
[root@localhost ~]# echo $$
4759
[root@localhost ~]# ps aux | grep 4759
root       4759  0.0  0.0 116712  3356 pts/1    Ss   00:11   0:00 -bash
root       5547  0.0  0.0 112828   984 pts/1    S+   02:00   0:00 grep --color=auto 4759
杀死当前的进程
[root@localhost ~]# kill -9 4759
───────────────────────────────────────────
Session stopped
    - Press <return> to exit tab
    - Press R to restart session
    - Press S to save terminal output to file

实践3 - $@ 和 $* 的区别

定制father脚本
[root@localhost ~]# cat father.sh
#!/bin/bash
echo "$0: 所有的参数 $@"
echo "$0: 所有的参数 $*"
echo '将 $* 值传递给 child-1.sh 文件'
/bin/bash child-1.sh "$*"

echo '将 $@ 值传递给 child-2.sh 文件'
/bin/bash child-2.sh "$@"
定制两个child脚本
[root@localhost ~]# cat child-1.sh
#!/bin/bash
echo "$0: 获取所有的参数 $1"

[root@localhost ~]# cat child-2.sh
#!/bin/bash
echo "$0: 获取所有的参数 $1"
执行 father.sh 脚本
[root@localhost ~]# /bin/bash father.sh 1 2 3
father.sh: 所有的参数 1 2 3
father.sh: 所有的参数 1 2 3
将 $* 值传递给 child-1.sh 文件
child-1.sh: 获取所有的参数 1 2 3
将 $@ 值传递给 child-2.sh 文件
child-2.sh: 获取所有的参数 1

3 内容格式化

3.1 常用符号解读

3.1.1 信息传递

这一节,我们从 重定向、管道符、小结 三个方面来学习。

重定向

重定向符号

在shell脚本中有两类常见的重定向符号:
覆盖式重定向:
	> 表示将符号左侧的内容,以覆盖的方式输入到右侧文件中
	< 表示将符号右侧的内容,以覆盖的方式输入到左侧文件中
追加式重定向:
	>> 表示将符号左侧的内容,以追加的方式输入到右侧文件的末尾行中
	<< 表示将符号右侧的内容,以追加的方式输入到左侧文件的末尾行中

实践1 - 覆盖式重定向

查看文件内容
[root@localhost ~]# cat file.txt
nihao

使用重定向符号给文件中增加内容
[root@localhost ~]# echo "file1.txt" > file.txt

再次查看文件内容
[root@localhost ~]# cat file.txt
file1.txt

实践2 - 追加式重定向

查看文件内容
[root@localhost ~]# cat file.txt 
file1.txt

使用重定向符号给文件中增加内容
[root@localhost ~]# echo "file2.txt" >> file.txt 

再次查看文件内容
[root@localhost ~]# cat file.txt 
file1.txt
file2.txt

管道符

符号简介

| 这个就是管道符,常用于将两个命令隔开,然后命令间(从左向右)传递信息使用的。

使用格式

命令1 | 命令2
	管道符左侧命令1 执行后的结果,传递给管道符右侧的命令2使用

实践1 - 信息的传递

查看当前系统中的全局变量SHELL
[root@localhost ~]# env | grep SHELL
SHELL=/bin/bash

3.1.2 终端输出

这一节,我们从 后台执行、信息符号、小结 三个方面来学习。

后台执行

简介

& 就是将一个命令从前台转到后台执行,使用格式如下:
	命令 &

简单演示

前台执行休眠命令
[root@localhost ~]# sleep 4
界面卡住4秒后消失

后台执行休眠命令
[root@localhost ~]# sleep 10 &
[1] 4198
[root@localhost ~]# ps aux | grep sleep
root       4198  0.0  0.0   9032   808 pts/17   S    21:58   0:00 sleep 10
root       4200  0.0  0.0  15964   944 pts/17   S+   21:58   0:00 grep --color=auto sleep

信息符号

信息简介

1 表示正确输出的信息
2 表示错误输出的信息
2>&1 代表所有输出的信息,也可以简写为 "&>"

示例1 - 符号演示

标准正确输出重定向到zhengque文件
[root@localhost ~]# cat nihao.txt 1>> zhengque 

标准错误输出重定向到errfile文件
[root@localhost ~]# dsfadsfadsfa 2>> errfile

实例2 - 综合演练

脚本文件内容
[root@localhost ~]# cat ceshi.sh
#!/bin/bash
# 输出正确信息
echo '下一条错误命令'
# 执行错误命令,输出错误信息
dsfsafsafdsa

脚本执行效果
[root@localhost ~]# /bin/bash ceshi.sh
下一条错误命令
ceshi.sh:行5: dsfsafsafdsa: 未找到命令
1 和 2 综合演练
[root@localhost ~]# /bin/bash ceshi.sh 1>> ceshi-ok 2>> ceshi-err
[root@localhost ~]# cat ceshi-ok
下一条错误命令
[root@localhost ~]# cat ceshi-err
ceshi.sh:行5: dsfsafsafdsa: 未找到命令
全部信息演练
[root@localhost ~]# /bin/bash ceshi.sh >> ceshi-all 2>&1
[root@localhost ~]# cat ceshi-all
下一条错误命令
ceshi.sh:行5: dsfsafsafdsa: 未找到命令

3.2 输入格式化

3.2.1 EOF原理

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

	在运维岗位中,有非常多的场景需要我们在脚本中编写应用软件的配置文件。在这些应用软件的配置文件中,经常携带大量的格式,尤其是携带空格的层级格式,如果我们一个一个的编写好标准的配置文件,但是一旦涉及到场景信息的动态化调整,固定好的配置文件就不太适合了,所以我们需要一种方法能够实现整个动态的格式化需求。

解决方法

	在shell编程中,"EOF”通常与"<<”结合使用,"<<EOF”表示后续的输入作为子命令或子shell的输入,直到遇到"EOF”,再次返回到主调用shell,可将其理解为分界符(delimiter)。
	所谓的 EOF,就是End of file的缩写,它是一种自定义的文件内容终止符。
	既然是分界符,那么形式自然不是固定的,这里可以将”EOF"可以进行自定义,但是前后的”EOF"必须成对出现且不能和shell命令冲突。其使用形式如下:

语法格式

交互式程序 << EOF
command1
command2
...
EOF
注意:
	最后的"EOF"必须单独占一行,而且必须顶格写,如果不想受到如此限制的话,使用<<-符号
		<<- 的作用是自动去除最后一个EOF前面的制表符\t【注意,对于空格无效】
	前后两个EOF可以是任意一个字符,比如aaa,只要前后两个边界内容一致即可。
	"EOF"中间的内容将以标准输入的形式输入到”交互式程序",当shell看到”<<"知道其后面输入的分界符,当shell再次看到分界符时,两个分界符中间的部分将作为标准输入。

简单实践

实践1 - EOF信息格式化输出

终端方式接收多行信息,然后交个一个命令
[root@localhost ~]# cat << EOF
> Dear Wang:
>   jian dao ni hen gaoxing.
> danshi wo bu xihuan ni.
> zaijian.
>                zhaoliu
>                2100-11-11
> EOF
Dear Wang:
  jian dao ni hen gaoxing.
danshi wo bu xihuan ni.
zaijian.
               zhaoliu
               2100-11-11

实践2 - 脚本中实践EOF

编写请假条
[root@localhost ~]# cat qingjiatiao.sh
#!/bin/bash
# EOF演示请假条示例

cat << EOF
                        请假条
王老师:
    我因患急性空腹病,现在需要到火锅理疗,不能到学校上课,
请准假一天。恳请批准!
                                             请假人:李四
                                                 6月1日
EOF

执行脚本示例
[root@localhost ~]# /bin/bash qingjiatiao.sh
                        请假条
王老师:
    我因患急性空腹病,现在需要到火锅理疗,不能到学校上课,
请准假一天。恳请批准!
                                             请假人:李四
                                                 6月1日

实践3 - 末尾EOF前面有制表符

修改脚本
[root@localhost ~]# cat qingjiatiao.sh
...
# EOF前面有一个制表符,也就是 Tab键效果
	EOF
	
执行脚本后效果
[root@localhost ~]# /bin/bash qingjiatiao.sh
qingjiatiao.sh:行11: 警告:立即文档在第 4 行被文件结束符分隔 (需要 `EOF')
...
修改脚本效果
[root@localhost ~]# cat qingjiatiao.sh
#!/bin/bash
# EOF演示请假条示例,将<< 替换为 <<-

cat <<- EOF
                        请假条
王老师:
    我因患急性空腹病,现在需要到火锅理疗,不能到学校上课,
请准假一天。恳请批准!
                                             请假人:李四
                                                 6月1日
        EOF
脚本执行后的效果
[root@localhost ~]# /bin/bash qingjiatiao.sh
                        请假条
王老师:
    我因患急性空腹病,现在需要到火锅理疗,不能到学校上课,
请准假一天。恳请批准!
                                             请假人:李四
                                                 6月1日

3.2.2 文本输入

这一节,我们从 cat实践、tee实践、小结 三个方面来学习。

cat实践

实现多行文件的输出

语法格式
    cat > file  << EOF
    ...
    EOF
注意:
	> 代表覆盖式增加内容到 file 文件
	>> 代表追加式增加内容到 file 文件

简单示例

定制主机解析名信息
[root@localhost ~]# cat >> hosts << EOF
10.0.0.13 k8s-master
10.0.0.14 k8s-node1
10.0.0.15 k8s-node2
EOF

演示效果
[root@localhost ~]# cat hosts
10.0.0.13 k8s-master
10.0.0.14 k8s-node1
10.0.0.15 k8s-node2

实践1- 定制nginx配置文件

定制nginx配置文件的脚本
[root@localhost ~]# cat define-nginx-conf.sh
#!/bin/bash
# 定制nginx的配置文件

# 定制配置文件目录
NGINX_DIR='/data/server/conf'
NGINX_CONF='nginx.conf'

# 创建基本目录
mkdir -p $NGINX_DIR

# 定制nginx配置文件
cat > $NGINX_DIR/$NGINX_CONF << EOF
server {
  listen 80;
  server_name www.example.com
  listen / {
     proxy_pass http://10.0.0.12:10086;
  }
}
EOF
执行文件后的效果
[root@localhost ~]# /bin/bash define-nginx-conf.sh
[root@localhost ~]# cat /data/server/conf/nginx.conf
server {
  listen 80;
  server_name www.example.com
  listen / {
     proxy_pass http://10.0.0.12:10086;
  }
}

tee实践

功能简介

	tee命令读取标准输入,把这些内容同时输出到标准输出和(多个)文件中,tee命令可以重定向标准输出到多个文件。要注意的是:在使用管道线时,前一个命令的标准错误输出不会被tee读取。

命令格式

样式1:只输出到标准输出
	tee
样式2:输出到标准输出的同时,保存到文件file中
	tee file
样式3:输出到标准输出的同时,追加到文件file中。如果文件不存在则创建;如果文件存在则追加。
	tee -a file
	tee host2 <<- EOF ... EOF
样式4: 输出到标准输出两次。
	tee -
样式5:输出到标准输出两次,同时保存到file1和file2中。
	tee file1 file2 -

简单演示

实践1 - 仅输出到当前屏幕
[root@localhost ~]# echo tee-test | tee
tee-test

实践2 - 同时输出到屏幕和文件
[root@localhost ~]# echo tee-test | tee tee-file
tee-test
[root@localhost ~]# cat tee-file
tee-test
实践3 - 追加到对应的文件中
[root@localhost ~]# cat tee-file
tee-test
[root@localhost ~]# echo tee-test1 | tee tee-file
tee-test1
[root@localhost ~]# cat tee-file
tee-test1
[root@localhost ~]# echo tee-test2 | tee -a tee-file
tee-test2
[root@localhost ~]# cat tee-file
tee-test1
tee-test2
实践4 - 输出多次信息
[root@localhost ~]# echo tee-test | tee
tee-test
[root@localhost ~]# echo tee-test | tee -
tee-test
tee-test
[root@localhost ~]# echo tee-test | tee - -
tee-test
tee-test
tee-test
[root@localhost ~]# echo tee-test | tee - - -
tee-test
tee-test
tee-test
tee-test
实践5 - 保存到多个文件
[root@localhost ~]# echo tee-test | tee file-1 file-2 file-3
tee-test
[root@localhost ~]# cat file-1
tee-test
[root@localhost ~]# cat file-2
tee-test
[root@localhost ~]# cat file-3
tee-test
实践6 - 接收命令行多行信息,同时在文件和当前终端显示
[root@localhost ~]# tee host2 <<- EOF
> 10.0.0.13 k8s-master
> 10.0.0.14 k8s-node1
> 10.0.0.15 k8s-node2
> EOF
10.0.0.13 k8s-master
10.0.0.14 k8s-node1
10.0.0.15 k8s-node2

查看文件内容
[root@localhost ~]# cat host2
10.0.0.13 k8s-master
10.0.0.14 k8s-node1
10.0.0.15 k8s-node2

案例实践 - kubernetes参数配置

定制kubernetes的网络模块加载
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

定制kubernetes的数据包内核参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system

4 内容格式化

4.1 输出格式化

4.1.1 echo解读

这一节,我们从 基础知识、简单实践、小结、三个方面来学习。

基础知识

命令简介

echo命令的功能是将内容输出到默认显示设备,一般起到一个提示的作用。
OPTIONS:
-n	不要在最后自动换行
-e	若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出:

转义字符
\a		发出警告声;
\b		删除前一个字符;
\c		最后不加上换行符号;
\f		换行但光标仍旧停留在原来的位置;
\n		换行且光标移至行首;
\r		光标移至行首,但不换行;
\t		插入tab;
\v		与\f相同;
\		插入\字符;
\0nnn	打印nnn(八进制)所代表的ASCII字符;  备注:数字0  不要理解成字母o
\xNN  	打印NN(十六进制)所代表的ASCII字符;

-–help	显示帮助
-–version显示版本信息

简单实践

实践1 - 信息的输出

echo后边用单引号包围要添加的内容
[root@localhost ~]# echo 'hello world' >> /tmp/hello.txt
[root@localhost ~]# cat /tmp/hello.txt
hello world

实践2 - 引号信息输出

通过引号的错开实现引号的输出
[root@localhost ~]# echo "I'm a king of the world."
I'm a king of the world.

实践3 - 特殊符号的输出

使用 -e 选项启用转义字符的解析
[root@localhost ~]# echo -e "The 2021 State of DevOps Report\n\t- is here"
The 2021 State of DevOps Report
        - is here

实践4 - 内容的拼接

使用 -n 选项启用信息输出不换行
[root@localhost ~]# echo -n hello;echo world
helloworld

4.1.2 字体颜色

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

echo本质上是将信息内容输出到当前的屏幕终端,如果只是一种颜色的话,可能导致视觉疲劳。所以,一般情况下,我们在显示信息的时候,往往会通过颜色的方式实现特定内容的颜色高亮显示。

echo命令可以修改字体类型,字体背景色以及字体颜色,转义序列\033可以用于改变字体属性。

格式解读

格式如下:
	echo -e "\033[字背景颜色;文字颜色m字符串\033[0m"
	echo -e "\033[41;36m 显示的内容 \033[0m"

颜色分类

色彩 绿
字体色 30 31 32 33 34 35 36 37
背景色 40 41 42 43 44 45 46 47

结束控制符

最后面控制选项说明
  \033[0m 关闭所有属性		  \033[1m 设置高亮度		  \033[4m 下划线
  \033[5m 闪烁				 \033[7m 反显				 \033[8m 消隐
注意:
	\033 是八进制的ESCAPE字符,我们可以用 \e 来代替

简单实践

实践1 - 字体颜色和背景颜色

字体颜色示例
echo -e "\033[30m 黑色字 \033[31m 红色字 \033[32m 绿色字 \033[33m 黄色字 \033[0m"
echo -e "\033[34m 蓝色字 \033[35m 紫色字 \033[36m 天蓝字 \033[37m 白色字 \033[0m"
 
背景颜色示例
echo -e "\033[40;37m 黑底白字 \033[41;37m 红底白字 \033[42;37m 绿底白字 \033[0m"
echo -e "\033[43;37m 黄底白字 \033[44;37m 蓝底白字 \033[45;37m 紫底白字 \033[0m"
echo -e "\033[46;37m 天蓝底白字 \033[47;30m 白底黑字 \033[0m"

实践2 - 信息颜色显示

定制堡垒机的测试页面脚本
[root@localhost ~]# cat simple_jumpserver.sh
#!/bin/bash
# 功能:定制堡垒机的展示页面
# 作者:wangshuji
# 版本:V0.1
# 联系:superopsmsb.com

echo -e "\e[31m \t\t 欢迎使用堡垒机"

echo -e "\e[32m
-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------
"'\033[0m'
echo -e "请输入您要选择的远程主机编号: "

4.1.3 printf格式化

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

	虽然我们能够通过 echo的方式实现信息某种程度的格式化输出,但是很多信息的输出偏重于手工的干预,效率太慢。我们需要一种功能更强大、效率更高的格式化手段。
	-- printf

printf简介

	printf 命令模仿 C 程序库(library)里的 printf() 程序。由于 printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。
	printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。

基本语法

查看帮助
[root@localhost ~]# help printf
printf: printf [-v var] 格式 [参数]
格式化替换符
    %s 		字符串
    %d,%i 	十进制整数
    %f 		浮点格式
    %c 		ASCII字符,即显示对应参数的第一个字符
    %b 		相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
    %o 		八进制值
    %u 		不带正负号的十进制值
    %x 		十六进制值(a-f)
    %X 		十六进制值(A-F)
    %% 		表示%本身
格式化转义符号
    \a		警告字符,通常为ASCII的BEL字符
    \b		后退
    \c		抑制(不显示)输出结果中任何结尾的换行字符
    \f		换页(formfeed)
    \n		换行
    \r		回车(Carriage return)
    \t		水平制表符
    \v		垂直制表符
    \\		一个字面上的反斜杠字符
    \ddd	表示1到3位数八进制值的字符。仅在格式字符串中有效
    \0ddd	表示1到3位的八进制值字符
格式化字符解释
–		将字段里已格式化的值向左对齐
空格	   在正值前置一个空格,在负值前置一个负号
+		总是在数值之前放置一个正号或负号,即便是正值也是
#		下列形式选择其一:
			%o有一个前置的o;					%x与%X分别前置的0x与0X;
			%e,%E与%f总是在结果中有一个小数点;  %g与%G为没有结尾的零。
0		以零填补输出,而非空白.这仅发生在字段宽度大于转换后的情况

简单实践

实践1 - 命令演示

普通echo命令演示换行信息和非换行信息
[root@localhost ~]# echo "Hello, Shell Base"
Hello, Shell Base
[root@localhost ~]# echo -n "Hello, Shell Base"
Hello, Shell Base[root@localhost ~]#

printf命令演示换行信息和非换行信息
[root@localhost ~]# printf "Hello, Shell Base\n"
Hello, Shell Base
[root@localhost ~]# printf "Hello, Shell Base"
Hello, Shell Base[root@localhost ~]#

实践2 - 替换符号演示

基本信息演示 - 单引号和双引号效果一样
[root@localhost ~]# printf "姓名:%s, 语文:%d, 数学:%d\n" "张三" 89 98
姓名:张三, 语文:89, 数学:98
[root@localhost ~]# printf '姓名:%s, 语文:%d, 数学:%d\n' "张三" 89 98
姓名:张三, 语文:89, 数学:98
注意:
	相关信息会按照顺序依次替换到格式化的替换符位置
数字格式展示
[root@localhost ~]# printf '姓名:%s, 身高:%f米, 体重:%f公斤\n' "张三" 1.7 66
姓名:张三, 身高:1.700000米, 体重:66.000000公斤
[root@localhost ~]# printf '姓名:%s, 身高:%.2f米, 体重:%.1f公斤\n' "张三" 1.7 66
姓名:张三, 身高:1.70米, 体重:66.0公斤
注意:
	通过 %.nf,n代表小数点后面的可显示的位数。

实践3 - 简单格式化

() 用于信息的批量化显示
[root@localhost ~]# printf "(%d %s)\n" 1 张三 2 李四 3 王五
(1 张三)
(2 李四)
(3 王五)

printf默认不携带最后的换行
[root@localhost ~]# printf "(%d %s)  " 1 张三 2 李四 3 王五
(1 张三)  (2 李四)  (3 王五)  [root@localhost ~]#

借助于echo的功能实现换行的效果
[root@localhost ~]# printf "(%d %s)  " 1 张三 2 李四 3 王五; echo
(1 张三)  (2 李四)  (3 王五)

实践4 - 格式化补充

%s字符串格式化
[root@localhost ~]# printf '姓名:%-10s语文:%d, 数学:%d\n' "张三" 89 98 "李四" 87 97
姓名:张三    语文:89, 数学:98
姓名:李四    语文:87, 数学:97
注意:
	%-10s,代表信息后面携带10个空格,便于格式化
%d数字格式补全
[root@localhost ~]# printf "%5d %s\n" 1 张三
    1 张三
[root@localhost ~]# printf "%05d %s\n" 1 张三
00001 张三
[root@localhost ~]# printf "%05d %s\n" 100 张四
00100 张四
注意:
	0的作用就是不用空格补全,而是用0填充,实现格式化

实践5 - 环境变量的使用

定制环境变量
[root@localhost ~]# VAR1=hello; VAR2=shell

颜色显示
[root@localhost ~]# printf "\033[32m%s \033[31m%s\033[0m\n" $VAR1 $VAR2
hello shell

4.1.4 综合案例

这一节,我们从 java部署、脚本定制、小结 三个方面来学习。

java部署

准备工作

创建目录
mkdir /data/{softs,server} -p
cd /data/softs

下载java或者上传java
ls /data/softs
安装java
tar xf jdk-8u121-linux-x64.tar.gz -C /data/server
cd /data/server/
ln -s jdk1.8.0_121 java
配置java环境变量
echo 'export JAVA_HOME=/data/server/java' >> /etc/profile.d/java.sh
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile.d/java.sh
source /etc/profile.d/java.sh

检查效果
java -version
手工清理环境
rm -f /etc/profile.d/java.sh
rm -rf /data/server/java /data/server/jdk1.8.0_121

信息显示

定制java环境部署脚本

[root@localhost ~]# cat /data/scripts/java_install.sh
#!/bin/bash
# 功能: java环境的定制
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制基本信息
JAVA_SOFT='jdk-8u121-linux-x64.tar.gz'
JAVA_DIR='jdk1.8.0_121'
JAVA_NAME='java'
SERVER_HOME='/data/server'
SOFTS_HOME='/data/softs'
RED="echo -e \E[1;31m"
END="\E[0m"

# 安装软件
tar xf $SOFTS_HOME/$JAVA_SOFT -C $SERVER_HOME
ln -s $SERVER_HOME/$JAVA_DIR $SERVER_HOME/$JAVA_NAME

# 定制环境变量
cat > /etc/profile.d/java.sh << EOF
export JAVA_HOME=$SERVER_HOME/$JAVA_NAME
export PATH=\$JAVA_HOME/bin:\$PATH
EOF

# 加载java环境变量文件
source /etc/profile.d/java.sh

# 检查效果
java -version > /tmp/txt 2>&1
java_version=$(grep version /tmp/txt  | cut -d '"' -f2)
runtime_version=$(grep Runtime /tmp/txt  | cut -d ' ' -f6 | cut -d ")" -f1)
jvm_type=$(grep VM /tmp/txt | cut -d " " -f8)

# 信息显示
$RED---------$JAVA_NAME软件运行情况---------$END
printf "\033[32m%s:\033[33m%s\033[0m\n" "java软件版本" $java_version
printf "\033[32m%s:\033[33m%s\033[0m\n" "java运行时环境版本" $runtime_version
printf "\033[32m%s:\033[33m%s\033[0m\n" "jvm运行模式" $jvm_type
$RED----------------------------------$END

5 脚本交互

5.1 基础知识

5.1.1 shell登录解读

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

shell配置文件

系统级别生效配置文件
	/etc/profile
		系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行
    /etc/profile.d/*.sh
    	被/etc/profile文件调用,执行当前目录下所有的文件中关于shell的设置
    /etc/bashrc
    	为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取。
用户级别生效配置文件
	~/.bash_profile
		设定用户专用的shell信息,当用户登录时,该文件仅仅执行一次
	~/.bashrc
		该文件包含用户专用的bash信息,当登录时以及每次打开新的shell时,该文件被读取
用户退出生效配置文件
	~/.bash_logout: 当每次退出系统(退出bash shell)时,执行该文件.
    ~/.bash_history: 
    	用户登录时自动读取其中的内容并加载到内存hiatory记录中
    	logout时将内存中的history记录写入该文件中

shell的登录方式

交互式登录
    方法1:密码登录
        直接通过终端输入账号密码登录
        复制终端
    方法2:su 变更shell操作用户的身份
        su - 用户名
        超级用户除外,需要键入该使用者的密码。
非交互式登录
	方法1:脚本执行
	方法2:su 用户名
登录shell的文件生效流程
	/etc/profile.d/*.sh
		-> /etc/profile
			-> /etc/bashrc
				-> ~/.bashrc
					-> ~/.bash_profile
非登录shell的文件生效流程
	/etc/profile.d/*.sh
		-> /etc/bashrc
			-> ~/.bashrc 
注意:
	若多配置文件中设置相同的变量,则后面配置文件中变量的值会覆盖前面配置文件中同一变量的值。
su的相关参数
	-:当前用户不仅切换为指定用户的身份,同时所用的工作环境也切换为此用户的环境。
    -l:同 - 的使用类似,完整切换工作环境,后面需要添加欲切换的使用者账号。
    -p:表示切换为指定用户的身份,但不改变当前的工作环境(不使用切换用户的配置文件)。
    -m:和 -p 一样;
    -c 命令:仅切换用户执行一次命令,执行后自动切换回来,该选项后通常会带有要执行的命令。

配置文件修改后生效的方法

修改profile和bashrc文件后需生效两种方法
    1. 重新启动shell进程
    2. source|. 配置文件

注意:
	source 会在当前shell中执行脚本,所有一般只用于执行置文件,或在脚本中调用另一个脚本的场景

简单实践

准备工作

为所有的shell相关的配置文件添加关键信息
echo "echo '1 - /etc/profile'" >> /etc/profile
echo "echo '2 - /etc/profile.d/2.sh'" >> /etc/profile.d/2.sh
echo "echo '3 - /etc/bashrc'" >> /etc/bashrc
echo "echo '4 - ~/.bash_profile'" >> ~/.bash_profile
echo "echo '5 - ~/.bashrc'" >> ~/.bashrc

非登录效果

[root@localhost ~]# su - python
上一次登录:五 6月 10 16:16:37 CST 2022pts/1 上
2 - /etc/profile.d/2.sh
1 - /etc/profile
3 - /etc/bashrc
[python@localhost ~]$ su root
密码:
2 - /etc/profile.d/2.sh
3 - /etc/bashrc
5 - ~/.bashrc
[root@localhost /home/python]# exit
exit

登录查看效果

切换标准root用户
[python@localhost ~]$ su - root
密码:
上一次登录:日 6月 12 12:41:11 CST 2022pts/2 上
2 - /etc/profile.d/2.sh
1 - /etc/profile
3 - /etc/bashrc
5 - ~/.bashrc
4 - ~/.bash_profile

新建终端效果
Last login: Sun Jun 12 12:35:59 2022 from 10.0.0.1
2 - /etc/profile.d/2.sh
1 - /etc/profile
3 - /etc/bashrc
5 - ~/.bashrc
4 - ~/.bash_profile
[root@localhost ~]#

清理环境

将刚才创建的5条命令执行反向操作
[root@localhost ~]# vim ...
	
退出当前shell环境
[root@localhost ~]# exit
登出
Session stopped
    - Press <return> to exit tab
    - Press R to restart session
    - Press S to save terminal output to file

5.1.2 子shell基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景

	之前我们对于环境变量在多个shell环境中的应用进行了学习,那种操作量比较大。对于一些临时性的场景,我们在临时性的环境中,做一些操作,但是不希望对外部的环境造成影响,这个时候我们就涉及到了一些临时shell环境的实践。
	关于临时shell环境的创建,我们可以借助于()方法来实现。

临时shell

临时shell环境 - 启动子shell
	(命令列表),在子shell中执行命令列表,退出子shell后,不影响后续环境操作。
临时shell环境 - 不启动子shell
	{命令列表}, 在当前shell中运行命令列表,会影响当前shell环境的后续操作。

简单实践

() 实践

查看当前shell的pid
[root@localhost ~]#  echo $BASHPID
11413
[root@localhost ~]# ps aux | grep bash
root      11413  0.0  0.0 116724  3160 pts/0    Ss   12:54   0:00 -bash
root      11660  0.0  0.0 112824   984 pts/0    R+   14:49   0:00 grep --color=auto bash

查看子shell的pid
[root@localhost ~]# (echo $BASHPID; echo haha)
11661
haha
[root@localhost ~]# (echo $BASHPID; sleep 30)
11711

另开一个终端查看效果
[root@localhost ~]# pstree | grep sleep
        |-sshd-+-sshd---bash---bash---sleep
结果显示:
	在一个shell内部开启了另一个shell
子shell的操作不影响当前shell环境
[root@localhost ~]# (export SUBSHELL=subshell)
[root@localhost ~]# echo $SUBSHELL

[root@localhost ~]#

子shell中,查看命令执行效果
[root@localhost ~]# (cd /tmp;pwd)
/tmp
[root@localhost ~]# pwd
/root

{} 实践

查看当前shell的进程id号
[root@localhost ~]# echo $BASHPID
11676

在{}环境中查看当前shell的进程id号
[root@localhost ~]# { echo $BASHPID; }
11676
{} 环境中,操作命令会影响当前的shell环境
[root@localhost ~]# { export SUBSHELL=subshell; }
[root@localhost ~]# echo $SUBSHELL
subshell

子shell中,查看命令执行效果
[root@localhost ~]# { cd /tmp;pwd; }
/tmp
[root@localhost /tmp]# pwd
/tmp

5.1.3 子shell实践

这一节,我们从 CA创建、脚本实践、小结 三个方面来学习。

CA创建

umask基础

umask 解读
	umask指的是文件权限默认的掩码,默认的值是022,也就是说
		默认创建的目录是777-022=755
		默认创建的文件是666-022-544
[root@localhost /data/scripts]# umask
0022
[root@localhost /data/scripts]# mkdir dir
[root@localhost /data/scripts]# touch file
[root@localhost /data/scripts]# ll
总用量 0
drwxr-xr-x 2 root root 6 6月  12 15:18 dir
-rw-r--r-- 1 root root 0 6月  12 15:18 file

CA手工实践

创建临时目录
[root@localhost ~]# mkdir /tmp/CA; cd /tmp/CA

生成私钥
[root@localhost /tmp/CA]# (umask 077;openssl genrsa -out ca.key 2048)
Generating RSA private key, 2048 bit long modulus
.............................................................+++
..................+++
e is 65537 (0x10001)
[root@localhost /tmp/CA]# ll
总用量 4
-rw------- 1 root root 1679 6月  12 15:35 ca.key

生成证书
[root@localhost /tmp/CA]# openssl req -new -x509 -key ca.key -out ca.crt -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:BJ
Locality Name (eg, city) [Default City]:BJ
Organization Name (eg, company) [Default Company Ltd]:BJ
Organizational Unit Name (eg, section) []:BJ
Common Name (eg, your name or your server's hostname) []:www.example.com
Email Address []:bj.example.com
查看生成的文件
[root@localhost /tmp/CA]# ls
ca.crt  ca.key

查看证书信息
[root@localhost /tmp/CA]# openssl x509 -in ca.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            d6:25:a6:0e:be:98:ec:48
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=CN, ST=BJ, L=BJ, O=BJ, OU=BJ, CN=www.example.com/emailAddress=bj.example.com

脚本实践

创建脚本

查看脚本定制内容
[root@localhost /data/scripts]# cat ca_create.sh
#!/bin/bash
# 功能: 创建自建CA
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通环境变量
CA_DIR="tls"
CA_DOMAIN="$1"
CA_KEY='tls.key'
CA_CRT='tls.crt'

# 创建CA证书
mkdir ${CA_DIR}
(umask 077; cd ${CA_DIR}; openssl genrsa -out tls.key 2048)
openssl req -new -x509 -key ${CA_DIR}/${CA_KEY} -out ${CA_DIR}/${CA_CRT} -subj "/CN=${CA_DOMAIN}" -days 365
执行脚本
[root@localhost /data/scripts]# /bin/bash ca_create.sh www.example.com
Generating RSA private key, 2048 bit long modulus
....+++
.......................................+++
e is 65537 (0x10001)
[root@localhost /data/scripts]# ls tls/
tls.crt  tls.key

确认效果
[root@localhost /data/scripts]# openssl x509 -in tls/tls.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            e1:8b:55:da:65:04:fc:c7
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=www.example.com

5.2 脚本外交互

5.2.1 read基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

	虽然我们可以通过脚本传参的方式实现脚本一定程度的灵活性,但是生产工作中,有很多更加灵活的场景,需要我们在脚本运行的过程中,传递一些用户定制的具体信息。这个时候,普通的脚本参数就无法满足需求了。
	read 命令可以实现我们脚本内外的信息自由传递功能。

命令简介

	read命令是用于从终端或者文件中读取输入的内建命令,read命令读取整行输入,每行末尾的换行符不被读入。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量REPLY。常用方式如下:
    read				从标准输入读取一行并赋值给特定变量REPLY。
    read answer			从标准输入读取输入并赋值给变量answer。
    read first last		从标准输入读取内容,将第一个单词放到first中,其他内容放在last中。
    read -s passwd		从标准输入读取内容,写入passwd,不输入效果
    read -n n name		从标准输入读取内容,截取n个字符,写入name,超过n个字符,直接退出
    read -p "prompt"	打印提示,等待输入,并将输入存储在REPLY中。
    read -r line		允许输入包含反斜杠。
    read -t second		指定超时时间,默认是秒,整数
    read -d sper		指定输入信息的截止符号

简单实践

命令操作

交互式接收用户信息
[root@localhost ~]# read
nihao-answer

接收用户输入给一个临时变量
[root@localhost ~]# read answer
nihao-answer
[root@localhost ~]# echo $answer
nihao-answer

接收多个信息,按照顺序交给不同的临时变量
[root@localhost ~]# read first last
first-1 last-2 end-3
[root@localhost ~]# echo $first
first-1
[root@localhost ~]# echo $last
last-2 end-3

实践2-静默显示

显式接收用户输入信息
[root@localhost ~]# read password
123456
[root@localhost ~]# echo $password
123456

隐式接收用户输入信息
[root@localhost ~]# read -s password
[root@localhost ~]# echo $password
abcdefg

实践3-提示用户输入信息

通过 -p 参数提示用户输入的信心
[root@localhost ~]# read -p "请输入登录用户名: " user
请输入登录用户名: root
[root@localhost ~]# echo $user
root

实践4-限制用户输入信息

[root@localhost ~]# read -n 6 -p "sss: " aaa
sss: 123456[root@localhost ~]# read -n 6 -p "只接收6个字符,超过自动退出: " string
只接收6个字符,超过自动退出: 123456[root@localhost ~]#
[root@localhost ~]# echo $string
123456
注意:
	-p + -s 的组合会导致不会自动换行,可以结合 echo的方式实现换行

实践5-等待时长

[root@localhost ~]# read -t 5 -p "等待5秒后自动退出! " second
等待5秒后自动退出! 4
[root@localhost ~]# echo $second
4
[root@localhost ~]# read -t 5 -p "等待5秒后自动退出! " second
等待5秒后自动退出! [root@localhost ~]#

5.2.2 案例实践

这一节,我们从 登录模拟、堡垒机实践、小结 三个方面来学习。

登录模拟

需求

模拟shell终端工具的登录,功能过程如下:
	请输入用户名: 
	请输入密码:
	您输入的用户名和密码是: xxx

脚本实践

[root@localhost ~]# cat simple_login.sh
#!/bin/bash
# 功能: 模拟shell登录
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制命令变量
OS_INFO=$(cat /etc/redhat-release)
KERNEL_INFO=$(uname -r)
OS_ARCH=$(uname -m)
HOSTNAME=$(hostname)

# 清屏
clear

# 输出提示信息
echo -e "\e[32m${OS_INFO} \e[0m"
echo -e "\e[32mKernel ${KERNEL_INFO} on an ${OS_ARCH} \e[0m"
echo "---------------------------------"
# 交互输入登陆信息
read -p "请输入用户名:" account
read -s -t30 -p "请输入登录密码:" password
echo
echo "---------------------------------"
# 输出用户输入信息
printf "您输入的用户名:\e[31m%s\e[0m您输入的密码:\e[31m%s\e[0m\n" ${account} ${password}
脚本执行效果
[root@10 ~]# /bin/bash simple_login.sh
CentOS Linux release 7.9.2009 (Core)
Kernel 3.10.0-1160.el7.x86_64 on an x86_64
---------------------------------
请输入用户名:root
请输入登录密码:
---------------------------------
您输入的用户名:root您输入的密码:123456

堡垒机实践

功能需求

模拟堡垒机的登录,功能过程如下:
	请选择要登录的主机
	请输入用户名: 
	使用指定的用户连接远程主机

脚本实践

[root@localhost ~]# cat simple_jumpserver.sh
#!/bin/bash
# 功能:定制堡垒机的展示页面
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 堡垒机的信息提示
echo -e "\e[31m \t\t 欢迎使用堡垒机"

echo -e "\e[32m
-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------
"'\033[0m'

# 由于暂时没有学习条件判断,所以暂时选择 q
read -p "请输入您要选择的远程主机编号: " host_index
read -p "请输入登录本地主机的用户名: " user

# 远程连接主机
ssh $user@localhost
脚本执行效果
[root@10 ~]# /bin/bash simple_jumpserver.sh
                 欢迎使用堡垒机

-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------

请输入您要选择的远程主机编号: p
请输入登录本地主机的用户名: root
The authenticity of host 'localhost (::1)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
root@localhost's password:
Last login: Mon Jun 13 12:19:34 2022 from 10.0.0.1
[root@10 ~]# exit
登出

6 表达式

6.1 运算符

6.1.1 运算符基础

这一节,我们从 基础知识、赋值运算、小结 三个方面来学习。

基础知识

需求

	根据我们之前的学习,通过现有的知识内容可以完成一个简单的功能操作,即使通过所谓的脚本参数可以实现一个脚本在多个数据值的情况下实现不同的结果。但是问题是,目前脚本本身还没有实现灵活的机制。
	所谓脚本级别的灵活机制,说的是脚本内部能够实现数据的操作和判断。而数据的操作也是判断过程中必须的一个条件组成部分。所以数据的操作是脚本的一个核心内容。

数据操作

	关于shell可以实施的数据操作,按照不同的业务场景主要可以分为如下两个方面:
	运算符 - 数据值之间的操作
		赋值运算
			- 结果值输出
			- 示例: =、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|=等
		二元运算 
			- 数据值操作
			- 示例: +、-、*、/、%等
		高阶运算
			- 更高一级的数学运算
			- 示例:**、^、++、--、
		其他运算 
			- 其他运算操作
			- 示例:<<、>>等
注意:
	这些所谓的运算符一般很难单独来使用,都需要结合计算表达式来实现相应的效果
	表达式 - 数据值在特定场景的运算符操作
		计算表达式
			- 将多个值的运算操作执行起来
			- 示例:bc、let、expr、$(())等
		测试表达式 
			- 判断结果值是否满足需求
			- 示例:test、[]等
		逻辑表达式 
			- 多条件的多场景组合
			- 示例:&&、||、and、or、not、&、|等
		比较表达式 
			- 判断数据之间的适配关系
			- 示例:-f|d|s、-r|x|w、-e、-n、==、!=、>、<、<=、>=等
		三元表达式
			- 多逻辑的简单计算表达式
			- 示例:expr?expr:expr
		集合表达式 
			- 表达式之间的关联关系
			- 示例:expr1 , expr2、 [[ ]]、[ -a|o ]、[ ! ]等

赋值运算

基础知识

	所谓的赋值运算,其实指的就是将一个值赋予一个变量名,虽然我们在变量一节中对于该知识点进行了一些基础性的操作,但是赋值运算仍然还有一些其他的表现样式,尤其是关于命令计算相关的。

简单的赋值操作

为变量赋值
[root@localhost ~]# echo $num $string

[root@localhost ~]# num=123 string=abc
[root@localhost ~]# echo $num $string
123 abc
赋值时候定制属性
[root@localhost ~]# declare stringnum=123
[root@localhost ~]# declare string=nihao
[root@localhost ~]# echo $stringnum $string
123 nihao
[root@localhost ~]# declare -i num=654
[root@localhost ~]# declare -i num2=aaa
[root@localhost ~]# echo $num $num2
654 0
获取特定数据
[root@localhost ~]# myuser=$(whoami)
[root@localhost ~]# echo $myuser
root
[root@localhost ~]# kernel_info=$(cat /etc/redhat-release)
[root@localhost ~]# echo $kernel_info
CentOS Linux release 7.9.2009 (Core)

6.1.2 简单计算

这一节,我们从 $[]、let、(())、$(())、小结 五个方面来学习。

$[]

简介

	$[]方法,常用于整数计算场景,适合不太复杂的计算,运算结果是小数的也会自动取整。
	后面的几种也是一样

格式

方法1:
	$[计算表达式]
方法2:
	a=$[变量名a+1]

注意:
	这里的表达式可以不是一个整体

简单示例

简单运算
[root@localhost ~]# echo $[100/5]
20
[root@localhost ~]# echo $[ 2 + 5 ]
7

变量参与运算
[root@localhost ~]# a=6
[root@localhost ~]# a=$[a+1]
[root@localhost ~]# echo $a
7

运算结果取整
[root@localhost ~]# echo $[100/3]
33

let

简介

	let是另外一种相对来说比较简单的数学运算符号了

格式

let	变量名a=变量名a+1

注意:
	表达式必须是一个整体,中间不能出现空格等特殊字符

简单示例

简单运算
[root@localhost ~]# i=1
[root@localhost ~]# let i=i+7
[root@localhost ~]# echo $i
8

let表达式必须是一个整体
[root@localhost ~]# let i = i * 2
bash: let: =: 语法错误: 期待操作数 (错误符号是 "=")
[root@localhost ~]# let i=i * 2
bash: let: anaconda-ks.cfg: 语法错误: 无效的算术运算符 (错误符号是 ".cfg")
[root@localhost ~]# let i=i*2
[root@localhost ~]# echo $i
16

(())

简介

(())的操作与let基本一致,相当于let替换成了 (())

格式

((变量计算表达式))
注意:
	对于 $(())中间的表达式,可以不是一个整体,不受空格的限制

简单实践

[root@localhost ~]# num1=34
[root@localhost ~]# ((num2=num1+34))
[root@localhost ~]# echo $num2
68

$(())

简介

$(())的操作,相当于 (()) + echo $变量名 的组合

格式

echo $((变量计算表达式))
注意:
	对于 $(())中间的表达式,可以不是一个整体,不受空格的限制

简单实践

[root@localhost ~]# num1=34
[root@localhost ~]# echo $((num2=num1+34))
68

6.1.3 赋值运算进阶

这一节,我们从 二元运算、赋值运算、小结、三个方面来学习。

二元运算

简介

	所谓的二元运算,指的是 多个数字进行+-*/%等运算

简单实践

加法运算
[root@localhost ~]# echo $(( 4 + 4 ))
8
[root@localhost ~]# num1=3 num2=4
[root@localhost ~]# echo $(($num1 + $num2))
7

减法运算
[root@localhost ~]# echo $((8-2))
6
[root@localhost ~]# echo $(($num2-$num1))
1

乘法运算
[root@localhost ~]# echo $((8*2))
16
[root@localhost ~]# echo $(($num2*$num1))
12

除法运算
[root@localhost ~]# echo $((8/2))
4
[root@localhost ~]# echo $(($num2/$num1))
1

取余运算
[root@localhost ~]# echo $((8%3))
2
[root@localhost ~]# echo $(($num2%$num1))
1

案例实践-小学计算题

案例需求:鸡兔同笼,共有30个头,88只脚。求笼中鸡兔各有多少只?
查看脚本内容
[root@localhost ~]# cat count_head_feet.sh
#!/bin/bash
# 功能:小学计算题目
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
head="$1"
feet="$2"

# 计算逻辑
# 每个头至少两只脚,多出的脚都是兔子的
rabbit_num=$(( ($feet - $head - $head) / 2))
chick_num=$[ $head - $rabbit_num ]

# 结果输出
echo -e "\e[31m\t鸡和兔的数量\e[0m"
echo -e "\e[32m============================"
echo "鸡的数量: ${chick_num}"
echo "兔的数量:${rabbit_num}"
echo -e "============================\e[0m"
脚本执行效果
[root@localhost ~]# /bin/bash count_head_feet.sh 30 88
        鸡和兔的数量
============================
鸡的数量: 16
兔的数量:14
============================

赋值运算

简介

	这里的赋值运算,是一种进阶版本的复制操作,常见的样式如下:
		样式1:+=、-=、*=、/=
			- 在自身的基础上进行二元运算,结果值还是自己
		样式2:++、--
			- 在自身的基础上进行递增和递减操作,结果值还是自己

简单实践

+=运算
[root@localhost ~]# num1=6
[root@localhost ~]# let value+=$num1    # 相当于 let value=value+$num1
[root@localhost ~]# echo $value
6

-=运算
[root@localhost ~]# let value-=2    # 相当于 let value=value-2
[root@localhost ~]# echo $value
4

*=运算
[root@localhost ~]# let value*=2    # 相当于 let value=value*2
[root@localhost ~]# echo $value
8

/=运算
[root@localhost ~]# let value/=2    # 相当于 let value=value/2
[root@localhost ~]# echo $value
4
i++运算
[root@localhost ~]# value=9
[root@localhost ~]# let value++		# 相当于 let value=value+1
[root@localhost ~]# echo $value
10
[root@localhost ~]# let value++		# 相当于 let value=value+1
[root@localhost ~]# echo $value
11

++i运算
[root@localhost ~]# let ++value		# 相当于 let value=value+1
[root@localhost ~]# echo $value
12
--运算
[root@localhost ~]# value=9
[root@localhost ~]# let value--		# 相当于 let value=value-1
[root@localhost ~]# echo $value
8
[root@localhost ~]# let value--		# 相当于 let value=value-1
[root@localhost ~]# echo $value
7
[root@localhost ~]# let --value		# 相当于 let value=value-1
[root@localhost ~]# echo $value
6

6.1.4 expr计算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	expr即可以做常见的整数运算,还可以做数字比较,字符串计算等操作。

格式

数字场景:
	expr 运算表达式
字符串场景:
	match:用户获取匹配到字符串的长度
		expr match 字符串 匹配内容
	substr:截取字符串
		expr substr 字符串 起始位置 截取长度
		注意:起始位置值>=1
	index:查找第一次匹配字符的位置
		expr index 字符串 字符
	length:计算字符串的长度
		expr length 字符串 

简单实践

数学运算

数学运算
[root@localhost ~]# expr 1 + 2 - 3 \* 4 / 5 + \( 6 - 7 \) \* 8
-7
[root@localhost ~]# x=1
[root@localhost ~]# expr $x + 4
5

字符串运算

用户获取匹配到字符串的长度
[root@localhost ~]# file=jdslkfajkldsjafklds
[root@localhost ~]# expr match $file "k.*j"
0
[root@localhost ~]# expr match $file ".*j"
13

截取字符串
[root@localhost ~]# expr substr $file 0 4
[root@localhost ~]# expr substr $file 1 4
jdsl

查找第一次匹配字符的位置
[root@localhost ~]# expr index $file b
0
[root@localhost ~]# expr index $file j
1

计算字符串的长度
[root@localhost ~]# expr length $file
19

6.1.5 bc计算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	bc是一种任意精度的计算语言,提供了语法结构,比如条件判断、循环等,功能是很强大的,还能进行进制转换。
常见参数
    -i 强制交互模式;
    -l 使用bc的内置库,bc里有一些数学库,对三角计算等非常实用;
    -q 进入bc交互模式时不再输出版本等多余的信息。
特殊变量
    ibase,obase 用于进制转换,ibase是输入的进制,obase是输出的进制,默认是十进制;
    scale 小数保留位数,默认保留0位。

简单实践

实践1-交互示例

在shell命令行直接输入bc即进入bc语言的交互模式
[root@localhost ~]# bc -l -q
4 / 3
1.33333333333333333333
3 + 4
7
5 * 8
40
exit
0
^C
(interrupt) Exiting bc.
[root@localhost ~]#

实践2 - 非交互示例

格式:echo "属性设置; 计算表达式" | bc
[root@localhost ~]# echo "scale=2; 9-8*2/5^2" | bc
8.36
[root@localhost ~]# echo "scale=2; sqrt(10)" | bc
3.16
[root@localhost ~]# echo '45.36-22.33' | bc
23.03

实践3 - 内存使用率统计

查看脚本效果
[root@localhost ~]# cat memory_info.sh
#!/bin/bash
# 功能:定制内存信息的使用率
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制基础信息
temp_file='/tmp/free.txt'
hostname=$(hostname)

# 获取内存信息
free -m > /tmp/free.txt
# 获得内存总量
memory_totle=$(grep -i "mem" /tmp/free.txt | tr -s " " | cut -d " " -f2)
# 获得内存使用的量
memory_use=$(grep -i "mem" /tmp/free.txt | tr -s " " | cut -d " " -f3)
# 获得内存空闲的量
memory_free=$(grep -i "mem" /tmp/free.txt | tr -s " " | cut -d " " -f4)

# 定制使用比例
# 获取内存使用率
percentage_use=$(echo "scale=2; $memory_use * 100 / $memory_totle" | bc)
# 定制内存空闲率
percentage_free=$(echo "scale=2; $memory_free * 100 / $memory_totle" | bc)

# 内容信息输出
echo -e "\e[31m\t${hostname} 内存使用信息统计\e[0m"
echo -e "\e[32m=========================================="
echo '内存总量:     ' ${memory_totle}
echo '内存使用量:   ' ${memory_use}
echo '内存空闲量:   ' ${memory_free}
echo '内存使用比率: ' ${percentage_use}
echo '内存空闲利率: ' ${percentage_free}
echo "=========================================="
echo -e "\e[0m"
脚本执行后效果
[root@localhost ~]# /bin/bash memory_info.sh
        localhost 内存使用信息统计
==========================================
内存总量:      3770
内存使用量:    238
内存空闲量:    3376
内存使用比率:  6.31
内存空闲利率:  89.54
==========================================

6.2 表达式

6.2.1 基础知识

这一节,我们从 基础知识、测试表达式、小结 三个方面来学习。

基础知识

简介

	所谓的表达式,就是在场景需求的前提下,判断数据和运算符的操作是否满足需求。		

语法格式:

格式
	真实值 操作符 真实值 比较运算符 预期值
示例
	3 + 4 > 6
要点:
	表达式应该具有判断的功能

测试表达式

简介

	Shell环境根据命令执行后的返回状态值($?)来判断是否执行成功,当返回值为0,表示成功,值为其他时,表示失败。使用专门的测试工具---test命令,可以对特定条件进行测试,并根据返回值来判断条件是否成立(返回值0为成立)

测试表达式

样式1: test 条件表达式
样式2: [ 条件表达式 ]
注意:
    以上两种方法的作用完全一样,后者为常用。
    但后者需要注意方括号[、]与条件表达式之间至少有一个空格。
    test跟 [] 的意思一样
        条件成立,状态返回值是0
        条件不成立,状态返回值是1

简单示例

test语法示例
[root@localhost ~]# test 1 == 1
[root@localhost ~]# echo $?
0
[root@localhost ~]# test 1 == 2
[root@localhost ~]# echo $?
1

test -v语法测试变量有没有被设置值,亦可理解为变量是否为空。
[root@localhost ~]# echo $empty

[root@localhost ~]# test -v empty
[root@localhost ~]# echo $?
1
[root@localhost ~]# empty=value
[root@localhost ~]# echo $?
0
[] 语法示例
[root@localhost ~]# [ 1 == 1 ]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ 1 == 12 ]
[root@localhost ~]# echo $?
1
[] 格式解读
[root@localhost ~]# [ 1 == 12]
bash: [: 缺少 `]'
[root@localhost ~]# [ 1 == 12] ]
[root@localhost ~]# echo $?
1

6.2.2 逻辑表达式

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	逻辑表达式一般用于判断多个条件之间的依赖关系。
	常见的逻辑表达式有:&&  和  ||,根据观察的角度不同含义也不同

语法解读

&&		
	示例:命令1  &&  命令2
		如果命令1执行成功,那么我才执行命令2		-- 夫唱妇随
		如果命令1执行失败,那么命令2也不执行
||	
	示例:命令1 || 命令2
		如果命令1执行成功,那么命令2不执行			-- 对着干
		如果命令1执行失败,那么命令2执行
!
	示例:! 命令
		如果命令执行成功,则整体取反状态

组合使用

使用样式:
	命令1 && 命令2 || 命令3 
    	方便理解的样式  ( 命令1 && 命令2 ) || 命令3
功能解读:
	命令1执行成功的情况下,执行命令2
	命令2执行失败的情况下,执行命令3
注意:
	&& 必须放到前面,|| 放到后面

简单实践

实践1-语法实践

&& 语法实践
[root@localhost ~]# [ 1 = 1 ] && echo "条件成立"
条件成立
[root@localhost ~]# [ 1 = 2 ] && echo "条件成立"

|| 语法实践
[root@localhost ~]# [ 1 = 2 ] || echo "条件不成立"
条件不成立
[root@localhost ~]# [ 1 = 1 ] || echo "条件不成立"
[root@localhost ~]#

实践2-案例实践

执行文件前保证具备执行权限
[root@localhost ~]# cat test_argnum.sh
#!/bin/bash
# && 和 || 演示

# 设定普通变量
arg_num=$#

[ $# == 1 ] && echo "脚本参数为1,允许执行脚本"
[ $# == 1 ] || echo "脚本参数不为1,不允许执行脚本"
脚本执行后效果
[root@localhost ~]# /bin/bash test_argnum.sh
脚本参数不为1,不允许执行脚本
[root@localhost ~]# /bin/bash test_argnum.sh 1
脚本参数为1,允许执行脚本
[root@localhost ~]# /bin/bash test_argnum.sh 1 2
脚本参数不为1,不允许执行脚本

实践3-取反

查看正常的字符串判断
[root@localhost ~]# [ aaa == aaa ]
[root@localhost ~]# echo $?
0

查看取反的效果判断
[root@localhost ~]# [ ! aaa == aaa ]
[root@localhost ~]# echo $?
1
[root@localhost ~]# [ ! aaa == bbb ]
[root@localhost ~]# echo $?
0

实践4 - 组合使用

[root@localhost ~]# [ -d /etc ] && echo "目录存在" || echo "目录不存在"
目录存在
[root@localhost ~]# [ -d /etc1 ] && echo "目录存在" || echo "目录不存在"
目录不存在

实践5 - 主机网络连通性测试

查看脚本内容
[root@localhost ~]# cat host_network_test.sh
#!/bin/bash
# 功能:测试主机网络连通性
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
host_addr="$1"

# 脚本基本判断
[ -z ${host_addr} ] && echo "请输入待测试主机ip" && exit
[ $# -ne 1 ] && echo "请保证输入1个脚本参数" && exit

# 测试主机网络
net_status=$(ping -c1 -w1 ${host_addr} >/dev/null 2>&1 && echo "正常" || echo "异常")

# 信息输出
echo -e "\e[31m\t主机网络状态信息\e[0m"
echo -e "\e[32m================================"
echo "${host_addr} 网络状态: ${net_status}"
echo -e "================================\e[0m"
脚本执行效果
[root@localhost ~]# /bin/bash host_network_test.sh
请输入待测试主机ip
[root@localhost ~]# /bin/bash host_network_test.sh aa bb
请保证输入1个脚本参数
[root@localhost ~]# /bin/bash host_network_test.sh 10.0.0.12
        主机网络状态信息
================================
10.0.0.12 网络状态: 正常
================================
[root@localhost ~]# /bin/bash host_network_test.sh 10.0.0.15
        主机网络状态信息
================================
10.0.0.15 网络状态: 异常
================================

6.2.3 字符串表达式

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	所谓的字符串表达式,主要是判断 比较运算符 两侧的值的内容是否一致,由于bash属于弱类型语言,所以,默认情况下,无论数字和字符,都会可以被当成字符串进行判断。

符号解读

内容比较判断
    str1 == str2			str1和str2字符串内容一致
    str1 != str2			str1和str2字符串内容不一致,!表示相反的意思

内容空值判断
    -z 	str					空值判断,获取字符串长度,长度为0,返回True
    -n  "str"				非空值判断,获取字符串长度,长度不为0,返回True
    						注意:str外侧必须携带"",否则无法判断

简单实践

实践1-内容比较判断

判断字符串内容是否一致
[root@localhost ~]# test aaa == bbb
[root@localhost ~]# echo $?
1
[root@localhost ~]# test aaa != bbb
[root@localhost ~]# echo $?
0

判断数字内容是否一致
[root@localhost ~]# num1=234 num2=456
[root@localhost ~]# test $num1 == $num2
[root@localhost ~]# echo $?
1
[root@localhost ~]# test $num1 != $num2
[root@localhost ~]# echo $?
0

实践2-空值判断

判断内容是否为空
[root@localhost ~]# string=nihao
[root@localhost ~]# test -z $string
[root@localhost ~]# echo $?
1
[root@localhost ~]# test -z $string1
[root@localhost ~]# echo $?
0

判断内容是否为不空,可以理解为变量是否被设置
[root@localhost ~]# unset str
[root@localhost ~]# [ -n $str ]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ -n "$str" ]
[root@localhost ~]# echo $?
1
[root@localhost ~]# str=value
[root@localhost ~]# [ -n "$str" ]
[root@localhost ~]# echo $?
0

实践3-脚本实践

查看脚本内容
[root@localhost ~]# cat simple_login_string.sh
#!/bin/bash
# 功能: 模拟shell登录
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制命令变量
OS_INFO=$(cat /etc/redhat-release)
KERNEL_INFO=$(uname -r)
OS_ARCH=$(uname -m)
HOSTNAME=$(hostname)

# 清屏
clear

# 输出提示信息
echo -e "\e[32m${OS_INFO} \e[0m"
echo -e "\e[32mKernel ${KERNEL_INFO} on an ${OS_ARCH} \e[0m"
echo "---------------------------------"
# 交互输入登陆信息
read -p "请输入用户名:" account
[ -z $account ] && read -p "请输入用户名:" account
read -s -t30 -p "请输入登录密码:" password
echo
echo "---------------------------------"
# 输出用户输入信息
[ $account == 'root' ] && [ $password == '123456' ] && echo "登录成功" || echo "登录失败"
脚本执行测试
[root@localhost ~]# /bin/bash simple_login_string.sh
CentOS Linux release 7.9.2009 (Core)
Kernel 3.10.0-1160.el7.x86_64 on an x86_64
---------------------------------
请输入用户名:root
请输入登录密码:
---------------------------------
登录成功
[root@localhost ~]# /bin/bash simple_login_string.sh
CentOS Linux release 7.9.2009 (Core)
Kernel 3.10.0-1160.el7.x86_64 on an x86_64
---------------------------------
请输入用户名:
请输入用户名:root1
请输入登录密码:
---------------------------------
登录失败
[root@localhost ~]#

6.2.4 文件表达式

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	所谓的文件表达式,主要是判断文件相关的权限和属性信息的。

表达式解读

文件属性判断
	-d  检查文件是否存在且为目录文件
    -f  检查文件是否存在且为普通文件
    -S	检查文件是否存在且为socket文件
    -L	检查文件是否存在且为链接文件
    -O  检查文件是否存在并且被当前用户拥有
    -G  检查文件是否存在并且默认组为当前用户组
    
文件权限判断   
    -r  检查文件是否存在且可读
    -w  检查文件是否存在且可写
    -x  检查文件是否存在且可执行
    
文件存在判断
    -e  检查文件是否存在
    -s  检查文件是否存在且不为空
    
文件新旧判断
    file1 -nt file2  检查file1是否比file2新
    file1 -ot file2  检查file1是否比file2旧
    file1 -ef file2  检查file1是否与file2是同一个文件,判定依据的是i节点

简单实践

实践1- 文件属性判断

[root@localhost ~]# [ -f weizhi.sh ] && echo "是一个文件"
[root@localhost ~]# [ -f weizhi.sddh ] || echo "不是一个文件"
不是一个文件
[root@localhost ~]# [ -d weizhi.sddh ] || echo "不是一个目录"
不是一个目录
[root@localhost ~]# [ -d /tmp ] && echo "是一个目录"
是一个目录

实践2-文件权限判断

[root@localhost ~]# [ -x memory_info.sh ] || echo "文件没有执行权限"
文件没有执行权限
[root@localhost ~]# [ -x memory_info.sh ] || chmod +x memory_info.sh
[root@localhost ~]# [ -x memory_info.sh ] && ./memory_info.sh
        localhost 内存使用信息统计
==========================================
内存总量:      3770
内存使用量:    242
内存空闲量:    3372
内存使用比率:  6.41
内存空闲利率:  89.44
==========================================

实践3-文件存在判断

文件内容空值判断
[root@localhost ~]# touch nihao.txt
[root@localhost ~]# [ -s nihao.txt ] || echo "文件为空"
文件为空
[root@localhost ~]# echo nihao > file.txt
[root@localhost ~]# [ -s file.txt ] && echo "文件不为空"
文件不为空
文件存在与否判断
[root@localhost ~]# [ -e file.txt ] && echo "文件存在"
文件存在
[root@localhost ~]# [ -e file.txt1 ] || echo "文件不存在"
文件不存在

6.2.5 数字表达式

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

主要根据给定的两个值,判断第一个与第二个数的关系,如是否大于、小于、等于第二个数。

语法解读

    n1 -eq n2   相等		n1 -ne n2   不等于		n1 -ge n2   大于等于	
    n1 -gt n2   大于		n1 -lt n2   小于		n1 -le n2   小于等于

简单实践

实践1-命令实践

[root@localhost ~]# [ 3 -gt 2 ] && echo "3 大于 2"
3 大于 2
[root@localhost ~]# [ 3 -ne 2 ] && echo "3 不等于 2"
3 不等于 2
[root@localhost ~]# [ 3 -eq 3 ] && echo "3 等于 3"
3 等于 3

实践2-脚本安全

查看脚本内容
[root@localhost ~]# cat test_argnum.sh
#!/bin/bash
# -eq 和 -ne 演示

# 设定普通变量
arg_num=$#

[ $arg_num -eq 1 ] && echo "脚本参数为1,允许执行脚本"
[ $arg_num -ne 1 ] && echo "脚本参数不为1,不允许执行脚本"
脚本执行效果
root@localhost ~]# /bin/bash test_argnum.sh
脚本参数不为1,不允许执行脚本
[root@localhost ~]# /bin/bash test_argnum.sh 1
脚本参数为1,允许执行脚本
[root@localhost ~]# /bin/bash test_argnum.sh 1 2
脚本参数不为1,不允许执行脚本

6.3 表达式进阶

6.3.3 [[]] 测试进阶

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	我们之前学习过 test 和 [ ] 测试表达式,这些简单的测试表达式,仅仅支持单条件的测试。如果需要针对多条件测试场景的话,我们就需要学习[[  ]] 测试表达式了。
	我们可以将 [[  ]] 理解为增强版的 [ ],它不仅仅支持多表达式,还支持扩展正则表达式和通配符。

语法解析

基本格式:
	[[ 源内容 操作符 匹配内容 ]]
	
操作符解析:
	== 左侧源内容可以被右侧表达式精确匹配
	=~ 左侧源内容可以被右侧表达式模糊匹配

简单实践

实践1-内容的基本匹配

定制默认的的变量
[root@localhost ~]# string=value
[root@localhost ~]# echo $string
value
[root@localhost ~]# [[ $string == value ]]
[root@localhost ~]# echo $?
0

[[]] 支持通配符
[root@localhost ~]# [[ $string == v* ]]
[root@localhost ~]# echo $?
0

使用""取消正则,则内容匹配失败
[root@localhost ~]# [[ $string == v"*" ]]
[root@localhost ~]# echo $?
1

使用\取消正则,则内容匹配失败
[root@localhost ~]# [[ $string == v\* ]]
[root@localhost ~]# echo $?
1

实践2-文件的匹配

定制文件名称
[root@localhost ~]# script_name=file.sh
[root@localhost ~]# [[ $script_name == *.sh ]]
[root@localhost ~]# echo $?
0

尝试其他匹配
[root@localhost ~]# [[ $script_name == *.txt ]]
[root@localhost ~]# echo $?
1
[root@localhost ~]# [[ $script_name != *.txt ]]
[root@localhost ~]# echo $?
0

6.3.2 集合基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	所谓的集合,主要是针对多个条件表达式组合后的结果,尤其是针对于逻辑场景的组合。初中数学的相关逻辑示意图:

表现样式

两个条件
	1 - 真 		0 - 假
三种情况:
	与 - &		或 - |		非 - !
	
注意:
	这里的 0 和 1 ,千万不要与条件表达式的状态值混淆
与关系:
	0 与 0 = 0 		0 & 0 = 0
	0 与 1 = 0 		0 & 1 = 0
	1 与 0 = 0 		1 & 0 = 0
	1 与 1 = 1		1 & 1 = 1
或关系:
    0 或 0 = 0		0 | 0 = 0
    0 或 1 = 1		0 | 1 = 1
    1 或 0 = 1		1 | 0 = 1
    1 或 1 = 1		1 | 1 = 1
非关系:
    非 1 = 0   		! true = false
    非 0 = 1 		! false = true

简单实践

实践1- 简单判断

或实践
[root@localhost ~]# echo $[ 0 | 1 ]
1
[root@localhost ~]# echo $[ 0 | 0 ]
0
[root@localhost ~]# echo $[ 1 | 0 ]
1
[root@localhost ~]# echo $[ 1 | 1 ]
1
与实践
[root@localhost ~]# echo $[ 1 & 1 ]
1
[root@localhost ~]# echo $[ 1 & 0 ]
0
[root@localhost ~]# echo $[ 0 & 1 ]
0
[root@localhost ~]# echo $[ 0 & 0 ]
0
非实践
[root@localhost ~]# true
[root@localhost ~]# echo $?
0
[root@localhost ~]# false
[root@localhost ~]# echo $?
1

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

6.3.3 逻辑组合

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

所谓的条件组合,指的是在同一个场景下的多个条件的综合判断效果。

语法解析

方法1:
	[ 条件1 -a 条件2 ]		- 两个条件都为真,整体为真,否则为假
	[ 条件1 -o 条件2 ]		- 两个条件都为假,整体为假,否则为真
方法2:
	[[ 条件1 && 条件2 ]]	- 两个条件都为真,整体为真,否则为假
	[[ 条件1 || 条件2 ]]	- 两个条件都为假,整体为假,否则为真

简单实践

实践1-[]组合实践

[root@localhost ~]# user=root pass=123456
[root@localhost ~]# [ $user == "root" -a $pass == "123456" ]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ $user == "root" -a $pass == "1234567" ]
[root@localhost ~]# echo $?
1
[root@localhost ~]# [ $user == "root" -o $pass == "1234567" ]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ $user == "root1" -o $pass == "1234567" ]
[root@localhost ~]# echo $?
1

实践2 - [[]]组合实践

[root@localhost ~]# [[ $user == "root" && $pass == "123456" ]]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [[ $user == "root" && $pass == "1234567" ]]
[root@localhost ~]# echo $?
1
[root@localhost ~]# [[ $user == "root" || $pass == "1234567" ]]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [[ $user == "root1" || $pass == "1234567" ]]
[root@localhost ~]# echo $?
1

6.3.4 综合实践

这一节,我们从 堡垒机登录、信息检测、小结 三个方面来学习。

堡垒机登录

脚本功能-扩充用户名和密码验证功能

[root@localhost ~]# cat simple_jumpserver.sh
#!/bin/bash
# 功能:定制堡垒机的展示页面
# 版本:v0.3
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
login_user='root'
login_pass='123456'

# 堡垒机的信息提示
echo -e "\e[31m \t\t 欢迎使用堡垒机"
echo -e "\e[32m
-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------
"'\033[0m'

# 由于暂时没有学习条件判断,所以暂时选择 q
read -p "请输入您要选择的远程主机编号: " host_index
read -p "请输入登录本地主机的用户名: " user
read -s -p "请输入登录本地主机的密码: " password
echo
# 远程连接主机
[[ ${user} == ${login_user} && ${password} == ${login_pass} ]] && echo "主机登录验证成功" || echo "您输入的用户名或密码有误"
脚本执行效果
[root@localhost ~]# /bin/bash simple_jumpserver.sh
                 欢迎使用堡垒机

-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------

请输入您要选择的远程主机编号: q
请输入登录本地主机的用户名: root
请输入登录本地主机的密码:
主机登录验证成功
[root@localhost ~]# /bin/bash simple_jumpserver.sh
                 欢迎使用堡垒机

-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------

请输入您要选择的远程主机编号: q
请输入登录本地主机的用户名: python
请输入登录本地主机的密码:
您输入的用户名或密码有误

信息检测

脚本功能-检测公司网站的存活

判断网站的命令
[root@localhost ~]# wget --spider -T5 -q -t2 www.baidu.com
[root@localhost ~]# echo $?
0
[root@localhost ~]# wget --spider -T5 -q -t2 www.baidu.com1
[root@localhost ~]# echo $?
4
[root@localhost ~]# curl -s -o /dev/null www.baidu.com
[root@localhost ~]# echo $?
0
[root@localhost ~]# curl -s -o /dev/null www.baidu.com1
[root@localhost ~]# echo $?
6
脚本的核心内容
[root@localhost ~]# cat site_healthcheck.sh
#!/bin/bash
# 功能:定制站点的检测功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
site_addr="$1"
# 脚本基本判断
[ -z ${site_addr} ] && echo "请输入待测试站点域名" && exit
[ $# -ne 1 ] && echo "请保证输入1个脚本参数" && exit

# 检测平台的信息提示
echo -e "\e[32m-----------检测平台支持的检测类型-----------
 1: wget
 2: curl
----------------------------------------"'\033[0m'

# 选择检测类型
read -p "请输入网站的检测方法: " check_type
site_status=$([ ${check_type} == 1 ] && wget --spider -T5 -q -t2 ${site_addr} && echo "正常" || echo "异常") 
site_status=$([ ${check_type} == 2 ] && curl -s -o /dev/null ${site_addr} && echo "正常" || echo "异常")
# 信息输出
echo
echo -e "\e[31m\t站点状态信息\e[0m"
echo -e "\e[32m================================"
echo "${site_addr} 站点状态: ${site_status}"
echo -e "================================\e[0m"
脚本执行效果
[root@localhost ~]# /bin/bash site_healthcheck.sh
请输入待测试站点域名
[root@localhost ~]# /bin/bash site_healthcheck.sh aa bb
请保证输入1个脚本参数
[root@localhost ~]# /bin/bash site_healthcheck.sh www.baidu.com
-----------检测平台支持的检测类型-----------
 1: wget
 2: curl
----------------------------------------
请输入网站的检测方法: 1

        站点状态信息
================================
www.baidu.com 站点状态: 异常
================================
[root@localhost ~]# /bin/bash site_healthcheck.sh www.baidu.com1
-----------检测平台支持的检测类型-----------
 1: wget
 2: curl
----------------------------------------
请输入网站的检测方法: 2

        站点状态信息
================================
www.baidu.com1 站点状态: 异常
================================

7 数组实践

7.1 基础操作

7.1.1 数组基础

这一节,我们从 基础知识、数组分类、小结 三个方面来学习。

基础知识

简介

	数组(Array)是有序的元素序列,它是数据结构在shell当中非常常见的一种数据存储方式,它将有限个类型相同的数据放到一个集合中,这个集合就是数组。
	为了操作方便,我们为数组定制一个名称变量,数组内的每一个数据都是数组元素,这些数组元素在集合中有顺序的观念,顺序的位置值我们称为下标。

数组分类

数组样式-从数据结构的本身出发,它主要有多种数组样式

一维数组
	一维数组是最简单的数组,按照顺序将一系列数据排列下来即可,数组本身没有场景含义。
	数据的表现样式:数组[下标]
	适用场景:编程语言中基于数据的查询、聚合等操作
二维数组
	二维数组是业务场景中常见的数组表现样式,即在一维数组的前提下,扩充了数据元素在场景中的含义。
	数据的表现样式:数组[行下标][列下标]
	适用场景:数据库场景中基于数据的查询、聚合等操作
三维数组
	三维数组是大型业务场景中通用的一种数组表现样式,它是在二维数据的前提下,扩充了数据空间的含义。
	数据的表现样式:数组[行下标][列下标][页下标]
	适用场景:数据分析场景中基于数据的查询、聚合等操作
注意:
	1 bash支持一维数组(不支持多维数组),并且没有限定数组的大小。数组元素的下标由0开始编号。
	2 获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0
	3 bash的数组支持稀疏格式(索引名称可以不连续)

7.1.2 数组定义

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

数组创建

在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的语法格式:
    array_name=(value1 ... valuen)
注意:
	基于元素的格式,主要有单行定义、多行定义、单元素定义、命令定义等多种样式

语法解读

单行定义
	array_name=(value0 value1 value2 value3)
	
多行定义
    array_name=(
    value0
    value1
    value2
    value3
    )
    
单元素定义
    array_name[0]=value0
    array_name[1]=value1
    array_name[2]=value2
    
注意:
	单元素定义的时候,可以不使用连续的下标,而且下标的范围没有限制。
	
命令定义就是value的值以命令方式来获取
	file_array=($(ls /tmp/))

简单实践

实践1-单行定义

定制数据数组
[root@localhost ~]# num_list=(123,234,345,456,567)
[root@localhost ~]# echo ${num_list[0]}
123,234,345,456,567

数据元素之间使用空格隔开
[root@localhost ~]# num_list=(123 234 345 456 567)
[root@localhost ~]# echo ${num_list[0]}
123
[root@localhost ~]# echo ${num_list[@]}
123 234 345 456 567

实践2-多行定义

定制数组
[root@localhost ~]# class_one=(
> zhangsan
> lisi
> wangwu
> zhaoliu
> )

查看数组元素
[root@localhost ~]# echo ${class_one[0]}
zhangsan
[root@localhost ~]# echo ${class_one[@]}
zhangsan lisi wangwu zhaoliu

实践3-单元素定义

定制数组
[root@localhost ~]# mix_list[0]=nihao
[root@localhost ~]# mix_list[2]=345
[root@localhost ~]# mix_list[4]="1.23,4.56"

查看数组元素
[root@localhost ~]# echo ${mix_list[1]}
[root@localhost ~]# echo ${mix_list[@]}
nihao 345 1.23,4.56

批量多元素定义
[root@localhost ~]# string_list=([0]="value-1" [3]="value-2")
[root@localhost ~]# echo ${string_list[@]}
value-1 value-2
[root@localhost ~]# echo ${!string_list[@]}
0 3

实践4-命令定义

定制数组元素
[root@localhost ~]# file_array=$(ls *.sh)

查看数组元素
[root@localhost ~]# echo ${file_array[0]}
count_head_feet.sh host_network_test.sh memory_info.sh simple_jumpserver.sh simple_login.sh simple_login_string.sh site_healthcheck.sh test_argnum.sh
[root@localhost ~]# echo ${file_array[1]}
[root@localhost ~]# echo ${file_array[2]}
[root@localhost ~]# echo ${file_array[@]}
count_head_feet.sh host_network_test.sh memory_info.sh simple_jumpserver.sh simple_login.sh simple_login_string.sh site_healthcheck.sh test_argnum.sh

注意:
	对于命令的数组创建来说,它只有一个元素

7.1.3 数组取值

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	对于shell的数组数据来说,获取制定的数组元素主要有两种方法,一种是获取内容,一种是获取其他信息。

语法解读

基于索引找内容
	读取数组元素值可以根据元素的下标值来获取,语法格式如下:
    	${array_name[index]}
    	${array_name[@]:起始位置:获取数量}
注意:
	获取具体的元素内容,指定其下标值,从0开始
	获取所有的元素内容,下标位置写"@"或者"*"
获取数组索引
	在找内容的时候,有时候不知道数组的索引都有哪些,我们可以基于如下方式来获取,数组的所有索引:
    	${!array_name[index]}
注意:
	获取所有的元素位置,下标位置写"@"或者"*"
获取数组长度的方法与获取字符串长度的方法相同,格式如下:
	${#array_name[index]}
注意:
	获取具体的元素长度,指定其下标值,从0开始
	获取所有的元素个数,下标位置写"@"或者"*"
从系统中获取所有的数组
	declare -a

简单实践

实践1-基于索引找内容

设定数组内容
[root@localhost ~]# num_list=(123 234 345 456 567)

获取指定位置元素
[root@localhost ~]# echo ${num_list[0]}
123
[root@localhost ~]# echo ${num_list[1]}
234

获取所有位置元素
[root@localhost ~]# echo ${num_list[*]}
123 234 345 456 567
[root@localhost ~]# echo ${num_list[@]}
123 234 345 456 567

获取末尾位置元素
[root@localhost ~]# echo ${num_list[-1]}
567
[root@localhost ~]# echo ${num_list[-2]}
456

获取指定范围元素
[root@localhost ~]# echo ${num_list[@]:1:1}
234
[root@localhost ~]# echo ${num_list[@]:1:3}
234 345 456

实践2-基于内容获取元素

[root@localhost ~]# echo ${!num_list[@]}
0 1 2 3 4
[root@localhost ~]# echo ${!num_list[@]}
0 1 2 3 4

实践3-获取数组长度

获取数组的元素数量
[root@localhost ~]# echo ${#num_list[@]}
5
[root@localhost ~]# echo ${#num_list[*]}
5

获取数据元素的长度
[root@localhost ~]# echo ${#num_list[3]}
3

实践4-获取系统所有数组

设定数组
[root@localhost ~]# num_list=(123 234 345 456 567)

查看所有数组
[root@localhost ~]# declare -a
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="0")'
declare -a num_list='([0]="123" [1]="234" [2]="345" [3]="456" [4]="567")'

7.1.4 数组变动

这一节,我们从 元素修改、元素删除、小结 三个方面来学习。

元素修改

简介

数组元素的改其实就是定义数组时候的单元素定义,主要包含两种,元素替换,元素部分内容替换,格式如下
元素内容替换:
	array_name[index]=值
注意:
	在修改元素的时候,index的值一定要保持准确
元素部分内容替换,可以参考字符串替换格式:
	${array_name[index]/原内容/新内容}
注意:
	默认是演示效果,原数组未被修改,如果真要更改需要结合单元素内容替换

简单实践

修改指定位置的值
[root@localhost ~]# num_list[2]=aaa
[root@localhost ~]# echo ${num_list[@]}
123 234 aaa 456 567

替换元素值的特定内容
[root@localhost ~]# echo ${num_list[2]/aa/lualu-}
lualu-a
[root@localhost ~]# num_list[2]=${num_list[2]/aa/lualu-}
[root@localhost ~]# echo ${num_list[@]}
123 234 lualu-a 456 567

元素删除

简介

将shell中的数组删除,可以使用unset来实现,主要有两种情况:删除单元素,删除整个数组。格式如下:
删除单元素
	unset array_name[index]
删除整个数组
	unset array_name

简单实践

删除指定的元素
[root@localhost ~]# echo ${num_list[@]}
123 234 lualu-a 456 567
[root@localhost ~]# unset num_list[2]
[root@localhost ~]# echo ${num_list[@]}
123 234 456 567
[root@localhost ~]# unset num_list[2]
[root@localhost ~]# echo ${num_list[@]}
123 234 456 567
[root@localhost ~]# unset num_list[1]
[root@localhost ~]# echo ${num_list[@]}
123 456 567
[root@localhost ~]# echo ${!num_list[@]}
0 3 4
替换元素值的特定内容
[root@localhost ~]# unset num_list
[root@localhost ~]# echo ${!num_list[@]}

[root@localhost ~]#

7.2 综合实践

7.2.1 数组关联

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	上一节,我们学习了shell环境下的数组定制的简写方式。数组的定制主要有如下两种:
定制索引数组 - 数组的索引是普通的数字
	declare -a array_name
	- 普通数组可以不事先声明,直接使用
	
定制关联数组 - 数组的索引是自定义的字母
	declare -A array_name
	- 关联数组必须先声明,再使用

简单实践

实践1-定制索引数组

定制一个空内容的数组
[root@localhost ~]# declare -a course
[root@localhost ~]# declare -a | grep course
declare -a course='()'

定制一个包含元素的数组
[root@localhost ~]# course=(yuwen shuxue yingyu)
[root@localhost ~]# declare -a | grep course
declare -a course='([0]="yuwen" [1]="shuxue" [2]="yingyu")'

实践2-定制关联数组

定制关联数组
[root@localhost ~]# declare -A score
[root@localhost ~]# declare -a | grep score
[root@localhost ~]# declare -A | grep score
declare -A score='()'

关联数组和数字索引数组不能通用
[root@localhost ~]# declare -a score
-bash: declare: score: 无法将关联数组转化为索引数组
关联数组的操作
[root@localhost ~]# declare -A | grep score
declare -A score='([yingyu]="32" [yuwen]="67" [shuxue]="65" )'
[root@localhost ~]# echo ${!score[@]}
yingyu yuwen shuxue
[root@localhost ~]# echo ${score[@]}
32 67 65

7.2.2 数组案例

这一节,我们从 信息统计、服务管理、小结 三个方面来学习。

信息统计

需求

分别打印CPU 1min 5min 15min load负载值
命令提示:
	uptime
	
信息显示:
    CPU 1 min平均负载为: 0.00
    CPU 5 min平均负载为: 0.01
    CPU 15 min平均负载为: 0.05

编写脚本

查看脚本内容
[root@localhost ~]# cat cpu_load.sh
#!/bin/bash
# 功能:采集系统cpu负载信息
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 获取CPU负载情况
cpu_load=($(uptime | tr -s " " | cut -d " " -f 11-13 | tr "," " "))

# 信息输出
echo -e "\e[31m\t系统cpu负载信息\e[0m"
echo -e "\e[32m================================"
echo "CPU 1 min平均负载为: ${cpu_load[0]}"
echo "CPU 5 min平均负载为: ${cpu_load[1]}"
echo "CPU 15 min平均负载为: ${cpu_load[2]}"
echo -e "================================\e[0m"
脚本执行后效果
[root@localhost ~]# /bin/bash cpu_load.sh
        系统cpu负载信息
================================
CPU 1 min平均负载为: 0.00
CPU 5 min平均负载为: 0.01
CPU 15 min平均负载为: 0.05
================================

服务管理

需求

服务的管理动作有:
	"启动" "关闭" "重启" "重载" "状态"
服务的管理命令有:
	"start" "stop" "restart" "reload" "status"
选择不同的动作,输出不同的服务执行命令,格式如下:
	systemctl xxx service_name

编写脚本

[root@localhost ~]# cat service_manager.sh
#!/bin/bash
# 功能:定制服务管理的功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通数组
oper_array=(启动 关闭 重启 重载 状态)
# 定制关联数组
declare -A cmd_array
cmd_array=([启动]=start [关闭]=stop [重启]=restart [重载]=reload [状态]=status)

# 服务的操作提示
echo -e "\e[31m---------------服务的操作动作---------------
 1: 启动  2: 关闭  3: 重启  4: 重载  5: 状态
--------------------------------------------"'\033[0m'

# 选择服务操作类型
read -p "> 请输入服务的操作动作: " oper_num
echo 
echo -e "\e[31m您选择的服务操作动作是:  ${oper_array[$oper_num-1]}\e[0m"
echo -e "\e[32m===============服务的执行动作===============
您即将对服务执行如下命令:
\tsystemctl ${cmd_array[${oper_array[$oper_num-1]}]} service_name
=========================================="'\033[0m'
脚本执行效果
[root@localhost ~]# /bin/bash service_manager.sh
---------------服务的操作动作---------------
 1: 启动  2: 关闭  3: 重启  4: 重载  5: 状态
--------------------------------------------
> 请输入服务的操作动作: 3

您选择的服务操作动作是:  重启
===============服务的执行动作===============
您即将对服务执行如下命令:
        systemctl restart service_name
==========================================

8 流程控制

8.1 基础知识

8.1.1 流程基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

编程逻辑

编程语言的目的是通过风格化的编程思路将代码写出来后,实现项目功能的。为了实现功能,我们通过在代码层面通过一些代码逻辑来实现:
    顺序执行 - 程序按从上到下顺序执行
    选择执行 - 程序执行过程中,根据条件选择不同的顺序执行
    循环执行 - 程序执行过程中,根据条件重复执行代码

shell逻辑

简介

	在shell编程中,默认情况下,处于shell脚本中的命令,它是按照从上到下的方式顺序执行每一条命令,这也导致我们在shell编程的过程中,必须保证每一条命令都能够正常的执行。当然了,真实的生产中的shell编程,不可能仅有这一种编程逻辑。
	许多程序在脚本命令之间需要某种逻辑流控制,这就意味着shell脚本在具体的场景中,根据条件判断选择一条具体的代码逻辑执行特定范围的命令 -- 脚本范围内,允许出现多个场景下的命令块,而控制执行不同命令块的编程逻辑结构,在shell编程中有一个名称 -- 结构化命令。

结构化命令

	结构化命令允许脚本程序根据条件或者相关命令的结果进行判断,执行一些功能命令块,在另外一些条件下,执行跳过这些命令块。
	在shell编程中,这些结构化的命令主要有:
条件逻辑 - 多分支执行命令块
	- if控制语句
    - case控制语句
    - select控制语句
循环逻辑 - 多循环执行命令块
	- for控制语句
	- while控制语句
	- until控制语句
逻辑控制 - 命令块执行过程中,精细化控制
	- continue控制
	- break控制
	- exit控制
	- shift控制

8.2 if条件控制

8.2.1 语法解读

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	条件结构能够根据某个特定的条件,结合内置的测试表达式功能,结合测试的结果状态值对于条件进行判断,然后选择执行合适的任务。在bash中,if命令是条件结构最简单的形式。
	shell中的if语句支持多种条件的决策形式:
单路决策 - 单分支if语句
	样式:
        if [ 条件 ]
        then
            指令
        fi
    特点:
    	单一条件,只有一个输出
双路决策 - 双分支if语句
	样式:
        if [ 条件 ]
        then
            指令1
        else
            指令2
        fi
    特点:
    	单一条件,两个输出
多路决策 - 多分支if语句
	样式:
		if [ 条件 ]
        then
            指令1
        elif [ 条件2 ]
        then
            指令2
        else
            指令3
        fi
	特点:
    	n个条件,n+1个输出
单行命令写法
	if [ 条件1 ]; then 指令1; elif [ 条件2 ]; then 指令2; ... ; else 指令n; fi
关键点解读:
	1 if 和 then 配套使用
	2 if 和末尾的 fi 顺序反写

内嵌测试语句

	shell的if语句中关于条件判断这块内嵌了如下几种测试表达式语句:
		[ 表达式 ]		- 针对通用的判断场景 
		[[ 表达式 ]]	- 针对扩展的判断场景
		(( 命令 ))	 - (())代替let命令来测试数值表达式

简单实践

实践1-单if实践

[root@localhost ~]# cat single_branch_if.sh
#!/bin/bash
# 单分支if语句的使用场景

# 定制普通变量
gender="$1"

# 条件判断逻辑
if [ "${gender}" == "nan" ]
then
   echo "您的性别是 男"
fi
脚本执行效果
[root@localhost ~]# /bin/bash single_branch_if.sh nv
[root@localhost ~]# /bin/bash single_branch_if.sh nan
您的性别是 男

实践2-双if实践

[root@localhost ~]# cat double_branch_if.sh
#!/bin/bash
# 双分支if语句的使用场景

# 定制普通变量
gender="$1"

# 条件判断逻辑
if [ "${gender}" == "nan" ]
then
   echo "您的性别是 男"
else
   echo "您的性别是 女"
fi
脚本执行效果
[root@localhost ~]# /bin/bash double_branch_if.sh
您的性别是 女
[root@localhost ~]# /bin/bash double_branch_if.sh nan
您的性别是 男
[root@localhost ~]# /bin/bash double_branch_if.sh xxx
您的性别是 女

实践3-多if实践

[root@localhost ~]# cat multi_branch_if.sh
#!/bin/bash
# 多分支if语句的使用场景

# 定制普通变量
gender="$1"

# 条件判断逻辑
if [ "${gender}" == "nan" ]
then
   echo "您的性别是 男"
elif [ "${gender}" == "nv" ]
then
   echo "您的性别是 女"
else
   echo "您的性别,我不知道"
fi
脚本执行效果
[root@localhost ~]# /bin/bash multi_branch_if.sh
您的性别,我不知道
[root@localhost ~]# /bin/bash multi_branch_if.sh nan
您的性别是 男
[root@localhost ~]# /bin/bash multi_branch_if.sh nv
您的性别是 女
[root@localhost ~]# /bin/bash multi_branch_if.sh xxx
您的性别,我不知道

8.2.2 if 案例实践

这一节,我们从 服务管理、堡垒机登录、小结 三个方面来学习。

服务管理

案例需求

要求脚本执行需要有参数,通过传入参数来实现不同的功能。

参数和功能详情如下:
	参数 			   执行效果
	start			服务启动中...
	stop			服务关闭中...
	restart			服务重启中...
	*				脚本 X.sh 使用方式 /bin/bash X.sh [ start|stop|restart ]

脚本内容

[root@localhost ~]# cat service_manager_if.sh
#!/bin/bash
# 功能:定制服务管理的功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
service_ops="$1"

# 脚本基本判断
if [ $# -ne 1 ] 
then 
    echo -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m"
    exit
fi

# 脚本内容的判断
if [ "${service_ops}" == "start" ]
then
   echo -e "\e[31m服务启动中...\e[0m"
elif [ "${service_ops}" == "stop" ]
then
   echo -e "\e[31m服务关闭中...\e[0m"
elif [ "${service_ops}" == "restart" ]
then
   echo -e "\e[31m服务重启中...\e[0m"
else
   echo -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m"
fi
脚本执行效果
[root@localhost ~]# /bin/bash service_manager_if.sh
service_manager_if.sh 脚本的使用方式: service_manager_if.sh [ start | stop | restart ]
[root@localhost ~]# /bin/bash service_manager_if.sh start
服务启动中...
[root@localhost ~]# /bin/bash service_manager_if.sh stop
服务关闭中...
[root@localhost ~]# /bin/bash service_manager_if.sh restart
服务重启中...
[root@localhost ~]# /bin/bash service_manager_if.sh xxx
service_manager_if.sh 脚本的使用方式: service_manager_if.sh [ start | stop | restart ]

堡垒机登录

需求

	在之前的堡垒机功能基础上,扩充条件判断效果

脚本内容

[root@localhost ~]# cat simple_jumpserver_if.sh
#!/bin/bash
# 功能:定制堡垒机的展示页面
# 版本:v0.4
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
login_user='root'
login_pass='123456'

# 堡垒机的信息提示
echo -e "\e[31m \t\t 欢迎使用堡垒机"
echo -e "\e[32m-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------\033[0m"

# 由于暂时没有学习条件判断,所以暂时选择 q
read -p "请输入您要选择的远程主机编号: " host_index
read -p "请输入登录本地主机的用户名: " user
read -s -p "请输入登录本地主机的密码: " password
echo
# 远程连接主机
if [[ ${user} == ${login_user} && ${password} == ${login_pass} ]] 
then
    echo -e "\e[31m主机登录验证成功\e[0m"
else
    echo -e "\e[31m您输入的用户名或密码有误\e[0m"
fi
脚本执行效果
[root@localhost ~]# /bin/bash simple_jumpserver_if.sh
                 欢迎使用堡垒机
-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------
请输入您要选择的远程主机编号: q
请输入登录本地主机的用户名: root
请输入登录本地主机的密码:
主机登录验证成功
[root@localhost ~]# /bin/bash simple_jumpserver_if.sh
                 欢迎使用堡垒机
-----------请选择你要登录的远程主机-----------
 1: 10.0.0.14 (nginx)
 2: 10.0.0.15 (tomcat)
 3: 10.0.0.19 (apache)
 q: 使用本地主机
----------------------------------------------
请输入您要选择的远程主机编号: q
请输入登录本地主机的用户名: python
请输入登录本地主机的密码:
您输入的用户名或密码有误

8.2.3 嵌套if实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	一个if语句仅仅能够针对一个场景的多种情况。当我们面对多场景的条件判断的时候,一个if结构语句无法满足需求,这个时候,我们可以借助于嵌套if的结构语句来实现相应的效果。
注意:
	如果是多个独立的、彼此没有关联的业务场景,可以使用不同的if语句即可
	嵌套的if语句只能适用于彼此关联的业务场景

简单实践

案例需求

运维管理人员,通过监控平台获取站点的运行状态数据信息,当发现问题的时候,根据情况进行后续判断:
    状况1: 问题类型
    	研发标识 - 交给开发团队
    	测试标识 - 交给测试团队
    	运维标识 - 交给运维团队
    状况2: 问题级别
		红灯 - 紧急故障
		黄灯 - 严重故障
		绿灯 - 一般故障
		灰灯 - 未知故障,后续操作

脚本内容

[root@localhost ~]# cat monitor_operator_if.sh
#!/bin/bash
# 功能:定制监控异常的处理措施
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
monitor_type=(研发 测试 运维)
error_level=(红灯 黄灯 绿灯 灰灯)

# 监控平台的信息提示
echo -e "\e[31m \t\t 欢迎使用监控处理平台"
echo -e "\e[32m-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------\033[0m"
# 定制业务逻辑
read -p "请输入问题标识: " monitor_id
# 判断问题类型是否有效
if [ ${#monitor_type[@]} -lt ${monitor_id} ]
then
	echo -e "\e[31m无效标识,请输入正确的问题标识\e[0m"
else
	# 定制问题类型识别逻辑
    if [ ${monitor_type[$monitor_id-1]} == "研发" ]
    then
        echo -e "\e[31m转交研发团队处理\e[0m"
    elif [ ${monitor_type[$monitor_id-1]} == "测试" ]
    then
        echo -e "\e[31m转交测试团队处理\e[0m"
    elif [ ${monitor_type[$monitor_id-1]} == "运维" ]
    then
        echo -e "\e[32m-----------请选择故障级别-----------"
        echo " 1: 红灯 2: 黄灯 3: 绿灯 4: 灰灯"
        echo -e "----------------------------------\033[0m"
        read -p "请输入故障级别: " level_id
        # 判断故障级别是否有效
        if [ ${#error_level[@]} -lt ${level_id} ]
        then
            echo -e "\e[31m无效标识,请输入正确的故障级别\e[0m"
        else
        	# 定制故障级别逻辑
            if [ ${error_level[$level_id-1]} == "红灯" ]
            then
                echo -e "\e[31m请按照 紧急故障 性质进行处理\e[0m"
            elif [ ${error_level[$level_id-1]} == "黄灯" ]
            then
                echo -e "\e[31m请按照 严重故障 性质进行处理\e[0m"
            elif [ ${error_level[$level_id-1]} == "绿灯" ]
            then
                echo -e "\e[31m请按照 一般故障 性质进行处理\e[0m"
            elif [ ${error_level[$level_id-1]} == "灰灯" ]
            then
                echo -e "\e[31m请按照 未知故障 性质进行处理\e[0m"
            fi
        fi
    fi
fi
脚本执行效果
[root@localhost ~]# /bin/bash monitor_operator_if.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 4
无效标识,请输入正确的问题标识
[root@localhost ~]# /bin/bash monitor_operator_if.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 1
转交研发团队处理
[root@localhost ~]# /bin/bash monitor_operator_if.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 3
-----------请选择故障级别-----------
 1: 红灯 2: 黄灯 3: 绿灯 4: 灰灯
----------------------------------
请输入故障级别: 1
请按照 紧急故障 性质进行处理
[root@localhost ~]# /bin/bash monitor_operator_if.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 3
-----------请选择故障级别-----------
 1: 红灯 2: 黄灯 3: 绿灯 4: 灰灯
----------------------------------
请输入故障级别: 6
无效标识,请输入正确的故障级别

8.2.4 其他实践

这一节,我们从 条件进阶、单行命令、小结 三个方面来学习。

条件进阶

简介

	if条件控制语句支持(())来代替let命令测试表达式的执行效果,支持[[]]实现正则级别的内容匹配。
表达式的样式如下:
if (( 表达式 ))  或 [[ 内容匹配 ]]
then
	指令
fi

实践1-(()) 计算条件匹配

查看脚本内容
[root@localhost ~]# cat odd_even_if.sh
#!/bin/bash
# (()) 在if分支中的应用

# 接收一个数字
read -p "请输入一个数字: " num

# 定制数字奇数和偶数判断
if (( ${num} % 2 == 0 ))
then
	echo -e "\e[31m ${num} 是一个偶数\e[0m"
else
	echo -e "\e[31m ${num} 是一个奇数\e[0m"
fi
脚本执行效果
[root@localhost ~]# /bin/bash odd_even_if.sh
请输入一个数字: 2
 2 是一个偶数
[root@localhost ~]# /bin/bash odd_even_if.sh
请输入一个数字: 1
 1 是一个奇数

实践2-[[]]扩展匹配条件

查看脚本内容
[root@localhost ~]# cat condition_extend_if.sh
#!/bin/bash
# [[]] 在if分支中的应用

# 接收一个数字
read -p "请输入一个单词: " string

# 判断内容是否是我们想要的
if [[ ${string} == v* ]]
then
	echo -e "\e[31m ${num} 是满足条件的单词\e[0m"
else
	echo -e "\e[31m ${num} 不满足条件\e[0m"
fi
执行脚本效果
[root@localhost ~]# /bin/bash condition_extend_if.sh
请输入一个单词: very
  是满足条件的单词
[root@localhost ~]# /bin/bash condition_extend_if.sh
请输入一个单词: hello
  不满足条件

实践3-[[]]扩展实践

#!/bin/bash
# [[]] 在if分支中的应用

# 接收一个数字
read -p "请输入你的身高(m为单位): " height

# 身高判断逻辑
if [[ ! ${height} =~ ^[0-2](\.[0-9]{,2})?$ ]]
then 
	echo -e "\e[31m你确定自己的身高是 ${height} ?\e[0m"
else
	echo -e "\e[31m我说嘛,${height} 才是你的身高!\e[0m"
fi
脚本执行效果
[root@localhost ~]# /bin/bash person_height_if.sh
请输入你的身高(m为单位): 3
你确定自己的身高是 3 ?
[root@localhost ~]# /bin/bash person_height_if.sh
请输入你的身高(m为单位): 1.2
我说嘛,1.2 才是你的身高!

单行命令

简介

所谓的单行命令,其实指的是对于简单的if条件判断,我们可以直接一行显示,减少代码的行数

简单实践

示例1-命令行的if语句
[root@localhost ~]# num1=3 num2=1
[root@localhost ~]# if [ $num1 -gt $num2 ]; then echo "$num1 数字大"; else echo "$num2 数字大"; fi
3 数字大
[root@localhost ~]# num1=3 num2=9
[root@localhost ~]# if [ $num1 -gt $num2 ]; then echo "$num1 数字大"; else echo "$num2 数字大"; fi
9 数字大
示例2-使用逻辑表达式来满足if效果
[root@localhost ~]# num1=3 num2=1
[root@localhost ~]# [ $num1 -gt $num2 ] && echo "$num1 数字大" || echo "$num2 数字大"
3 数字大
[root@localhost ~]# num1=3 num2=9
[root@localhost ~]# [ $num1 -gt $num2 ] && echo "$num1 数字大" || echo "$num2 数字大"
9 数字大

8.3 case条件控制

8.3.1 语法解读

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

case命令是一个多路分支的命令,它可以来代替 if/elif相关的命令,在case语句中,它通过引入一个变量接收用户输入的数据,然后依次与相关的值进行匹配判断,一旦找到对应的匹配值后,就执行相关的语句。

语法格式

case 变量名 in
   值1)
      指令1
         ;;
   ...
   值n)
 	   指令n
         ;;
esac
注意:
    首行关键字是case,末行关键字esac
    选择项后面都有 )
    每个选择的执行语句结尾都有两个分号;
    

简单实践

案例需求

改造多分支if语句对脚本进行升级
	要求脚本执行需要有参数,通过传入参数来实现不同的功能。

参数和功能详情如下:
	参数 		   执行效果
	start		服务启动中...
	stop		服务关闭中...
	restart		服务重启中...
	*			脚本 X.sh 使用方式 X.sh [ start|stop|restart ]

脚本效果

查看脚本内容
[root@localhost ~]# cat service_manager_case.sh
#!/bin/bash
# 功能:定制服务管理的功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
service_ops="$1"

# 脚本内容的判断
case "${service_ops}" in
    "start")
        echo -e "\e[31m服务启动中...\e[0m";;
    "stop")
        echo -e "\e[31m服务关闭中...\e[0m";;
    "restart")
        echo -e "\e[31m服务重启中...\e[0m";;
    "start")
        echo -e "\e[31m服务启动中...\e[0m";;
    *)
        echo -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m";;
esac
脚本执行效果
[root@localhost ~]# /bin/bash service_manager_case.sh
service_manager_case.sh 脚本的使用方式: service_manager_case.sh [ start | stop | restart ]
[root@localhost ~]# /bin/bash service_manager_case.sh start
服务启动中...
[root@localhost ~]# /bin/bash service_manager_case.sh stop
服务关闭中...
[root@localhost ~]# /bin/bash service_manager_case.sh restart
服务重启中...
[root@localhost ~]# /bin/bash service_manager_case.sh xxx
service_manager_case.sh 脚本的使用方式: service_manager_case.sh [ start | stop | restart ]

8.3.2 case实践

这一节,我们从 环境标准化、k8s部署、小结 三个方面来学习。

环境标准化

案例需求

	由于项目环境复杂,虽然我们创建了大量的脚本,但是管理起来不方便,需要我们做一个简单的脚本执行管理平台,输入不同的环境关键字,罗列该环境下的相关脚本。
信息提示
        欢迎使用脚本管理平台
-----------请选择功能场景-----------
 1: 系统环境下脚本
 2: web环境下脚本
 3: 数据库环境下脚本
 4: 存储环境下脚本
 5: 其他环境下脚本
----------------------------------

准备工作

mkdir os web sql storage other
touch {os/os-{1..3}.sh,web/web-{1..4}.sh,sql/sql-{1..5}.sh,storage/st-{1..2}.sh,other/other.sh}

脚本内容

查看脚本内容
[root@localhost ~]# cat scripts_manager_case.sh
#!/bin/bash
# 功能:定制服务管理的功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
env_array=(os web sql storage other)

# 监控平台的信息提示
echo -e "\e[31m        欢迎使用脚本管理平台"
echo -e "\e[32m-----------请选择功能场景-----------
 1: 系统环境下脚本
 2: web环境下脚本
 3: 数据库环境下脚本
 4: 存储环境下脚本
 5: 其他环境下脚本
----------------------------------\033[0m"
# 定制业务逻辑
read -p "请输入功能标识: " env_id

# 脚本内容的判断
case "${env_array[$env_id-1]}" in
    "os")
        echo -e "\e[31m系统环境下脚本文件有:\e[0m"
        ls os;;
    "web")
        echo -e "\e[31mweb环境下脚本文件有:\e[0m"
        ls web;;
    "sql")
        echo -e "\e[31m数据库环境下脚本文件有:\e[0m"
        ls sql;;
    "storage")
        echo -e "\e[31m存储环境下脚本文件有:\e[0m"
        ls storage;;
    "other")
        echo -e "\e[31m其他环境下脚本文件有:\e[0m"
        ls other;;
    *)
        echo -e "\e[31m请输入有效的功能场景标识\e[0m";;
esac
脚本执行效果
[root@localhost ~]# /bin/bash scripts_manager_case.sh
        欢迎使用脚本管理平台
-----------请选择功能场景-----------
 1: 系统环境下脚本
 2: web环境下脚本
 3: 数据库环境下脚本
 4: 存储环境下脚本
 5: 其他环境下脚本
----------------------------------
请输入功能标识: 6
请输入有效的功能场景标识
[root@localhost ~]# /bin/bash scripts_manager_case.sh 3
        欢迎使用脚本管理平台
-----------请选择功能场景-----------
 1: 系统环境下脚本
 2: web环境下脚本
 3: 数据库环境下脚本
 4: 存储环境下脚本
 5: 其他环境下脚本
----------------------------------
请输入功能标识: 3
数据库环境下脚本文件有:
sql-1.sh  sql-2.sh  sql-3.sh  sql-4.sh  sql-5.sh

k8s部署

案例需求

	由于k8s项目环境复杂,它依赖于很多功能步骤操作,我们需要按照合理的阶段部署整体环境。相关的操作步骤信息如下。
基础环境部署
	跨主机免密码操作
	时间同步操作
	内核配置操作
	容器私有仓库操作
高可用环境部署
	高可用环境部署
	负载均衡环境部署
kubernetes基础环境部署
	证书管理
	etcd环境部署
	集群证书配置
主角色环境部署
	apiserver环境部署
	scheduler环境部署
	controller环境部署
	认证配置
	容器环境部署
	kubelet环境部署
	kube-proxy环境部署
从角色环境部署
	容器环境部署
	kubelet环境部署
	kube-proxy环境部署

脚本内容

查看脚本内容
[root@localhost ~]# cat kubernetes_manager_case.sh
#!/bin/bash
# 功能:定制kubernetes环境部署管理的功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
env_array=(base ha k8s_base master slave)

# 监控平台的信息提示
echo -e "\e[31m     欢迎使用kubernetes部署平台"
echo -e "\e[32m-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
----------------------------------\033[0m"
# 定制业务逻辑
read -p "请输入功能标识: " env_id

# 脚本内容的判断
case "${env_array[$env_id-1]}" in
    "base")
        echo -e "\e[31m开始基础环境部署..."
        echo "1 执行跨主机免密码操作"
        echo "2 执行时间同步操作"
        echo "3 执行内核配置操作"
        echo -e "4 执行容器私有仓库部署操作\e[0m";;
    "ha")
        echo -e "\e[31高可用环境部署..."
        echo "1 执行高可用环境部署操作"
        echo -e "2 执行负载均衡环境部署操作\e[0m";;
    "k8s_base")
        echo -e "\e[31mkubernetes基础环境部署..."
        echo "1 执行证书管理操作"
        echo "2 执行etcd环境部署操作"
        echo -e "3 执行集群证书配置操作\e[0m";;
    "master")
        echo -e "\e[31m主角色环境部署..."
        echo "1 执行apiserver环境部署操作"
        echo "2 执行scheduler环境部署操作"
        echo "3 执行controller环境部署操作"
        echo "4 执行认证配置操作"
        echo "5 执行容器环境部署操作"
        echo "6 执行kubelet环境部署操作"
        echo -e "7 执行kube-proxy环境部署\e[0m";;
    "slave")
        echo -e "\e[31m主角色环境部署..."
        echo "1 执行容器环境部署操作"
        echo "2 执行kubelet环境部署操作"
        echo -e "3 执行kube-proxy环境部署\e[0m";;
    *)
        echo -e "\e[31m请输入有效的功能场景标识\e[0m";;
esac
脚本执行效果
[root@localhost ~]# /bin/bash kubernetes_manager_case.sh
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
----------------------------------
请输入功能标识: 7
请输入有效的功能场景标识
[root@localhost ~]# /bin/bash kubernetes_manager_case.sh
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
----------------------------------
请输入功能标识: 5
主角色环境部署...
1 执行容器环境部署操作
2 执行kubelet环境部署操作
3 执行kube-proxy环境部署

8.3.3 嵌套实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

这里的嵌套实践,与if语句的嵌套实践,基本一致,只不过组合的方式发生了一些变化。常见的组合样式如下:
case嵌套if语句
    case "变量" in
      	"值1")
        	if [ 条件判断 ]
        	...
        	;;
      	...
    esac
case嵌套case语句
    case "变量" in
      	"值1")
        	case语句
      	...
    esac
if嵌套case语句
	if [ 条件判断 ]
	then
        case "变量名" in
           "值1")
              指令1;;
           ...
        esac		
	else
		...
	fi

简单实践

案例需求

场景简介:
	火车站安检
		安检失败
			- 携带违禁物品禁止进站
		安检成功后进行火车票检查
			- 允许登上上火车
			- 禁止登上火车

实践1-case嵌套if语句

查看脚本内容
[root@localhost ~]# cat case_if.sh
#!/bin/bash
# 功能:case嵌套if语句实践

# 定制业务逻辑
read -p "是否携带违禁物品: " safe_check
# 业务逻辑定制
case "${safe_check}" in
    "true")
        echo -e "\e[31m不允许进入火车站\e[0m";;
    "false")
        read -p "火车票是否过期: " ticket_check
        if [ ${ticket_check} == "true" ]
        then
            echo -e "\e[31m不允许登上火车站\e[0m"
        else
            echo -e "\e[31m允许登上火车站\e[0m"
        fi;;
    *)
        echo -e "\e[31m再检查一遍\e[0m";;
esac
脚本执行效果
[root@localhost ~]# /bin/bash case_if.sh
是否携带违禁物品: true
不允许进入火车站
[root@localhost ~]# /bin/bash case_if.sh
是否携带违禁物品: false
火车票是否过期: true
不允许登上火车站
[root@localhost ~]# /bin/bash case_if.sh
是否携带违禁物品: false
火车票是否过期: false
允许登上火车站
[root@localhost ~]# /bin/bash case_if.sh
是否携带违禁物品: hah
再检查一遍

实践2-case嵌套case语句

查看脚本内容
[root@localhost ~]# cat case_case.sh
#!/bin/bash
# 功能:case嵌套case语句实践

# 定制业务逻辑
read -p "是否携带违禁物品: " safe_check
# 业务逻辑定制
case "${safe_check}" in
    "true")
        echo -e "\e[31m不允许进入火车站\e[0m";;
    "false")
        read -p "火车票是否过期: " ticket_check
        case ${ticket_check} in
            "true")
                echo -e "\e[31m不允许登上火车站\e[0m";;
            "false")
                echo -e "\e[31m允许登上火车站\e[0m";;
            *)
                echo -e "\e[31m再检查一遍\e[0m";;
        esac;;
    *)
        echo -e "\e[31m再检查一遍\e[0m";;
esac

实践3-if嵌套case语句

查看脚本内容
[root@localhost ~]# cat if_case.sh
#!/bin/bash
# 功能:if嵌套case语句实践

# 定制业务逻辑
read -p "是否携带违禁物品: " safe_check
# 业务逻辑定制
if [ "${safe_check}" == "true" ]
then
    echo -e "\e[31m不允许进入火车站\e[0m"
elif [ "${safe_check}" == "false" ]
then 
    read -p "火车票是否过期: " ticket_check
    case ${ticket_check} in
        "true")
            echo -e "\e[31m不允许登上火车站\e[0m";;
        "false")
            echo -e "\e[31m允许登上火车站\e[0m";;
        *)
            echo -e "\e[31m再检查一遍\e[0m";;
    esac
else
    echo -e "\e[31m再检查一遍\e[0m"
fi

8.3.4 嵌套案例

这一节,我们从 案例需求、简单实践、小结 三个方面来学习。

案例需求

运维管理人员,通过监控平台获取站点的运行状态数据信息,当发现问题的时候,根据情况进行后续判断:
    状况1: 问题类型
    	研发标识 - 交给开发团队
    	测试标识 - 交给测试团队
    	运维标识 - 交给运维团队
    状况2: 问题级别
		红灯 - 紧急故障
		黄灯 - 严重故障
		绿灯 - 一般故障
		灰灯 - 未知故障,后续操作

简单实践

实践-改造嵌套if的监控管理脚本

查看脚本内容
[root@localhost ~]# cat monitor_operator_if_case.sh
# 功能:定制监控异常的处理措施
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
monitor_type=(研发 测试 运维)
error_level=(红灯 黄灯 绿灯 灰灯)

# 监控平台的信息提示
echo -e "\e[31m \t\t 欢迎使用监控处理平台"
echo -e "\e[32m-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------\033[0m"
# 定制业务逻辑
read -p "请输入问题标识: " monitor_id
# 判断问题类型是否有效
if [ ${#monitor_type[@]} -lt ${monitor_id} ]
then
    echo -e "\e[31m无效标识,请输入正确的问题标识\e[0m"
else
    # 定制问题类型识别逻辑
    case ${monitor_type[$monitor_id-1]} in
        "研发")
            echo -e "\e[31m转交研发团队处理\e[0m";;
        "测试")
			echo -e "\e[31m转交测试团队处理\e[0m";;
        "运维")
            echo -e "\e[32m-----------请选择故障级别-----------"
            echo " 1: 红灯 2: 黄灯 3: 绿灯 4: 灰灯"
            echo -e "----------------------------------\033[0m"
            read -p "请输入故障级别: " level_id
            # 定制故障级别逻辑
            case ${error_level[$level_id-1]} in
                "红灯")
                    echo -e "\e[31m请按照 紧急故障 性质进行处理\e[0m";;
                "黄灯")
                    echo -e "\e[31m请按照 严重故障 性质进行处理\e[0m";;
                "绿灯")
                    echo -e "\e[31m请按照 一般故障 性质进行处理\e[0m";;
                "灰灯")
                    echo -e "\e[31m请按照 未知故障 性质进行处理\e[0m";;
                *)
                    echo -e "\e[31m无效标识,请输入正确的故障级别\e[0m";;
            esac
   esac
fi
脚本执行效果
[root@localhost ~]# /bin/bash monitor_operator_if_case.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 1
转交研发团队处理
[root@localhost ~]# /bin/bash monitor_operator_if_case.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 4
无效标识,请输入正确的问题标识

[root@localhost ~]# /bin/bash monitor_operator_if_case.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 3
-----------请选择故障级别-----------
 1: 红灯 2: 黄灯 3: 绿灯 4: 灰灯
----------------------------------
请输入故障级别: 3
请按照 一般故障 性质进行处理
[root@localhost ~]# /bin/bash monitor_operator_if_case.sh
                 欢迎使用监控处理平台
-----------请选择问题类型-----------
 1: 研发 2: 测试 3: 运维
----------------------------------
请输入问题标识: 3
-----------请选择故障级别-----------
 1: 红灯 2: 黄灯 3: 绿灯 4: 灰灯
----------------------------------
请输入故障级别: 6
无效标识,请输入正确的故障级别

8.4 for循环

8.4.1 for基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	生产工作中,我们有可能会遇到一种场景,需要重复性的执行相同的动作,我们在shell编程的过程中,我们可以借助于循环逻辑的方法来进行处理。
循环逻辑语法解析:
	关键字 [ 条件 ]
	do
		执行语句
	done
	
注意:
	这里的关键字主要有四种:
		for 	- 循环遍历一个元素列表
		while	- 满足条件情况下一直循环下去
		until	- 不满足条件情况下一直循环下去
		select	- 一种特殊的循环遍历,侧重于遍历用户输入,一般结合case等语句使用

简单实践

for语法解析

场景:遍历列表
    for 值 in 列表
    do
       执行语句
    done
注意:
    ”for” 循环总是接收 “in” 语句之后的某种类型的字列表
    执行次数和list列表中常数或字符串的个数相同,当循环的数量足够了,就自动退出

列表生成

样式1:手工列表
	- 1 2 3 4 5 6 7
样式2:定制列表
	- {1..7}
样式3:命令生成
	- $(seq 1 7)
样式4:脚本参数
	- $@ $*

实践1-手工列表

查看脚本内容
[root@localhost ~]# cat for_hand_list.sh
#!/bin/bash
# 功能:手工列表 for循环
for i in yuwen shuxue lishi
do
  echo "列表元素: ${i}"
done

脚本执行后效果
[root@localhost ~]# /bin/bash for_hand_list.sh
列表元素: yuwen
列表元素: shuxue
列表元素: lishi

实践2-定制列表

查看脚本内容
[root@localhost ~]# cat for_define_list.sh
#!/bin/bash
# 功能:定制列表 for循环
for i in {1..3}
do
  echo "列表元素: ${i}"
done

脚本执行后效果
[root@localhost ~]# /bin/bash for_define_list.sh
列表元素: 1
列表元素: 2
列表元素: 3

实践3-命令生成

查看脚本内容
[root@localhost ~]# cat for_cmd_list.sh
#!/bin/bash
# 功能:命令生成列表 for循环
for i in $(seq 1 3)
do
  echo "列表元素: ${i}"
done

脚本执行后效果
[root@localhost ~]# /bin/bash for_cmd_list.sh
列表元素: 1
列表元素: 2
列表元素: 3

实践4-脚本参数

查看脚本内容
[root@localhost ~]# cat for_arg_list.sh
#!/bin/bash
# 功能:脚本参数列表 for循环
for i in $@
do
  echo "列表元素: ${i}"
done

脚本执行后效果
[root@localhost ~]# /bin/bash for_arg_list.sh 1 2 3
列表元素: 1
列表元素: 2
列表元素: 3

8.4.2 for循环案例

这一节,我们从 普通循环、赋值循环、小结 三个方面来学习。

普通循环

简介

所谓的普通循环,仅仅是在特定的范围循环中,对相关命令进行重复性的操作

批量实践1- 批量创建多个使用随机密码的用户

查看脚本内容
[root@localhost ~]# cat for_add_user.sh
#!/bin/bash
# 功能:for批量创建用户
# 提示:
# /dev/random和/dev/urandom设备文件会生成随机数,第一个依赖系统中断

# 定制普通变量
user_file='/tmp/user.txt'

# 保证文件可用
[ -f ${user_file} ] && > ${user_file}

# 定制批量创建用户的业务逻辑
for i in {1..5}
do
    # 创建用户
    useradd user-$i
    # 生成密码
    password=$(head /dev/urandom | tr -dc '[:alnum:]' | head -c 8)
    # 为用户添加密码
    echo ${password} | passwd --stdin user-$i > /dev/null 2>&1
    # 信息输出
    echo "用户: user-$i, 密码: ${password}" >> ${user_file}
    echo -e "\e[31m用户 user-$i 创建成功\e[0m"
done
脚本执行后效果
[root@localhost ~]# /bin/bash for_add_user.sh
用户 user-1 创建成功
用户 user-2 创建成功
用户 user-3 创建成功
用户 user-4 创建成功
用户 user-5 创建成功
[root@localhost ~]# cat /tmp/user.txt
用户: user-1, 密码: 6eiNoy6i
用户: user-2, 密码: rWZ0VoIl
用户: user-3, 密码: i2MSnjls
用户: user-4, 密码: 9wzLjU7z
用户: user-5, 密码: U8a3Cj3R
清理环境
[root@localhost ~]# for i in {1..5};do userdel -r user-$i;done
[root@localhost ~]# grep user- /etc/passwd

批量实践2- 批量对特定网段的主机进行扫描

查看脚本内容
[root@localhost ~]# cat for_host_check.sh
#!/bin/bash
# 功能:for批量检测网段主机存活情况

# 定制普通变量
netsub='10.0.0'
net_file='/tmp/host.txt'

# 保证文件可用
[ -f ${net_file} ] && > ${net_file}

# 定制批量检测网段主机状态逻辑
for ip in {1..254}
do
    # 测试主机连通性
    host_status=$(ping -c1 -W1 $netsub.$ip >/dev/null 2>&1 && echo "UP" || echo "DOWN")
    echo "$netsub.$ip 主机状态: $host_status" >> ${net_file}
done
# 信息输出
live_num=$(grep UP ${net_file} | wc -l)
unlive_num=$(grep DOWN ${net_file} | wc -l)
echo -e "\e[31m${netsub}.0 网段主机存活情况\e[0m"
echo "------------------------------------"
echo -e "\e[32m${netsub}.0 网段存活主机数量: ${live_num}\e[0m"
echo -e "\e[32m${netsub}.0 网段异常主机数量: ${unlive_num}\e[0m"
脚本执行效果
[root@localhost ~]# /bin/bash for_host_check.sh
10.0.0.0 网段主机存活情况
------------------------------------
10.0.0.0 网段存活主机数量: 2
10.0.0.0 网段异常主机数量: 252

[root@localhost ~]# grep UP /tmp/host.txt
10.0.0.2 主机状态: UP
10.0.0.12 主机状态: UP

赋值循环

简介

所谓的赋值循环,指的是在for循环过程中进行数据的统计等相关计算操作

统计实践1- 计算1+2+...+100 的结果

查看脚本内容
[root@localhost ~]# cat for_odd_num.sh
#!/bin/bash
# 功能:for统计数据之和

# 定制普通变量
all_sum=0
odd_sum=0

# 定制所有数据求和逻辑
for i in {1..100}
do
  let all_sum+=i
done

# 定制所有奇数求和逻辑
for i in {1..100..2}
do
  let odd_sum+=i
done

# 信息输出
echo -e "\e[31m所有数据之和: ${all_sum}\e[0m"
echo -e "\e[31m所有奇数之和: ${odd_sum}\e[0m"

脚本执行后效果
[root@localhost ~]# /bin/bash for_odd_sum.sh
所有数据之和: 5050
所有奇数之和: 2500

8.4.3 for (())案例

这一节,我们从 基础知识、循环案例、小结 三个方面来学习。

基础实践

简介

	在for循环的语法中,它还支持一种包含赋值+循环双功能的语法,也就是双小括号(()),这种语法的语法格式如下:
	样式1: 单元素样式
		for (( i=0; i<10; i++ ))
	样式2: 多元素样式
		for (( i=0,j=0; i<10; i++,j++ ))
功能解读
	1 第一部分定制一个包含初始值的变量名
	2 第二部分定制循环的结束条件
	3 第三部分定制循环过程中的变量变化效果
		- 为了让循环出现结束
注意事项:
	1 变量的复制可以包含空格
	2 条件中的变量不能用$符号
	3 第三部分的数据操作过程不用 expr格式

实践1-(())简单使用

输出1-5的数字
[root@localhost ~]# for ((i=1;i<=5;i++));do echo $i;done
1
2
3
4
5

输出1-10中的所有奇数
[root@localhost ~]# for ((i=1;i<=10;i+=2));do echo $i;done
1
3
5
7
9

输出1-10中的所有偶数
[root@localhost ~]# for ((i=2;i<=10;i+=2));do echo $i;done
2
4
6
8
10

实践2-100个数字的求和

查看脚本内容
[root@localhost ~]# cat for_odd_num.sh
#!/bin/bash
# 功能:for统计数据之和

# 定制普通变量
all_sum=0
odd_sum=0

# 定制所有数据求和逻辑
for ((i=1;i<=100;i++))
do
  let all_sum+=i
done

# 定制所有奇数求和逻辑
for ((i=1;i<=100;i+=2))
do
  let odd_sum+=i
done

# 信息输出
echo -e "\e[31m所有数据之和: ${all_sum}\e[0m"
echo -e "\e[31m所有奇数之和: ${odd_sum}\e[0m"

脚本执行后效果
[root@localhost ~]# /bin/bash for_odd_sum.sh
所有数据之和: 5050
所有奇数之和: 2500

循环案例

实践1-命令行进度条数字

[root@localhost ~]# for ((i = 0; i<=100; ++i)); do printf "\e[4D%3d%%" $i;sleep 0.1s; done
 27%
... ...
[root@localhost ~]# for ((i = 0; i<=100; ++i)); do printf "\e[4D%3d%%" $i;sleep 0.1s; done
 99%

命令解读:
	%3d%% 指的是 3个数字位置 + 1个%位置,共计4个位置
	防止信息输出的叠加,采用\e[4D,每次输出信息的时候,光标左移4个位置,信息不会出现叠加
	
\e[3D 的演示
[root@localhost ~]# for ((i = 0; i <= 100; ++i)); do printf "\e[2D%3d%%" $i;sleep 0.1s; done
                     1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 56 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9100%[root@localhost ~]#

实践2-脚本生成进度条

查看文件内容
[root@localhost ~]# cat progress_bar_for.sh
#!/bin/bash
# 定制简单的进度条

# 定制进度条的进度符号
str="#"
# 定制进度转动提示符号,注意\\转义
arr=("|" "/" "-" "\\")

# 定制进度条循环控制
for ((i=0; i<=50; i++))
do
    # 设定数组信息的变化索引
    let index=i%4
    # 打印信息,格式:【%s进度符号】【%d进度数字】【%c进度进行中】
    # 注意:信息的显示宽度和进度的数字应该适配,否则终端显示不全
    printf "[%-50s][%d%%]%c\r" "$str" "$(($i*2))" "${arr[$index]}"
    # 进度的频率
    sleep 0.2
    # 进度符前进
    str+="#"
done
printf "\n"
脚本执行效果
[root@localhost ~]# /bin/bash progress_bar_for.sh
[###################################################][100%]-

实践2-生成10个随机数保存于数组中,并找出其最大值和最小值

查看脚本内容
[root@localhost ~]# cat compare_nums_for.sh
#!/bin/bash
# 设定随机数比大小

# 设定基本变量
declare -i min max
declare -a nums

# 设定大小比较
for ((i=0;i<10;i++))
do
    # 将随机数添加到数组中
    nums[$i]=$RANDOM
    # 设定初始值
    [ $i -eq 0 ] && min=${nums[0]} max=${nums[0]}
    # 设定最大值
    [ ${nums[$i]} > $max ] && max=${nums[$i]}
    # 设定最小值
    [ ${nums[$i]} < $min ] && min=${nums[$i]}
done
echo -e "\e[31m    随机数的统计信息\e[0m"
echo "------------------------------"
echo -e "\e[32m所有的随机数:${nums[@]}"
echo -e "最大的随机数:${max}"
echo -e "最小的随机数:${min}\e[0m"
脚本执行效果
[root@localhost ~]# /bin/bash compare_num_for.sh.sh
    随机数的统计信息
------------------------------
所有的随机数:32506 31542 6495 11273 8532 5789 22139 25904 16252 7357
最大的随机数:7357
最小的随机数:32506

8.4.4 嵌套循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

这里的嵌套实践,与选择语句的嵌套实践基本一致,只不过组合的方式发生了一些变化。常见的组合样式如下:
for嵌套for语句
    for 循环列表1
    do
      	for 循环列表2
      	do
        	...
      	done
    done
for嵌套if|case语句
    for 循环列表
    do
      	if 条件判断语句
      	或
      	case 条件判断语句
    done
if语句嵌套for语句
	if 条件判断
	then
		for 循环列表语句
	fi
	
case语句嵌套for语句	
	case 条件判断
		for 循环列表语句
		;;
	esac

简单实践

for嵌套for语句实践1-输出99乘法表

查看脚本内容
[root@localhost ~]# cat for_nine_table.sh
#!/bin/bash
# 功能:for打印99乘法表

# 定制打印99乘法表的业务逻辑
# 对第一个位置的数字进行循环
for num1 in {1..9}
do
    # 对第二个位置的数字进行循环
    for num2 in $(seq $num1)
    do
        # 信息输出,\t\c 的目的是删除后续信息,生成的内容是固定长度
        echo -e "\e[$[RANDOM%9+31]m${num1} x ${num2} = $[num1*num2]\e[0m\t\c"
    done
    echo # 一个子循环一行内容
done

脚本执行效果

for嵌套if语句实践2-判断目录中的文件类型

准备工作
mkdir dir/{server,soft,scripts,logs} -p
touch dir/{{a..c}.sh,{1..3}.txt}
查看脚本内容
[root@localhost ~]# cat file_type_for_if.sh
#!/bin/bash
# 功能:for嵌套if查看目录下的文件类型

# 定制普通变量
dir_name='dir'

# 获取所有文件列表
for file in $(ls dir)
do
    # 判断文件类型
    if [ -d ${dir_name}/${file} ]
    then
        echo -e "\e[31m${dir_name}/${file} 是一个目录文件\e[0m"
    elif [ -f ${dir_name}/${file} ]
    then
        echo -e "\e[31m${dir_name}/${file} 是一个普通文件\e[0m"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash file_type_for_if.sh
dir/1.txt 是一个普通文件
dir/2.txt 是一个普通文件
dir/3.txt 是一个普通文件
dir/a.sh 是一个普通文件
dir/b.sh 是一个普通文件
dir/c.sh 是一个普通文件
dir/logs 是一个目录文件
dir/scripts 是一个目录文件
dir/server 是一个目录文件
dir/soft 是一个目录文件
收尾动作
[root@localhost ~]# rm -rf dir

if嵌套for语句实践3-获取系统支持的shell类型

查看脚本内容
[root@localhost ~]# cat os_shell_if_for.sh
#!/bin/bash
# 功能:if嵌套for查看系统支持的shell类型

# 定制普通变量
shell_file='/etc/shells'

# 获取所有文件列表
if [ -f ${shell_file} ]
then
    for shell in $(grep sh /etc/shells)
    do
        echo -e "\e[31m当前系统支持的shell类型有: ${shell}\e[0m"
    done
else
    echo -e "\e[31m没有 ${shell_file} 文件\e[0m"
fi
脚本执行效果
[root@localhost ~]# /bin/bash os_shell_if_for.sh
当前系统支持的shell类型有: /bin/sh
当前系统支持的shell类型有: /bin/bash
当前系统支持的shell类型有: /usr/bin/sh
当前系统支持的shell类型有: /usr/bin/bash

8.4.5 综合案例

这一节,我们从 信息收集、其他实践、小结 三个方面来学习。

信息收集

案例需求

	根据提示信息,选择输出 cpu 或者 内存信息。

脚本实践-采集系统负载信息

查看脚本内容
[root@localhost ~]# cat systemctl_load.sh
#!/bin/bash
# 功能:采集系统负载信息
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制资源类型
resource_type=(CPU MEM)
cpu_attribute=(1 5 15)
free_attribute=(总量 使用 空闲)

# 获取相关的属性信息
cpu_load=($(uptime | tr -s " " | cut -d " " -f 11-13 | tr "," " "))
free_info=($(free -m | grep Mem | tr -s " " | cut -d " " -f 2-4))
# 服务的操作提示
echo -e "\e[31m---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------"'\033[0m'
# 选择服务操作类型
read -p "> 请输入要查看的资源信息类型: " resource_id
echo
if [ ${resource_type[$resource_id-1]} == "CPU" ]
then
    echo -e "\e[31m\t系统CPU负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!cpu_attribute[@]}
    do
        echo "CPU ${cpu_attribute[$index]} min平均负载为: ${cpu_load[$index]}" 
    done
    echo -e "================================\e[0m"
elif [ ${resource_type[$resource_id-1]} == "MEM" ]
then
    echo -e "\e[31m\t系统内存负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!free_attribute[@]}
    do
        echo "内存 ${free_attribute[$index]} 信息为: ${free_info[$index]} M" 
    done
    echo -e "================================\e[0m"
fi
脚本使用效果
[root@localhost ~]# /bin/bash systemctl_load.sh
---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------
> 请输入要查看的资源信息类型: 1

        系统CPU负载信息
================================
CPU 1 min平均负载为: 0.00
CPU 5 min平均负载为: 0.01
CPU 15 min平均负载为: 0.05
================================
[root@localhost ~]# /bin/bash systemctl_load.sh
---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------
> 请输入要查看的资源信息类型: 2

        系统内存负载信息
================================
内存 总量 信息为: 3770 M
内存 使用 信息为: 247 M
内存 空闲 信息为: 3302 M
================================

其他实践

需求

	按照信息提示,分别打印 三角形 和 等腰梯形
        *                  *****
       * *                *******
      * * *              *********
     * * * *            ***********
    * * * * *          *************	

脚本内容

[root@localhost ~]# cat drawn_graph.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

graph_type=(三角形 梯形)
# 服务的操作提示
echo -e "\e[31m---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------"'\033[0m'
# 选择服务操作类型
read -p "> 请输入要查看的资源信息类型: " graph_id
case ${graph_type[$graph_id-1]} in
    "三角形")
        read -p "> 请输入三角形绘制的层数: " layer_num
        # 定制打印n层的三角形
        for i in $(seq 1 ${layer_num});do
            # 定制打印三角形左侧的空格效果
            for m in $(seq $[${layer_num}-$i]);do
                echo -n " "
            done
            # 定制打印三角形核心部分
            for j in $(seq $i);do
                echo -n "* "
            done
            # 打印完每行就换行
            echo
        done;;
    "梯形")
        read -p "> 请输入梯形绘制的层数: " layer_num
        # 定制打印n层的梯形
        print_num=${layer_num}
        for i in $(seq 1 ${layer_num});do
            # 定制打印梯形左侧的空格效果
            for m in $(seq $[${layer_num}-$i]);do
                echo -n " "
            done
            # 定制打印梯形核心部分
            for j in $(seq $[$print_num]);do
                echo -n "*"
            done
            let print_num+=2
            echo
        done;;
    *)
        echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
esac
脚本执行效果
[root@localhost ~]# /bin/bash drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
[root@localhost ~]# /bin/bash drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 2
> 请输入梯形绘制的层数: 5
    *****
   *******
  *********
 ***********
*************

9 流程控制

9.1 for循环

9.1.1 嵌套循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

这里的嵌套实践,与选择语句的嵌套实践基本一致,只不过组合的方式发生了一些变化。常见的组合样式如下:
for嵌套for语句
    for 循环列表1
    do
      	for 循环列表2
      	do
        	...
      	done
    done
for嵌套if|case语句
    for 循环列表
    do
      	if 条件判断语句
      	或
      	case 条件判断语句
    done
if语句嵌套for语句
	if 条件判断
	then
		for 循环列表语句
	fi
	
case语句嵌套for语句	
	case 条件判断
		for 循环列表语句
		;;
	esac

简单实践

for嵌套for语句实践1-输出99乘法表

查看脚本内容
[root@localhost ~]# cat for_nine_table.sh
#!/bin/bash
# 功能:for打印99乘法表

# 定制打印99乘法表的业务逻辑
# 对第一个位置的数字进行循环
for num1 in {1..9}
do
    # 对第二个位置的数字进行循环
    for num2 in $(seq $num1)
    do
        # 信息输出,\t\c 的目的是删除后续信息,生成的内容是固定长度
        echo -e "\e[$[RANDOM%9+31]m${num1} x ${num2} = $[num1*num2]\e[0m\t\c"
    done
    echo # 一个子循环一行内容
done

脚本执行效果

for嵌套if语句实践2-判断目录中的文件类型

准备工作
mkdir dir/{server,soft,scripts,logs} -p
touch dir/{{a..c}.sh,{1..3}.txt}
查看脚本内容
[root@localhost ~]# cat file_type_for_if.sh
#!/bin/bash
# 功能:for嵌套if查看目录下的文件类型

# 定制普通变量
dir_name='dir'

# 获取所有文件列表
for file in $(ls dir)
do
    # 判断文件类型
    if [ -d ${dir_name}/${file} ]
    then
        echo -e "\e[31m${dir_name}/${file} 是一个目录文件\e[0m"
    elif [ -f ${dir_name}/${file} ]
    then
        echo -e "\e[31m${dir_name}/${file} 是一个普通文件\e[0m"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash file_type_for_if.sh
dir/1.txt 是一个普通文件
dir/2.txt 是一个普通文件
dir/3.txt 是一个普通文件
dir/a.sh 是一个普通文件
dir/b.sh 是一个普通文件
dir/c.sh 是一个普通文件
dir/logs 是一个目录文件
dir/scripts 是一个目录文件
dir/server 是一个目录文件
dir/soft 是一个目录文件
收尾动作
[root@localhost ~]# rm -rf dir

if嵌套for语句实践3-获取系统支持的shell类型

查看脚本内容
[root@localhost ~]# cat os_shell_if_for.sh
#!/bin/bash
# 功能:if嵌套for查看系统支持的shell类型

# 定制普通变量
shell_file='/etc/shells'

# 获取所有文件列表
if [ -f ${shell_file} ]
then
    for shell in $(grep sh /etc/shells)
    do
        echo -e "\e[31m当前系统支持的shell类型有: ${shell}\e[0m"
    done
else
    echo -e "\e[31m没有 ${shell_file} 文件\e[0m"
fi
脚本执行效果
[root@localhost ~]# /bin/bash os_shell_if_for.sh
当前系统支持的shell类型有: /bin/sh
当前系统支持的shell类型有: /bin/bash
当前系统支持的shell类型有: /usr/bin/sh
当前系统支持的shell类型有: /usr/bin/bash

9.1.2 综合案例

这一节,我们从 信息收集、其他实践、小结 三个方面来学习。

信息收集

案例需求

	根据提示信息,选择输出 cpu 或者 内存信息。

脚本实践-采集系统负载信息

查看脚本内容
[root@localhost ~]# cat systemctl_load.sh
#!/bin/bash
# 功能:采集系统负载信息
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制资源类型
resource_type=(CPU MEM)
cpu_attribute=(1 5 15)
free_attribute=(总量 使用 空闲)

# 获取相关的属性信息
cpu_load=($(uptime | tr -s " " | cut -d " " -f 11-13 | tr "," " "))
free_info=($(free -m | grep Mem | tr -s " " | cut -d " " -f 2-4))
# 服务的操作提示
echo -e "\e[31m---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------"'\033[0m'
# 选择服务操作类型
read -p "> 请输入要查看的资源信息类型: " resource_id
echo
if [ ${resource_type[$resource_id-1]} == "CPU" ]
then
    echo -e "\e[31m\t系统CPU负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!cpu_attribute[@]}
    do
        echo "CPU ${cpu_attribute[$index]} min平均负载为: ${cpu_load[$index]}" 
    done
    echo -e "================================\e[0m"
elif [ ${resource_type[$resource_id-1]} == "MEM" ]
then
    echo -e "\e[31m\t系统内存负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!free_attribute[@]}
    do
        echo "内存 ${free_attribute[$index]} 信息为: ${free_info[$index]} M" 
    done
    echo -e "================================\e[0m"
fi
脚本使用效果
[root@localhost ~]# /bin/bash systemctl_load.sh
---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------
> 请输入要查看的资源信息类型: 1

        系统CPU负载信息
================================
CPU 1 min平均负载为: 0.00
CPU 5 min平均负载为: 0.01
CPU 15 min平均负载为: 0.05
================================
[root@localhost ~]# /bin/bash systemctl_load.sh
---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------
> 请输入要查看的资源信息类型: 2

        系统内存负载信息
================================
内存 总量 信息为: 3770 M
内存 使用 信息为: 247 M
内存 空闲 信息为: 3302 M
================================

其他实践

需求

	按照信息提示,分别打印 三角形 和 等腰梯形
        *                  *****
       * *                *******
      * * *              *********
     * * * *            ***********
    * * * * *          *************	

脚本内容

[root@localhost ~]# cat drawn_graph.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

graph_type=(三角形 梯形)
# 服务的操作提示
echo -e "\e[31m---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------"'\033[0m'
# 选择服务操作类型
read -p "> 请输入要查看的资源信息类型: " graph_id
case ${graph_type[$graph_id-1]} in
    "三角形")
        read -p "> 请输入三角形绘制的层数: " layer_num
        # 定制打印n层的三角形
        for i in $(seq 1 ${layer_num});do
            # 定制打印三角形左侧的空格效果
            for m in $(seq $[${layer_num}-$i]);do
                echo -n " "
            done
            # 定制打印三角形核心部分
            for j in $(seq $i);do
                echo -n "* "
            done
            # 打印完每行就换行
            echo
        done;;
    "梯形")
        read -p "> 请输入梯形绘制的层数: " layer_num
        # 定制打印n层的梯形
        print_num=${layer_num}
        for i in $(seq 1 ${layer_num});do
            # 定制打印梯形左侧的空格效果
            for m in $(seq $[${layer_num}-$i]);do
                echo -n " "
            done
            # 定制打印梯形核心部分
            for j in $(seq $[$print_num]);do
                echo -n "*"
            done
            let print_num+=2
            echo
        done;;
    *)
        echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
esac
脚本执行效果
[root@localhost ~]# /bin/bash drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
[root@localhost ~]# /bin/bash drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 2
> 请输入梯形绘制的层数: 5
    *****
   *******
  *********
 ***********
*************

9.2 while循环

9.2.1 while基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	while命令有点像 if/then 和 for循环之间的结合,while走循环之前会对输入的值进行条件判断,如果满足条件的话,才会进入到循环体中执行对应的语句,否则的话就退出循环。

while语法解析

场景:只要条件满足,就一直循环下去
    while [ 条件判断 ]
    do
       执行语句
    done
注意:
    条件支持的样式 命令、[[ 字符串表达式 ]]、(( 数字表达式 ))
    true是一个特殊的条件,代表条件永远成立

简单实践

实践1-输出制定的范围数字

[root@localhost ~]# cat while_num_list.sh
#!/bin/bash
# 功能:while的输出5范围以内的数字

# 定制初始变量值
a=1

# 定制内容输出逻辑
while [ "${a}" -le 5 ]
do
   echo -n "${a} "
   # 每输出一次数据,数据值+1
   a=$((a+1))
done
echo
脚本执行后效果
[root@localhost ~]# /bin/bash while_num_list.sh
1 2 3 4 5

9.2.2 while案例

这一节,我们从 案例实践、read实践、小结 三个方面来学习。

案例实践

统计实践1- 计算1+2+...+100 的结果

查看脚本内容
[root@localhost ~]# cat while_odd_num.sh
#!/bin/bash
# 功能:while统计数据之和

# 定制普通变量
all_sum=0
odd_sum=0

# 定制所有数据求和逻辑
i=1
while ((i<=100))
do
  let all_sum+=i
  let i++
done

# 定制所有奇数求和逻辑
i=1
while ((i<=100))
do
  let odd_sum+=i
  let i+=2
done

# 信息输出
echo -e "\e[31m所有数据之和: ${all_sum}\e[0m"
echo -e "\e[31m所有奇数之和: ${odd_sum}\e[0m"

脚本执行后效果
[root@localhost ~]# /bin/bash while_odd_num.sh
所有数据之和: 5050
所有奇数之和: 2500

实践2-持续检测网站存活

查看文件内容
[root@localhost ~]# cat while_site_healthcheck.sh
#!/bin/bash
# 功能:定制站点的检测功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
read -p "> 请输入待测试站点域名: " site_addr

# 持久检测站点状态
while true
do
  wget --spider -T5 -q -t2 ${site_addr} && echo "${site_addr} 站点正常" || echo "${site_addr} 站点异常"
  sleep 1
done
脚本执行效果
[root@localhost ~]# /bin/bash while_site_healthcheck.sh
> 请输入待测试站点域名: www.baidu.com
www.baidu.com 站点正常
www.baidu.com 站点正常
...

read实践

功能简介

	while中有一种特殊的语法,while read line 它可以从文本中逐行读取相关的内容,然后存储到一个临时变量line中,然后我们后续就可以逐行对文本内容进行操作

语法解读

样式1: cat提前读				
cat a.log | while read line
do                         
    echo "File: ${line}"     
done                       
样式2: exec提前读    
exec 0< a.log    
while read line  
do               
    echo "${line}"
done
样式3:结尾导入读
while read line
do
    echo "File: ${line}"
done < a.log

注意:
	方法1和3可以直接在命令行来实验,但是方法2必须在脚本中才能实验

实践1-命令行实践

方法1实践读取文件
[root@localhost ~]# cat /etc/hosts | while read line;do echo "File: ${line}";done
File: 127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
File: ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

方法3实践读取文件
[root@localhost ~]# while read line;do echo "File: ${line}";done < /etc/hosts
File: 127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
File: ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

实践2-脚本实践

方法2实践读取文件
[root@localhost ~]# cat while_read_file.sh
#!/bin/bash
# 功能:while的exec读取文件内容

# 定制普通变量
read -p "> 请输入待读取的文件路径: " file_path

# 持久检测站点状态
exec < ${file_path}    
while read line  
do               
    echo "File: ${line}"
done
脚本执行效果
[root@localhost ~]# /bin/bash while_read_file.sh
> 请输入待读取的文件路径: /etc/hosts
File: 127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
File: ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

9.2.3 嵌套案例

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

这里的嵌套实践,与选择语句的嵌套实践基本一致,只不过组合的方式发生了一些变化。常见的组合样式如下:
while嵌套while语句
    while 循环条件
    do
    	while 循环条件语句
    done
while嵌套if语句
    while 循环条件
    do
    	if 条件控制语句
    done	

简单实践

while嵌套while语句实践1-输出99乘法表

查看脚本内容
[root@localhost ~]# cat while_nine_table.sh
#!/bin/bash
# 功能:while打印99乘法表

# 定制打印99乘法表的业务逻辑
# 对第一个位置的数字进行循环
num1=1
while [ ${num1} -le 9 ]
do
    # 对第二个位置的数字进行循环
    num2=1
    while [ ${num2} -le ${num1} ]
    do
        # 信息输出,\t\c 的目的是删除后续信息,生成的内容是固定长度
        echo -e "\e[$[RANDOM%9+31]m${num1}x${num2}=$[num1*num2]\e[0m\t\c"
        num2=$[$num2+1]
    done
    echo # 一个子循环一行内容
    num1=$[$num1+1]
done	
脚本文件执行效果
[root@localhost ~]# /bin/bash while_nine_table.sh
1x1=1
2x1=2   2x2=4
3x1=3   3x2=6   3x3=9
4x1=4   4x2=8   4x3=12  4x4=16
5x1=5   5x2=10  5x3=15  5x4=20  5x5=25
6x1=6   6x2=12  6x3=18  6x4=24  6x5=30  6x6=36
7x1=7   7x2=14  7x3=21  7x4=28  7x5=35  7x6=42  7x7=49
8x1=8   8x2=16  8x3=24  8x4=32  8x5=40  8x6=48  8x7=56  8x8=64
9x1=9   9x2=18  9x3=27  9x4=36  9x5=45  9x6=54  9x7=63  9x8=72  9x9=81

while嵌套if语句实践2-手机发送短信1次/1毛,余额低于1毛提示无法发送请充值

[root@localhost ~]# cat while_mobile_bill.sh
#!/bin/bash
# 功能:while提示收集发短信

# 定制普通变量
read -p "> 请输入收集话费余额(元): " mobile_bill

# 定制普通变量
sms_num=0
bull_count=$[$mobile_bill * 10]
while [ $bull_count -ge 0 ]
do
    sms_num=$(($sms_num+1))
    if [ $bull_count -lt 1 ];then
        echo "剩余费用不足,请充话费!"
    else
        echo "截至目前,您已发送 ${sms_num} 条短信。"
    fi
    bull_count=$(($bull_count-1))
    sleep 0.1
done
脚本执行效果
[root@localhost ~]# /bin/bash while_mobile_bill.sh
> 请输入收集话费余额(元): 1
截至目前,您已发送 1 条短信。
截至目前,您已发送 2 条短信。
截至目前,您已发送 3 条短信。
截至目前,您已发送 4 条短信。
截至目前,您已发送 5 条短信。
截至目前,您已发送 6 条短信。
截至目前,您已发送 7 条短信。
截至目前,您已发送 8 条短信。
截至目前,您已发送 9 条短信。
截至目前,您已发送 10 条短信。
剩余费用不足,请充话费!

9.3 until循环

9.3.1 until基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	until命令本质上与while循环一致,区别在于until走循环之前会对输入的值进行条件判断,如果不满足条件的话,才会进入到循环体中执行对应的语句,否则的话就退出循环。

until语法解析

场景:只要条件不满足,就一直循环下去
    until [ 条件判断 ]
    do
       执行语句
    done
注意:
    条件支持的样式 命令、[[ 字符串表达式 ]]、(( 数字表达式 ))
    true是一个特殊的条件,代表条件永远成立

简单实践

实践1-输出制定的范围数字

[root@localhost ~]# cat until_num_list.sh
#!/bin/bash
# 功能:until的输出5范围以内的数字

# 定制初始变量值
a=1

# 定制内容输出逻辑
until [ "${a}" -gt 5 ]
do
   echo -n "${a} "
   # 每输出一次数据,数据值+1
   a=$((a+1))
done
echo
脚本执行后效果
[root@localhost ~]# /bin/bash until_num_list.sh
1 2 3 4 5

实践2- 计算1+2+...+100 的结果

查看脚本内容
[root@localhost ~]# cat until_odd_num.sh
#!/bin/bash
# 功能:until统计数据之和

# 定制普通变量
all_sum=0
odd_sum=0

# 定制所有数据求和逻辑
i=1
until ((i>100))
do
  let all_sum+=i
  let i++
done

# 定制所有奇数求和逻辑
i=1
until ((i>100))
do
  let odd_sum+=i
  let i+=2
done

# 信息输出
echo -e "\e[31m所有数据之和: ${all_sum}\e[0m"
echo -e "\e[31m所有奇数之和: ${odd_sum}\e[0m"

脚本执行后效果
[root@localhost ~]# /bin/bash until_odd_num.sh
所有数据之和: 5050
所有奇数之和: 2500

9.3.2 嵌套案例

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

这里的嵌套实践,与while语句的嵌套实践基本一致,只不过组合的方式发生了一些变化。常见的组合样式如下:
until嵌套until语句
    until 循环条件
    do
    	until 循环条件语句
    done
until嵌套if语句
    until 循环条件
    do
    	if 条件控制语句
    done	

简单实践

until嵌套until语句实践1-输出99乘法表

查看脚本内容
[root@localhost ~]# cat until_nine_table.sh
#!/bin/bash
# 功能:until打印倒序99乘法表

# 定制打印99乘法表的业务逻辑
# 对第一个位置的数字进行循环
num1=9
until [ ${num1} -eq 0 ]
do
    # 对第二个位置的数字进行循环
    num2=1
    until [ ${num2} -gt ${num1} ]
    do
        # 信息输出,\t\c 的目的是删除后续信息,生成的内容是固定长度
        echo -e "\e[$[RANDOM%9+31]m${num1}x${num2}=$[num1*num2]\e[0m\t\c"
        num2=$[$num2+1]
    done
    num1=$[$num1-1]
    echo # 一个子循环一行内容
done	
脚本文件执行效果
[root@localhost ~]# /bin/bash until_nine_table.sh
9x1=9   9x2=18  9x3=27  9x4=36  9x5=45  9x6=54  9x7=63  9x8=72  9x9=81
8x1=8   8x2=16  8x3=24  8x4=32  8x5=40  8x6=48  8x7=56  8x8=64
7x1=7   7x2=14  7x3=21  7x4=28  7x5=35  7x6=42  7x7=49
6x1=6   6x2=12  6x3=18  6x4=24  6x5=30  6x6=36
5x1=5   5x2=10  5x3=15  5x4=20  5x5=25
4x1=4   4x2=8   4x3=12  4x4=16
3x1=3   3x2=6   3x3=9
2x1=2   2x2=4
1x1=1

until嵌套if语句实践2-手机发送短信1次/1毛,余额低于1毛提示无法发送请充值

[root@localhost ~]# cat until_mobile_bill.sh
#!/bin/bash
# 功能:until提示收集发短信

# 定制普通变量
read -p "> 请输入收集话费余额(元): " mobile_bill

# 定制普通变量
sms_num=0
bull_count=$[$mobile_bill * 10]
until [ $bull_count -lt 0 ]
do
    sms_num=$(($sms_num+1))
    if [ $bull_count -lt 1 ];then
        echo "剩余费用不足,请充话费!"
    else
        echo "截至目前,您已发送 ${sms_num} 条短信。"
    fi
    bull_count=$(($bull_count-1))
    sleep 0.1
done
脚本执行效果
[root@localhost ~]# /bin/bash until_mobile_bill.sh
> 请输入收集话费余额(元): 1
截至目前,您已发送 1 条短信。
截至目前,您已发送 2 条短信。
截至目前,您已发送 3 条短信。
截至目前,您已发送 4 条短信。
截至目前,您已发送 5 条短信。
截至目前,您已发送 6 条短信。
截至目前,您已发送 7 条短信。
截至目前,您已发送 8 条短信。
截至目前,您已发送 9 条短信。
截至目前,您已发送 10 条短信。
剩余费用不足,请充话费!

实践1-


9.4 循环控制

9.4.1 控制解析

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	所谓的流程控制,主要针对的是,当我们处于流程步骤执行的过程中,因为某些特殊的原因,不得不停止既定的操作进行步骤的调整,常见的临时调整场景如下:
continue控制
	- 满足条件的情况下,临时停止当前的循环,直接进入到下一循环
break控制
	- 满足条件的情况下,提前退出当前的循环
exit控制
	- 直接退出当前循环的程序
shift控制
	- 依次从循环列表中读取读取内容,并将读取的内容从列表中剔除

简单实践

exit简介

	exit在shell中是一个特殊的程序退出信号,不仅仅可以直接退出当前程序,还可以设定退出后的状态返回值,使用方式如下:
		exit num
注意:
	1 在脚本中遇到exit命令,脚本立即终止;终止退出状态取决于exit命令后面的数字
	2 如果exit后面无数字,终止退出状态取决于exit命令前面命令执行结果

实践1- 设定退出状态值

网段内主机地址的存活性探测
[root@192 ~]# ping -c1 -W1 10.0.0.12 &> /dev/null && echo '10.0.0.12 is up' || (echo '10.0.0.12 is unreachable'; exit 1)
10.0.0.12 is up
[root@192 ~]# ping -c1 -W1 10.0.0.13 &> /dev/null && echo '10.0.0.13 is up' || (echo '10.0.0.13 is unreachable'; exit 6)
10.0.0.13 is unreachable
[root@192 ~]# echo $?
6

服务器网址探测
[root@192 ~]# curl -s -o /dev/null baidu.com &> /dev/null && echo 'baidu.com is up' || (echo 'baidu.com is unreachable'; exit 7)
baidu.com is up
[root@192 ~]# curl -s -o /dev/null baidu.com1 &> /dev/null && echo 'baidu.com1 is up' || (echo 'baidu.com1 is unreachable'; exit 7)
baidu.com1 is unreachable
[root@192 ~]# echo $?
7

实践2-嵌套循环中exit退出程序

查看脚本内容
[root@localhost ~]# cat exit_multi_for.sh
#!/bin/bash
# 功能:exit退出脚本程序

# 外层循环遍历1-5
for var1 in {1..5}
do
   # 内层循环遍历a-d
   for var2 in {a..d}
   do
      # 判断退出条件,var1是2或者var2是c就退出内层循环
      if [ $var1 -eq 2 -o "$var2" == "c" ]
      then
         exit 111
      else
         echo "$var1 $var2"
      fi
   done
done
脚本执行效果
[root@192 ~]# /bin/bash exit_multi_for.sh
1 a
1 b
[root@192 ~]# echo $?
111
结果显示:
	一旦匹配到任何一个信息,就直接退出程序,而且状态码还是我们定制的。

9.4.2 break实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	break命令是在处理过程中终止循环的一种简单方法。可以使用break命令退出任何类型的循环,包括for、while、until等。
break主要有两种场景的表现样式:
	单循环场景下,break是终止循环
		- 仅有一层 while 、for、until等
	嵌套循环场景下,break是可以终止内层循环和外层循环。
		- 存在多层while、for、until嵌套等

语法格式

break语法格式:
	for 循环列表
	do
	    ...
	    break num
	done
	注意:
		单循环下,break就代表退出循环
		多循环下,break的num大于嵌套的层数,就代表退出循环

简单实践

实践1-break终止单层循环

查看脚本内容
[root@localhost ~]# cat break_single_while.sh
#!/bin/bash
# 功能:break退出单层循环
while true
do
    read -p "输入你的数字,最好在 1 ~ 5: " aNum
    case $aNum in
        1|2|3|4|5)
          echo "你的数字是 $aNum!"
        ;;
        *)
          echo "你选择的数字没在 1 ~ 5, 退出!"
          break
        ;;
    esac
done
[root@localhost ~]# /bin/bash break_single_while.sh
输入你的数字,最好在 1 ~ 5: 2
你的数字是 2!
输入你的数字,最好在 1 ~ 5: 5
你的数字是 5!
输入你的数字,最好在 1 ~ 5: 6
你选择的数字没在 1 ~ 5, 退出!

结果显示:
	一旦出发break,当前循环就终止了

实践2-多层循环下break退出内层循环

[root@localhost ~]# cat break_multi_in_while.sh
#!/bin/bash
# 功能:break退出内层循环

# 外层循环遍历1-5
for var1 in {1..5}
do
   # 内层循环遍历a-d
   for var2 in {a..d}
   do
      # 判断退出条件,var1是2或者var2是c就退出内层循环
      if [ $var1 -eq 2 -o "$var2" == "c" ]
      then
         break
      else
         echo "$var1 $var2"
      fi
   done
done
脚本执行效果
[root@localhost ~]# /bin/bash break_multi_in_while.sh
1 a
1 b
3 a
3 b
4 a
4 b
5 a
5 b
结果显示:
	一旦出发break,则匹配内容及其后面的信息就不再输出了

实践3-多层循环下break退出外层循环

[root@localhost ~]# cat break_multi_out_while.sh
#!/bin/bash
# 功能:break退出外层循环

# 外层循环遍历1-5
for var1 in {1..5}
do
   # 内层循环遍历a-d
   for var2 in {a..d}
   do
      # 判断退出条件,var1是2或者var2是c就退出内层循环
      if [ $var1 -eq 2 -o "$var2" == "c" ]
      then
         break 2
      else
         echo "$var1 $var2"
      fi
   done
done
脚本执行效果
[root@localhost ~]# /bin/bash break_multi_out_while.sh
1 a
1 b
结果显示:
	一旦匹配内层,则直接终止外层的循环

9.4.3 continue实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	continue命令是在处理过程中跳出循环的一种简单方法。可以使用continue命令跳出当前的循环直接进入到下一个循环,包括for、while、until等。
continue主要有两种场景的表现样式:
	单循环场景下,continue是跳出当前循环
		- 仅有一层 while 、for、until等
	嵌套循环场景下,continue是可以跳出内层循环和外层循环。
		- 存在多层while、for、until嵌套等

语法格式

continue语法格式:
	for 循环列表
	do
	    ...
	    continue num
	done
	注意:
		单循环下,continue就代表跳出当前循环
		多循环下,continue的num就代表要继续的循环级别

简单实践

实践1-continue跳出当前单层循环

查看脚本内容
[root@localhost ~]# cat continue_single_while.sh
#!/bin/bash
# 功能:continue退出单层循环
while true
do
    read -p "输入你的数字,最好在 1 ~ 5: " aNum
    case $aNum in
        1|2|3|4|5)
          echo "你的数字是 $aNum!"
        ;;
        *)
          echo "你选择的数字没在 1 ~ 5, 退出!"
          continue
        ;;
    esac
done
[root@localhost ~]# /bin/bash continue_single_while.sh
输入你的数字,最好在 1 ~ 5: 2
你的数字是 2!
输入你的数字,最好在 1 ~ 5: 6
你选择的数字没在 1 ~ 5, 退出!
输入你的数字,最好在 1 ~ 5: 1
你的数字是 1!
输入你的数字,最好在 1 ~ 5: ^C

结果显示:
	即使输出的数据不是我们想要的,也不会退出循环逻辑

实践2-多层循环下continue退出内层循环

[root@localhost ~]# cat continue_multi_in_while.sh
#!/bin/bash
# 功能:continue退出内层循环

# 外层循环遍历1-5
for var1 in {1..5}
do
   # 内层循环遍历a-d
   for var2 in {a..d}
   do
      # 判断退出条件,var1是2或者var2是c就退出内层循环
      if [ $var1 -eq 2 -o "$var2" == "c" ]
      then
         continue
      else
         echo "$var1 $var2"
      fi
   done
done
脚本执行效果
[root@localhost ~]# /bin/bash break_multi_in_while.sh
1 a
1 b
1 d
3 a
3 b
3 d
4 a
4 b
4 d
5 a
5 b
5 d

结果显示:
	满足条件的信息,都直接跳到下一循环,继续执行,可以看到内层的d信息输出了

实践3-多层循环下break退出外层循环

[root@localhost ~]# cat continue_multi_out_while.sh
#!/bin/bash
# 功能:continue退出外层循环

# 外层循环遍历1-5
for var1 in {1..5}
do
   # 内层循环遍历a-d
   for var2 in {a..d}
   do
      # 判断退出条件,var1是2或者var2是c就退出内层循环
      if [ $var1 -eq 2 -o "$var2" == "c" ]
      then
         continue 2
      else
         echo "$var1 $var2"
      fi
   done
done
脚本执行效果
[root@localhost ~]# /bin/bash continue_multi_out_while.sh
1 a
1 b
3 a
3 b
4 a
4 b
5 a
5 b
结果显示:
	满足条件的信息,直接将外层循环跳到下一循环,继续执行,可以看到外层的3-5信息输出了

9.4.4 shift实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	shift是一个特殊的循环控制命令,它的特点主要是依次从输入信息列表中获取相关的参数数据值,然后走循环。

语法解读

continue语法格式:
	for 循环列表
	do
	    ...
	    shift
	done
	注意:
		shift 从用户输入的信息中提取第一个位置的数据内容,每走一个循环,从下一个位置获取输入参数

简单实践

实践1-依次获取所有参数的内容

查看脚本效果
[root@localhost ~]# cat shift_get_args.sh
#!/bin/bash
#功能:shift依次从输入的参数列表中获取内容

# 定制一套循环逻辑,直到参数位置内容为空
until [ -z "$1" ] 
do
   echo "脚本的第1个位置参数内容是: $1, 当前脚本参数数量: $#"
   # 接收完第一个参数后,直接将起始值移至下一个位置
   shift
done
脚本执行效果
[root@localhost ~]# /bin/bash shift_get_args.sh aa bb cc dd ee
脚本的第1个位置参数内容是: aa, 当前脚本参数数量: 5
脚本的第1个位置参数内容是: bb, 当前脚本参数数量: 4
脚本的第1个位置参数内容是: cc, 当前脚本参数数量: 3
脚本的第1个位置参数内容是: dd, 当前脚本参数数量: 2
脚本的第1个位置参数内容是: ee, 当前脚本参数数量: 1
结果显示:
	每次从脚本的第一个位置获取的内容在逐渐变化中。

实践2-创建指定的系统用户

查看脚本效果
[root@localhost ~]# cat shift_add_user.sh
#!/bin/bash
# 功能:shift批量创建指定的用户
# 提示:
# /dev/random和/dev/urandom设备文件会生成随机数,第一个依赖系统中断

# 定制普通变量
user_file='/tmp/user.txt'

# 保证文件可用
[ -f ${user_file} ] && > ${user_file}

# 定制批量创建用户的业务逻辑
if [ $# -ne 0 ]
then
    # 注意: 这里用的是 -n,后面的$1两侧要有"",如果用until语句的话使用 -z表达式
    while [ -n "$1" ]
    do
        # 创建用户
        useradd $1
        # 生成密码
        password=$(head /dev/urandom | tr -dc '[:alnum:]' | head -c 8)
        # 为用户添加密码
        echo ${password} | passwd --stdin $1 > /dev/null 2>&1
        # 信息输出
        echo "用户: $1, 密码: ${password}" >> ${user_file}
        echo -e "\e[31m用户 $1 创建成功\e[0m"
        # 移动输入参数
        shift
    done
else
    echo -e "\e[31mUsage: /bin/bash $0 arg_list\e[0m"
fi
查看脚本执行效果
[root@localhost ~]# /bin/bash shift_add_user.sh
Usage: /bin/bash shift_add_user.sh arg_list
[root@localhost ~]# /bin/bash shift_add_user.sh zhangsan lisi wangwu zhaoliu
用户 zhangsan 创建成功
用户 lisi 创建成功
用户 wangwu 创建成功
用户 zhaoliu 创建成功
[root@localhost ~]# cat /tmp/user.txt
用户: zhangsan, 密码: 4m4njspn
用户: lisi, 密码: sFEFcecC
用户: wangwu, 密码: rsDmtkcQ
用户: zhaoliu, 密码: Ehsj2fZo

9.5 select条件控制

9.5.1 基础实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	select和其他循环控制不一样,它一般用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。
	虽然select本身就是循环,但是select的循环仅限于接收列表中的输入信息,所以在生产中,我们一般将Select 搭配 case来使用,从而实现更为复杂的菜单控制选项。



select 表达式是一种bash的扩展应用,动作包括:
    自动用1,2,3,4列出菜单 ( 没有echo指令,自动显示菜单)
    自动read输入选择 ( 没有 read指令,自动输入)
    赋值给变量  ( 没有赋值指令,自动输入数字后,赋值字符串给变量)

语法解读

select 变量名 in  input_list 
do 
    循环语句 
done
语法解读:
	1 select 从 input_list 获取相关的值,然后输出到一个菜单列表,元素格式如下:
		序号) 菜单条目
	2 用户输入信息的标识使用PS3 的值,默认值是#?。
		我们可以定制PS3环境变量
	3 如果用户输入菜单序号,则输出菜单内容
	4 select的退出方式有很多种
		强制退出:Ctrl+D 组合键
		普通退出:结合break方式

简单实践

实践1-语法实践

[root@localhost ~]# cat simple_select.sh
#!/bin/bash
# select语句的使用

# 定制select循环逻辑
echo "您喜欢的操作系统是?"
select osname in "HarmonyOS" "Linux" "Windows" "Ubuntu"
do   
    echo "您选择的操作系统是: $osname."
done
脚本执行效果
[root@localhost ~]# /bin/bash simple_select.sh
您喜欢的操作系统是?
1) HarmonyOS
2) Linux
3) Windows
4) Ubuntu
#? 5
您选择的操作系统是: .
#? 2
您选择的操作系统是: Linux.
#?						# 使用Ctrl + D 方式退出循环

实践2-定制信息+优雅退出

[root@localhost ~]# cat simple_select.sh
#!/bin/bash
# select语句的使用

# 定制普通环境变量
PS3='请选择菜单序号: '

# 定制select循环逻辑
echo "您喜欢的操作系统是?"
select osname in "HarmonyOS" "Linux" "Windows" "Ubuntu"
do   
    echo "您选择的操作系统是: $osname."
    break  
done
脚本执行效果
[root@localhost ~]# /bin/bash simple_select.sh
您喜欢的操作系统是?
1) HarmonyOS
2) Linux
3) Windows
4) Ubuntu
请选择菜单序号: 1
您选择的操作系统是: HarmonyOS.
[root@localhost ~]# 

9.5.2 案例实践

这一节,我们从 case实践、软件部署、小结 三个方面来学习。

case实践

实践3-整合case实践

查看脚本内容
[root@localhost ~]# cat simple_select_case.sh
#!/bin/bash
# select语句的使用

# 定制普通环境变量
PS3='请选择菜单序号: '

# 定制select循环逻辑
echo "您喜欢的操作系统是?"
select osname in "HarmonyOS" "Linux" "Windows" "Ubuntu"
do
    echo "-----------------------------"
    case $osname in
        "HarmonyOS")
            echo -e "\e[31m$osname 是华为推出的操作系统。\e[0m"
            break;;
        "Linux")
            echo -e "\e[31m$osname 是比较常用的操作系统。\e[0m"
            break;;
        "Windows")
            echo -e "\e[31m$osname 是一个桌面的操作系统。\e[0m"
            break;;
        "Ubuntu")
            echo -e "\e[31m$osname 是一个很好的操作系统。\e[0m"
            break;;
        *)
            echo -e "\e[31m您输入的是无效信息,再来一次!!!\e[0m"
            break;;
    esac  
done
[root@localhost ~]# /bin/bash simple_select_case.sh
您喜欢的操作系统是?
1) HarmonyOS
2) Linux
3) Windows
4) Ubuntu
请选择菜单序号: 1
-----------------------------
HarmonyOS 是华为推出的一款面向万物互联的全场景分布式操作系统。
[root@localhost ~]# /bin/bash simple_select_case.sh
您喜欢的操作系统是?
1) HarmonyOS
2) Linux
3) Windows
4) Ubuntu
请选择菜单序号: 5
-----------------------------
您输入的是无效信息,再来一次!!!

软件部署

select定制安装软件的界面功能

[root@localhost ~]# cat soft_install_select.sh
#!/bin/bash
# 功能:定制软件应用的部署功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通环境变量
PS3='请选择软件序号: '

# 定制数组变量
soft_name=(Nginx Apache Tomcat)
# 定制select循环逻辑
echo "您需要安装的Web软件是?"
select soft in ${soft_name[@]}
do
    echo "-----------------------------"
    case $soft in
        "Nginx")
            echo -e "\e[31m开始安装 $soft 软件环境...\e[0m"
            break;;
        "Apache")
            echo -e "\e[31m开始安装 $soft 软件环境...\e[0m"
            break;;
        "Tomcat")
            echo -e "\e[31m开始安装 $soft 软件环境...\e[0m"
            break;;
        *)
            echo -e "\e[31m您输入的是无效信息,再来一次!!!\e[0m"
            break;;
    esac  
done
脚本执行效果
[root@localhost ~]# /bin/bash soft_install_select.sh
您需要安装的Web软件是?
1) Nginx
2) Apache
3) Tomcat
请选择软件序号: 1
-----------------------------
开始安装 Nginx 软件环境...
[root@localhost ~]# /bin/bash soft_install_select.sh
您需要安装的Web软件是?
1) Nginx
2) Apache
3) Tomcat
请选择软件序号: 4
-----------------------------
您输入的是无效信息,再来一次!!!

10 流程控制

10.1 函数基础

10.1.1 基础知识

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

	在shell脚本的编写过程中,我们经常会遇到一些功能代码场景:多条命令组合在一起,实现一个特定的功能场景逻辑、一些命令在脚本内部的多个位置频繁出现。在这些场景的代码量往往不多,但是频繁使用的话,会导致脚本的整体逻辑脉络比较松散和框架散乱。
	所以我们需要一种脚本逻辑,不仅仅能够满足松散代码的功能目的,还能精简重复的代码。函数就是来满足这种场景的解决方案 -- 而函数,也是所谓的面向对象编程的一种表现样式。

函数

	所谓的函数,本质上就是一段能够满足特定功能的代码块。一旦定义好函数代码后,我们就可以在脚本的很多位置随意的使用。
	定义功能代码块的动作叫 函数定义,使用函数代码的动作叫 函数调用。
函数的优势:
	1. 代码模块化,调用方便,节省内存
    2. 代码模块化,代码量少,排错简单
    3. 代码模块化,可以改变代码的执行顺序

基本语法

定义函数:
	样式1:标准格式
    function 函数名{		
        函数体				
    }						
	样式2:简约格式
    函数名() {		
        函数体				
    }
注意:
	function 的作用和 () 的作用是一样的,都是定义一个函数。
	函数的名称是自定义的,而且在脚本范围内必须唯一。
	函数体内是普通的能够正常执行的命令,命令的执行流程符合顺序逻辑。
调用函数:			
	函数名					
注意:
	函数名出现在任何位置,就代表在该位置调用函数内代码块的执行。
	函数名一般在函数定义后调用,否则的话会发生报错。

简单实践

实践1-标准函数的实践

[root@localhost ~]# cat function_simple_test.sh
#!/bin/bash
# 功能:简单函数的定义和调用

# 定制一个函数,提示脚本的使用方式
function Usage {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}

# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    echo "您输入的脚本参数是1个"
else
    Usage
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_simple_test.sh
脚本的使用帮助信息: xxx
[root@localhost ~]# /bin/bash function_simple_test.sh aa
您输入的脚本参数是1个
[root@localhost ~]# /bin/bash function_simple_test.sh aa bb
脚本的使用帮助信息: xxx

实践2-变种函数的实践

[root@localhost ~]# cat function_simple_test2.sh
#!/bin/bash
# 功能:简单函数的定义和调用

# 定制一个函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}

# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    echo "您输入的脚本参数是1个"
else
    Usage
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_simple_test2.sh
脚本的使用帮助信息: xxx
[root@localhost ~]# /bin/bash function_simple_test2.sh aa
您输入的脚本参数是1个
[root@localhost ~]# /bin/bash function_simple_test2.sh aa bb
脚本的使用帮助信息: xxx

实践3-函数的调用顺序和名称唯一 实践

[root@localhost ~]# cat function_simple_test3.sh
#!/bin/bash
# 功能:简单函数的定义和调用

# 定制一个函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}
echo "第一次调用效果: "
Usage

# 定制同名的函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息-------: xxx\e[0m"
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    # 调用一个后面才会生成的函数
    func
else
    Usage
fi

# 定制一个函数
func() {
    echo "您输入的脚本参数是1个"
}

脚本执行效果
[root@localhost ~]# /bin/bash function_simple_test3.sh
第一次调用效果:
脚本的使用帮助信息: xxx
脚本的使用帮助信息-------: xxx
[root@localhost ~]# /bin/bash function_simple_test3.sh a
第一次调用效果:
脚本的使用帮助信息: xxx
function_simple_test3.sh:行18: func: 未找到命令

结果显示:
	函数名称重复的话,会导致同名函数被覆盖
	函数在没有定义前调用的话,会导致异常报错

10.1.2 函数退出

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	我们可以将函数代码块,看成shell脚本内部的小型脚本,所以说函数代码块也会有执行状态返回值。对于函数来说,它通常支持两种种状态返回值的样式。
样式1-默认的退出状态
	默认情况下,函数的退出状态是函数体内的最后一条命令的退出状态,可以通过 $? 来获取
样式2-return定制状态返回值
	在函数体内部,通过return定制状态返回值的内容
	注意:
		return的状态返回值必须尽快使用,否则会被其他return的值覆盖
		return的状态返回值必须在 0-255,否则失效

简单实践

实践1-默认退出状态

[root@localhost ~]# cat function_exit_status1.sh
#!/bin/bash
# 功能:函数默认状态返回值

# 定制成功运行的函数
ok_func() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}
# 定制一个运行失败的函数
err_func() {
    666666
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    err_func
    echo "错误函数的执行状态返回值: " $?
else
    ok_func
    echo "成功函数的执行状态返回值: " $?
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_exit_status1.sh
脚本的使用帮助信息: xxx
成功函数的执行状态返回值:  0
[root@localhost ~]# /bin/bash function_exit_status1.sh aa
function_exit_status1.sh:行10: 666666: 未找到命令
错误函数的执行状态返回值:  127
[root@localhost ~]# lll; echo $?
bash: lll: 未找到命令
127
结果显示:
	对于异常的函数来说,默认的状态返回值有安全隐患

实践2-return定制函数的返回值实践

[root@localhost ~]# cat function_exit_status2.sh
#!/bin/bash
# 功能:return定制函数状态返回值

# 定制成功运行的函数
ok_func() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
    # 定制超范围的状态返回值
    return 666
}
# 定制一个运行失败的函数
err_func() {
    666666
    # 定制状态返回值
    return 222
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    err_func
    echo "错误函数的执行状态返回值: " $?
else
    ok_func
    echo "成功函数的执行状态返回值: " $?
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_exit_status2.sh
脚本的使用帮助信息: xxx
成功函数的执行状态返回值:  154
[root@localhost ~]# /bin/bash function_exit_status2.sh aa
function_exit_status2.sh:行12: 666666: 未找到命令
错误函数的执行状态返回值:  222
结果显示:
	return的状态返回值范围必须满足要求

10.1.3 进阶实践

这一节,我们从 传参函数、脚本传参、小结 三个方面来学习。

传参函数

简介

	简单的函数定义和调用的实践,我们只能实现固定内容的输出,不具有灵活性。其实函数作为shell脚本内部的小脚本也支持脚本传参的一系列能力。基本语法效果如下
定义函数:
    函数名() {		
        函数体	${变量名}			
    }
注意:
	函数体内通过 ${变量名} 来实现函数体的功能通用性。
调用函数:			
	函数名 参数			
注意:
	函数在调用的时候,接收一些参数并传输到函数体内部。

实践1-传参函数实践

查看脚本内容
[root@localhost ~]# cat function_arg_input.sh
#!/bin/bash
# 功能:传参函数定义和调用

# 定制数据运算的函数
add_func() {
    echo $(( $1 + $2 ))
}
sub_func() {
    echo $(( $1 - $2 ))
}
mul_func() {
    echo $(( $1 * $2 ))
}
div_func() {
    echo $(( $1 / $2 ))
}

echo -n "4+3="; add_func 4 3
echo -n "4-3="; sub_func 4 3
echo -n "4*3="; mul_func 4 3
echo -n "4/3="; div_func 4 3
脚本执行效果
[root@localhost ~]# /bin/bash function_arg_input.sh
4+3=7
4-3=1
4*3=12
4/3=1

脚本传参

简介

	传参函数定义和调用的实践,实现了函数层面的灵活性,但是它受到函数调用本身的参数限制。往往这些参数我们需要在脚本执行的时候传递进去,从而实现脚本功能的灵活性。
	基本语法效果如下
定义函数:
    函数名() {		
        函数体	${函数参数}			
    }
    
调用函数:
	函数名	${脚本参数}

脚本执行:
	/bin/bash /path/to/scripts.sh arg
	
注意:
	由于脚本内部调用脚本参数和函数体内调用函数参数都遵循位置变量的使用
	所以,一般情况下,我们会借助于临时变量的方式接收各自的参数,从而避免引起误会

实践1-脚本传参函数实践

查看脚本内容
[root@localhost ~]# cat function_arg_scripts.sh
#!/bin/bash
# 功能:脚本传参函数调用

# 定制数据运算的函数
add_func() {
    echo $(( $1 + $2 ))
}
sub_func() {
    echo $(( $1 - $2 ))
}
mul_func() {
    echo $(( $1 * $2 ))
}
div_func() {
    echo $(( $1 / $2 ))
}

[ $# -ne 2  ] && echo "必须传递两个数字参数" && exit
echo -n "$1+$2="; add_func $1 $2
echo -n "$1-$2="; sub_func $1 $2
echo -n "$1*$2="; mul_func $1 $2
echo -n "$1/$2="; div_func $1 $2

注意:
	这种简单的脚本传参函数调用,导致大量的位置参数,容易引起混乱,需要改造
脚本执行效果
[root@localhost ~]# /bin/bash function_arg_scripts.sh
必须传递两个数字参数
[root@localhost ~]# /bin/bash function_arg_scripts.sh 5 4
5+4=9
5-4=1
5*4=20
5/4=1

实践2-脚本传参函数进阶实践

查看脚本内容
[root@localhost ~]# cat function_arg_scripts2.sh
#!/bin/bash
# 功能:传参函数定义和调用

# 接收脚本传参
arg1=$1
arg2=$2
# 定制数据运算的函数
add_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} + ${num2} ))
}
sub_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} - ${num2} ))
}
mul_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} * ${num2} ))
}
div_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} / ${num2} ))
}

[ $# -ne 2  ] && echo "必须传递两个数字参数" && exit
echo -n "${arg1}+${arg2}="; add_func ${arg1} ${arg2}
echo -n "${arg1}-${arg2}="; sub_func ${arg1} ${arg2}
echo -n "${arg1}*${arg2}="; mul_func ${arg1} ${arg2}
echo -n "${arg1}/${arg2}="; div_func ${arg1} ${arg2}
脚本执行效果
[root@localhost ~]# /bin/bash function_arg_scripts2.sh
必须传递两个数字参数
[root@localhost ~]# /bin/bash function_arg_scripts2.sh 7 5
7+5=12
7-5=2
7*5=35
7/5=1

10.1.4 综合案例

这一节,我们从 信息采集、环境部署、小结 三个方面来学习。

信息采集

脚本实践-采集系统负载信息

查看脚本内容
[root@localhost ~]# cat function_systemctl_load.sh
#!/bin/bash
# 功能:采集系统负载信息
# 版本:v0.3
# 作者:书记
# 联系:www.superopsmsb.com

# 定制资源类型
resource_type=(CPU MEM)


# 定制cpu信息输出函数
cpu_info() {
	cpu_attribute=(1 5 15)
	cpu_load=($(uptime | tr -s " " | cut -d " " -f 11-13 | tr "," " "))
    echo -e "\e[31m\t系统CPU负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!cpu_attribute[@]}
    do
        echo "CPU ${cpu_attribute[$index]} min平均负载为: ${cpu_load[$index]}" 
    done
    echo -e "================================\e[0m"	
}
# 获取内存相关属性信息
mem_info() {
	free_attribute=(总量 使用 空闲)
	free_info=($(free -m | grep Mem | tr -s " " | cut -d " " -f 2-4))
    echo -e "\e[31m\t系统内存负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!free_attribute[@]}
    do
        echo "内存 ${free_attribute[$index]} 信息为: ${free_info[$index]} M" 
    done
    echo -e "================================\e[0m"
}

# 服务的操作提示
echo -e "\e[31m---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------"'\033[0m'
# 选择服务操作类型
while true
do
    read -p "> 请输入要查看的资源信息类型: " resource_id
    echo
    case ${resource_type[$resource_id-1]} in
       "CPU")
            cpu_info;;
        "MEM")
            mem_info;;
        *)
            echo -e "\e[31m\t请输入有效的信息类型\e[0m";;
    esac
done
脚本使用效果
[root@localhost ~]# /bin/bash function_systemctl_load.sh
---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------
> 请输入要查看的资源信息类型: 1

        系统CPU负载信息
================================
CPU 1 min平均负载为: 0.00
CPU 5 min平均负载为: 0.01
CPU 15 min平均负载为: 0.05
================================
> 请输入要查看的资源信息类型: 2

        系统内存负载信息
================================
内存 总量 信息为: 3770 M
内存 使用 信息为: 237 M
内存 空闲 信息为: 3290 M
================================
> 请输入要查看的资源信息类型: 3

        请输入有效的信息类型
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

环境部署

需求

定制kubernetes环境部署管理的功能脚本改造
	1 功能函数实现
	2 扩充while循环执行功能
	3 增加q退出环境功能

脚本内容

查看脚本内容
[root@localhost ~]# cat function_kubernetes_manager.sh
#!/bin/bash
# 功能:定制kubernetes环境部署管理的功能
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
env_array=(base ha k8s_base master slave)

# 监控平台的信息提示
menu(){
    echo -e "\e[31m     欢迎使用kubernetes部署平台"
    echo -e "\e[32m-----------请选择部署阶段-----------"
    echo -e " 1: 基础环境部署"
    echo -e " 2: 高可用环境部署"
    echo -e " 3: kubernetes基础环境部署"
    echo -e " 4: 主角色环境部署"
    echo -e " 5: 从角色环境部署"
    echo -e " q: 退出"
    echo -e "----------------------------------\033[0m"
}
# 定制基础环境
os_base_func(){
    echo -e "\e[31m开始基础环境部署..."
    echo "1 执行跨主机免密码操作"
    echo "2 执行时间同步操作"
    echo "3 执行内核配置操作"
    echo -e "4 执行容器私有仓库部署操作\e[0m"
}

# 定制高可用环境
ha_func(){
    echo -e "\e[31高可用环境部署..."
    echo "1 执行高可用环境部署操作"
    echo -e "2 执行负载均衡环境部署操作\e[0m"
}

# 定制k8s基础环境
k8s_base_func(){
    echo -e "\e[31mkubernetes基础环境部署..."
    echo "1 执行证书管理操作"
    echo "2 执行etcd环境部署操作"
    echo -e "3 执行集群证书配置操作\e[0m"
}

# 定制主角色环境
master_func(){
    echo -e "\e[31m主角色环境部署..."
    echo "1 执行apiserver环境部署操作"
    echo "2 执行scheduler环境部署操作"
    echo "3 执行controller环境部署操作"
    echo "4 执行认证配置操作"
    echo "5 执行容器环境部署操作"
    echo "6 执行kubelet环境部署操作"
    echo -e "7 执行kube-proxy环境部署\e[0m"
}
# 定制从角色环境
slave_func(){
    echo -e "\e[31m主角色环境部署..."
    echo "1 执行容器环境部署操作"
    echo "2 执行kubelet环境部署操作"
    echo -e "3 执行kube-proxy环境部署\e[0m"
}
# 定制错误提示信息
usage_func(){
    echo -e "\e[31m请输入有效的功能场景标识\e[0m"
}


# 脚本内容的判断
while true
do
    # 定制业务逻辑
    menu
    read -p "请输入功能标识: " env_id
    if [ ${env_id} == "q" ];then
        exit
    else
        # 执行配套业务逻辑
        case "${env_array[$env_id-1]}" in
            "base")
                os_base_func;;
            "ha")
                ha_func;;
            "k8s_base")
                k8s_base_func;;
            "master")
                master_func;;
            "slave")
                slave_func;;
            *)
                usage_func;;
        esac
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_kubernetes_manager.sh
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
 q: 退出
----------------------------------
请输入功能标识: 6
请输入有效的功能场景标识
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
 q: 退出
----------------------------------
请输入功能标识: 5
主角色环境部署...
1 执行容器环境部署操作
2 执行kubelet环境部署操作
3 执行kube-proxy环境部署
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
 q: 退出
----------------------------------
请输入功能标识: q

10.2 函数进阶

10.2.1 函数变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	在函数作用范围中,我们可以通过大量的变量来实现特定数据的临时存储,不仅仅可以实现脚本层面的变量可视化,还可以借助于变量本身的全局和本地的特点实现更加强大的功能场景。

变量类型

全局变量:
	默认情况下,脚本中的普通变量就是全局变量,作用范围是shell脚本的所有地方,在函数内部也可以正常使用
	而且函数内可以可以修改脚本级别的全局变量
局部变量:
	我们可以通过local语法,将变量的作用范围限制在一段代码块范围中。
	注意:脚本内无法使用local语法,仅限于函数体内

简单实践

实践1-全局变量实践

查看脚本内容
[root@localhost ~]# cat function_env_test.sh
#!/bin/bash
# 功能:全局变量实践

# 定制普通的全局变量
message="helloworld"

# 定制一个函数,提示脚本的使用方式
function Usage {
    echo "直接调用脚本的message: ${message}"
    message="function-message"
    echo "函数体重置后的message: ${message}"
}

# 定制脚本使用逻辑
while true
do
    read -p "查看变量的方式[ 1-脚本内 | 2-函数内 ]:" type
    if [ ${type} == "1" ];then
        # 直接在脚本环境使用全局变量
        echo ${message}
    elif [ ${type} == "2" ];then
        # 函数内部使用全局变量
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_env_test.sh
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:2
直接调用脚本的message: helloworld
函数体重置后的message: function-message
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
function-message						# 结果显示,函数体内的变量生效了
查看变量的方式[ 1-脚本内 | 2-函数内 ]:^C
[root@localhost ~]#
结果显示:
	对于脚本的普通变量,函数内外都可以正常使用,而且函数内可以直接修改脚本级别的普通变量

实践2-本地变量实践

查看脚本内容
[root@localhost ~]# cat function_env_test2.sh
#!/bin/bash
# 功能:local定制函数级别的局部变量实践

# 定制普通变量
message="helloworld"
local local_env="local"

# 定制一个函数,提示脚本的使用方式
function Usage {
    echo "直接调用脚本的变量: ${message}-${local_env}"
    local message="function-message"
    echo "函数体重置后的变量: ${message}-${local_env}"
}

# 定制脚本使用逻辑
while true
do
    read -p "查看变量的方式[ 1-脚本内 | 2-函数内 ]:" type
    if [ ${type} == "1" ];then
        # 直接在脚本环境使用普通变量
        echo ${message}
    elif [ ${type} == "2" ];then
        # 函数内部使用普通变量
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_env_test2.sh
function_env_test2.sh: 第 6 行:local: 只能在函数中使用
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:2
直接调用脚本的变量: helloworld-
函数体重置后的变量: function-message-
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:^C
[root@localhost ~]#
结果显示:
	local仅限于函数中使用,函数外部无法使用
	经过local限制后的环境变量,无法被函数体外进行使用

10.2.2 数组传递

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	我们知道在shell脚本中,数组可以帮助我们做很多灵活的事情,尤其是数据的归类存储。而数组在函数的使用过程中,还是受到了一定程度的限制。这些限制主要体现在以下几个方面;
限制1:
	以变量的方式传递数组给函数,函数内部无法正常使用
限制2:
	我们只能先解析所有数组元素,然后再传递给函数,接着在函数体内部重新组合
限制3:
	函数内部以echo方式输出数组所有元素,然后再函数外部重新组合为数组

简单实践

实践1-函数无法正常接收数据元素

查看脚本内容
[root@localhost ~]# cat function_array_input.sh
#!/bin/bash
# 功能: 函数接收数组元素

# 定制功能函数
func_array(){
    echo "函数内接收的参数: $@"
}

# 定制数组变量
myarray=(aa bb cc dd ee)
echo "myarray数组的所有元素有: ${myarray[@]}"

# 数组传递给函数
func_array $myarray

脚本执行效果
[root@localhost ~]# /bin/bash function_array_input.sh
myarray数组的所有元素有: aa bb cc dd ee
函数内接收的参数: aa

结果显示:
	虽然我们传递数组给函数,但是函数无法正常接收数组

实践2-函数接收数组

查看脚本内容
[root@localhost ~]# cat function_array_input2.sh
#!/bin/bash
# 功能: 函数接收数组元素

# 定制功能函数
func_array(){
    echo "函数内接收的参数: $@"
    func_arr=($(echo $@))
    echo "函数内func_arr的数组元素有: ${func_arr[@]}"
}

# 定制数组变量
myarray=(aa bb cc dd ee)
echo "myarray数组的所有元素有: ${myarray[@]}"

# 数组解析后,将所有元素传递给函数
func_array ${myarray[@]}
脚本执行效果
[root@localhost ~]# /bin/bash function_array_input2.sh
myarray数组的所有元素有: aa bb cc dd ee
函数内接收的参数: aa bb cc dd ee
函数内func_arr的数组元素有: aa bb cc dd ee

实践3-脚本接收函数内数组

查看脚本内容
[root@localhost ~]# cat function_array_output.sh
#!/bin/bash
# 功能: 脚本接收函数内数组元素

# 定制功能函数
func_array(){
    # 函数体内构造新数组
    func_arr=($(echo $@))
    # 生成新数组
    for (( i=0; i<${#func_arr[@]}; i++ ))
    do
        newarray[$i]=$[ ${func_arr[$i]} * 3 ]
    done
    # 逐个返回数组元素
    echo ${newarray[@]}
}

# 定制数组变量
myarray=(1 2 3 4 5)
echo "myarray数组的所有元素有: ${myarray[@]}"

# 接收函数体返回的数组内容
result=($(func_array ${myarray[@]}))
echo "函数返回的result数组元素:${result[@]}"
脚本执行效果
[root@localhost ~]# /bin/bash function_array_output.sh
myarray数组的所有元素有: 1 2 3 4 5
函数返回的result数组元素:3 6 9 12 15

11 流程控制

11.1 函数进阶

11.1.1 函数嵌套

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	所谓的函数嵌套,主要是在函数间或者文件间相互使用的一种方式。它主要有三种样式:
样式1:函数间调用
	- 函数体内部调用其他的函数名
样式2:文件间调用
	- 函数体内部调用另外一个文件的函数名
	- 需要额外做一步文件source的加载动作
	注意:我们将专门提供函数的文件称为 -- 函数库
样式3:函数自调用
	- 函数体内部调用自己的函数名,将复杂的逻辑简单化

简单实践

函数间调用实践1-图形信息打印

	按照信息提示,分别打印 三角形 和 等腰梯形
        *                  *****
       * *                *******
      * * *              *********
     * * * *            ***********
    * * * * *          *************
[root@localhost ~]# cat function_drawn_graph.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

graph_type=(三角形 梯形)
# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------查看可以绘制的图形---------------"
    echo -e " 1: 三角形  2: 梯形"
    echo -e "-------------------------------------------\033[0m"
}

# 定制打印左侧空格效果
left_bland_func(){
   layer_num="$1"
   sub_num="$2"
   for m in $(seq $[${layer_num}-${sub_num}]);do
        echo -n " "
   done
}

# 打印图形的核心内容部分
kernel_character_func(){
   char_num="$1"
   char_mark="$2"
   for j in $(seq ${char_num});do
       echo -n "${char_mark}"
   done
}
# 定制打印三角形的函数
triangle_func(){
    # 接收函数传参
    layer_num=$1
    # 定制打印n层的三角形
    for i in $(seq 1 ${layer_num});do
       # 定制打印三角形左侧的空格效果
       left_bland_func ${layer_num} $i
       # 定制打印三角形核心部分
       kernel_character_func $i "* "
       # 打印完每行就换行
       echo
   done
}
# 定制梯形的功能函数
trapezium_func(){
    print_num=${layer_num}
    for i in $(seq 1 ${layer_num});do
        # 定制打印梯形左侧的空格效果
        left_bland_func ${layer_num} $i
        # 定制打印梯形核心部分
        kernel_character_func $print_num "*"
        let print_num+=2
        echo
    done
}
# 选择服务操作类型
while true;do
    menu
    read -p "> 请输入要查看的资源信息类型: " graph_id
    case ${graph_type[$graph_id-1]} in
        "三角形")
            read -p "> 请输入三角形绘制的层数: " layer_num
            triangle_func ${layer_num}
            ;;
        "梯形")
            read -p "> 请输入梯形绘制的层数: " layer_num
            # 定制打印n层的梯形
            trapezium_func ${layer_num}
            ;;
        *)
            echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
    esac
done
脚本执行后效果
[root@localhost ~]# /bin/bash function_drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 2
> 请输入梯形绘制的层数: 5
    *****
   *******
  *********
 ***********
*************
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

文件间调用实践2-拆分function_drawn_graph.sh脚本

需求:拆分绘图脚本文件
	1 将脚本文件中的功能逻辑函数拆分出来以单独的文件存在
	2 脚本文件保留核心逻辑功能
创建功能函数库文件目录
[root@localhost ~]# mkdir lib

查看库文件内容
[root@localhost ~]# cat lib/drawn_func.sh
#!/bin/bash
# 功能:打印相关图形功能函数库
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------查看可以绘制的图形---------------"
    echo -e " 1: 三角形  2: 梯形"
    echo -e "-------------------------------------------\033[0m"
}

# 定制打印三角形左侧空格效果
left_bland_func(){
   layer_num="$1"
   sub_num="$2"
   for m in $(seq $[${layer_num}-${sub_num}]);do
        echo -n " "
   done
}

# 打印图形的核心内容部分
kernel_character_func(){
   char_num="$1"
   char_mark="$2"
   for j in $(seq ${char_num});do
       echo -n "${char_mark}"
   done
}
# 定制打印三角形的函数
triangle_func(){
    # 接收函数传参
    layer_num=$1
    # 定制打印n层的三角形
    for i in $(seq 1 ${layer_num});do
       # 定制打印三角形左侧的空格效果
       left_bland_func ${layer_num} $i
       # 定制打印三角形核心部分
       kernel_character_func $i "* "
       # 打印完每行就换行
       echo
   done
}
# 定制梯形的功能函数
trapezium_func(){
    print_num=${layer_num}
    for i in $(seq 1 ${layer_num});do
        # 定制打印梯形左侧的空格效果
        left_bland_func ${layer_num} $i
        # 定制打印梯形核心部分
        kernel_character_func $print_num "*"
        let print_num+=2
        echo
    done
}
查看脚本框架文件
[root@localhost ~]# cat function_drawn_graph-lib.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.3
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
graph_type=(三角形 梯形)

# 加载功能函数库文件
source ./lib/drawn_func.sh

# 选择服务操作类型
while true;do
    menu
    read -p "> 请输入要查看的资源信息类型: " graph_id
    case ${graph_type[$graph_id-1]} in
        "三角形")
            read -p "> 请输入三角形绘制的层数: " layer_num
            triangle_func ${layer_num}
            ;;
        "梯形")
            read -p "> 请输入梯形绘制的层数: " layer_num
            # 定制打印n层的梯形
            trapezium_func ${layer_num}
            ;;
        *)
            echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
    esac
done
脚本执行效果
[root@localhost ~]# /bin/bash function_drawn_graph-lib.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

11.1.2 函数自调用

这一节,我们从 简单实践、案例实践、小结 三个方面来学习。

简单实践

简介

	函数自调用也称函数递归,说白了就是 函数调用自身,实现数据递归能力的实现

实践-函数自调用

需求: 实现数学阶乘的实践
	示例:5的阶乘
		 完整格式:5! = 1 * 2 * 3 * 4 * 5 = 120
		 简写格式:5! = 5 * (1 * 2 * 3 * 4) = 5 * 4!
	公式: x! = x * (x-1)!
查看脚本内容
[root@localhost ~]# cat function_func_test1.sh
#!/bin/bash
# 功能:函数自调用实践

# 定制功能函数框架
self_func(){
    # 接收一个参数
    num=$1
    if [ ${num} -eq 1 ];then
        echo 1
    else
        # 定制一个临时本地变量,获取递减后的值
        local temp=$[ ${num} - 1 ]
        # 使用函数自调用方式获取内容
        local result=$(self_func $temp)
        # 格式化输出信息
        echo $[ $result * ${num} ]
    fi
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的阶乘:" value
    result=$(self_func ${value})
    echo "${value}的阶乘是: ${result}"
done
脚本执行效果
[root@localhost ~]# /bin/bash function_func_test1.sh
请输入一个您要查询的阶乘:5
5的阶乘是: 120
请输入一个您要查询的阶乘:6
6的阶乘是: 720
请输入一个您要查询的阶乘:7
7的阶乘是: 5040
请输入一个您要查询的阶乘:^C
[root@localhost ~]#

案例实践

实践1-遍历制定目录下的所有文件

准备工作
[root@localhost ~]# mkdir -p dir/{softs/{nginx,tomcat},logs,server/{java,python}}
[root@localhost ~]# touch dir/softs/{nginx/nginx.conf,tomcat/server.xml}
[root@localhost ~]# touch dir/logs/user{1..3}.log
[root@localhost ~]# touch dir/server/{java/java.jar,python/python.py}
[root@localhost ~]# tree dir/
dir/
├── logs
│   ├── user1.log
│   ├── user2.log
│   └── user3.log
├── server
│   ├── java
│   │   └── java.jar
│   └── python
│       └── python.py
└── softs
    ├── nginx
    │   └── nginx.conf
    └── tomcat
        └── server.xml

7 directories, 7 files
[root@localhost ~]# cat function_scan_dir.sh
#!/bin/bash
# 功能:扫描目录下所有文件
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制功能函数框架
# 定制目录扫描功能函数
scan_dir() {
    # 定制临时局部功能变量
	# cur_dir 当前目录 workdir 工作目录
    local cur_dir workdir
	
	# 接收要检查的目录,进入到目录中
    workdir=$1
    cd ${workdir}
	
	# 对工作目录进行简单判断,根目录没有父目录
    if [ ${workdir} = "/" ]
    then
        cur_dir=""
    else
        cur_dir=$(pwd)
    fi
    
	# 查看当前目录下的文件列表
    for item in $(ls ${cur_dir})
    do
        # 如果文件是目录,则继续查看目录下文件
        if test -d ${item};then
            cd ${item}
            scandir ${cur_dir}/${item}
            cd ..
        # 如果文件是普通文件,则输出信息即可
        else
            echo ${cur_dir}/${item}
        fi
    done
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的目录:" value
    if [ -d ${value} ]
    then
        scandir ${value}
    else
        echo "您输入的不是目录,请重新输入!"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_scan_dir.sh
请输入一个您要查询的目录:dir
/root/dir/logs/user1.log
/root/dir/logs/user2.log
/root/dir/logs/user3.log
/root/dir/server/java/java.jar
/root/dir/server/python/python.py
/root/dir/softs/nginx/nginx.conf
/root/dir/softs/tomcat/server.xml
请输入一个您要查询的目录:^C
[root@localhost ~]#
结果显示:
	该脚本达到了我们需要的目录遍历效果

11.1.3 综合练习

这一节,我们从 案例解读、脚本实践、小结 三个方面来学习。

案例解读

案例需求

使用shell脚本绘制一个杨辉三角

案例解读

1、每行数字左右对称,从1开始变大,然后变小为1。   
2、第n行的数字个数为n个,所有数字和为 2^(n-1)。  
3、每个数字等于上一行的左右临近两个数字之和。
4、第n行的数字依次为 1、1×(n-1)、1×(n-1)×(n-2)/2、1×(n-1)×(n-2)/2×(n-3)/3 ...   
...

脚本实践

脚本实践

查看脚本内容
[root@localhost ~]# cat yanghui_triangle.sh
#!/bin/bash
# 功能:shell定制杨辉三角功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 判断输入是否为整数
check_int(){
    # 设定数据标识
    flag=true
    read -p "请输入一个数据值: " layer_num
    # 通过在循环内部进行数据操作判断是否是数据
    while $flag;do
      expr $layer_num + 0 > /dev/null 2>&1
      if [ $? -eq 0 ]
      then
          flag=false
      else
          read -p "请输入一个数据值: " layer_num
      fi
    done
}

# 定制一个数组
declare -a num_array

check_int
# 定制杨辉三角的行数变量 row
for(( row=1; row<=layer_num; row++ ))
do
   #打印杨辉三角的左侧空白
   for k in $(seq $[$layer_num - $row])
   do
       echo -n "    "
   done
   # 定制每行的数据获取
   for(( col=1; col<=row; col++ ))
   do
       # 第n行的第1个和第n行的第n个数字为1
       if [ $col -eq 1  -o $row -eq $col ]
       then
         # 设定每行的两个边界数字为1
         num_array[$row$col]=1
       else
         # 获取上一行的两个临近数据
         let row_up=row-1  # 获取上一行的数据
         let col_up=col-1  # 获取上一行的临近数据
         # 获取当前行的数据值为 上一行临近数据的数据和
         let num_array[$row$col]=${num_array[$row_up$col_up]}+${num_array[${row_up}${col}]}
       fi
   done
   # 打印每行的数据
   for(( col=1; col<=row; col++ ))
   do
       printf "%-8s" ${num_array[$row$col]}
   done
   echo
done
脚本执行效果
[root@localhost ]# /bin/bash yanghui_triangle.sh
请输入一个数据值: 8
                            1
                        1       1
                    1       2       1
                1       3       3       1
            1       4       6       4       1
        1       5       10      10      5       1
    1       6       15      20      15      6       1
1       7       21      35      35      21      7       1

函数嵌套改造

脚本改造后内容
[root@localhost ~]# cat yanghui_triangle.sh
#!/bin/bash
# 功能:shell定制杨辉三角功能
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制一个数组
declare -a num_array

# 判断输入是否为整数
check_int(){
    # 设定数据标识
    flag=true
    read -p "请输入一个数据值(q退出): " layer_num
    [ $layer_num == "q" ] && exit
    # 通过在循环内部进行数据操作判断是否是数据
    while $flag;do
      expr $layer_num + 0 > /dev/null 2>&1
      if [ $? -eq 0 ]
      then
          flag=false
      else
          read -p "请输入一个数据值: " layer_num
      fi
    done
}

# 定制左侧空格打印逻辑
left_blank_func(){
   # 获取参数值
   layer_num=$1
   row=$2
   # 空格打印逻辑
   for k in $(seq $[$layer_num - $row])
   do
       echo -n "    "
   done
}

# 获取每行的数据值
col_num_count(){
   # 获取参数值
   row=$1
   # 数据获取逻辑   
   for(( col=1; col<=row; col++ ))
   do
       # 第n行的第1个和第n行的第n个数字为1
       if [ $col -eq 1  -o $row -eq $col ]
       then
         # 设定每行的两个边界数字为1
         num_array[$row$col]=1
       else
         # 获取上一行的两个临近数据
         let row_up=row-1  # 获取上一行的数据
         let col_up=col-1  # 获取上一行的临近数据
         # 获取当前行的数据值为 上一行临近数据的数据和
         let num_array[$row$col]=${num_array[$row_up$col_up]}+${num_array[${row_up}${col}]}
       fi
   done
}
# 每行数据打印逻辑
col_num_print(){
   # 获取参数值
   row=$1
   # 数据打印逻辑
   for(( col=1; col<=row; col++ ))
   do
       printf "%-8s" ${num_array[$row$col]}
   done
   echo
}
while true
do
  check_int
  # 定制杨辉三角的行数变量 raw
  for(( row=1; row<=layer_num; row++ ))
  do
     #打印杨辉三角的左侧空白
     left_blank_func $layer_num $row
     # 获取数据的值
     col_num_count $row
     # 打印每行的所有数据
     col_num_print $row
  done
done
[root@localhost ~]# /bin/bash yanghui_triangle.sh
请输入一个数据值(q退出): 5
                1
            1       1
        1       2       1
    1       3       3       1
1       4       6       4       1
请输入一个数据值(q退出): q
[root@localhost ~]#

小结


12 脚本自动化

12.1 脚本信号

12.1.1 信号基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	当我们在构建一些更高级的脚本的时候,就会涉及到如何在linux系统上来更好的运行和控制它们,到目前为止,我们运行脚本的方式都是以实时的模式,在命令行来运行它。但是这并不是脚本唯一的运行方式,我们可以在linux系统中以更丰富的方式来运行它们,甚至在脚本遇到不可查的异常中止时候,以关闭linux终端界面的方式终止脚本。
	这些能力都是基于信号的机制来实现了

信号

	linux使用信号与系统上运行的进程进行通信,想要对shell的脚本控制,只需要传递相关信号给shell脚本即可。
信号 描述 信号 描述
1 SIGHUP 挂起进程 15 SIGTERM 优雅的终止进程
2 SIGINT 终止进程 17 SIGSTOP 无条件停止进程,不终止进程
3 SIGQUIT 停止进程 18 SIGTSTP 停止或暂停进程,不终止进程
9 SIGKILL 无条件终止进程 19 SIGCONT 继续运行停止的进程
    默认情况下,bash shell会忽略收到的任何SIGQUIT(3)和SIGTERM(15)信号(正因为这样交互式shell才不会被意外终止)。但是bash shell会处理收到的SIGHUP(1)和SIGINT(2)信号。

    如果bash shell收到SIGHUP信号,它会退出。但在退出之前,它会将信号传给shell启动的所有进程(比如shell脚本)。通过SIGINT信号,可以中断shell,Linux内核停止将CPU的处理时间分配给shell,当这种情况发生时,shell会将SIGINT信号传给shell启动的所有进程。

生成信号

终止进程:
	ctrl+c,
暂停进程:
	ctrl+z,停止的进程继续保留在内存中,并能从停止的位置继续运行
恢复进程:
	jobs查看运行任务,fg num 重新执行
杀死进程:
	kill -9 pid

简单实践

实践1-终止进程

[root@localhost ~]# sleep 1000
^C
[root@localhost ~]#

实践2-挂起进程

[root@localhost ~]# sleep 1000
^Z
[1]+  已停止               sleep 1000
[root@localhost ~]# ps aux  | grep sleep
root      39067  0.0  0.0 108052   360 pts/0    T    17:28   0:00 sleep 1000

实践3-恢复进程

查看所有挂起进程
[root@localhost ~]# jobs
[1]+  已停止               sleep 1000

恢复挂起进程的id
[root@localhost ~]# fg 1
sleep 1000
^C
[root@localhost ~]#

实践4-杀死进程

后台执行命令
[root@localhost ~]# sleep 1000 &
[1] 39074
[root@localhost ~]# ps aux  | grep sleep | grep -v grep
root      39074  0.0  0.0 108052   360 pts/0    S    17:30   0:00 sleep 1000

强制杀死进程
[root@localhost ~]# kill -9 39074
[root@localhost ~]#
[1]+  已杀死               sleep 1000
[root@localhost ~]# jobs

12.1.2 信号捕捉

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	shell编程提供了一种方式,让我们可以随意的控制脚本的运行状态,这就需要涉及到信号的捕获操作。在shell编程中,我们可以借助于 trap命令实现指定shell脚本要watch哪些linux信号并从shell中拦截。如果脚本收到了trap命令中列出的信号,它会阻止它被shell处理,而在本地处理。

trap命令格式

命令格式
	trap commands signals
	
命令示例:
	# 收到指定信号后,执行自定义指令,而不会执行原操作
    trap '触发指令' 信号
     
    # 忽略信号的操作
    trap '' 信号

    # 恢复原信号的操作
    trap '-' 信号
    
    # 列出自定义信号操作
    trap -p
    
    # 当脚本退出时,执行finish函数
    trap finish EXIT

简单实践

实践1-捕获终止信号

查看脚本内容
[root@localhost ~]# cat signal_trap_test1.sh
#!/bin/bash
# 功能:脚本信号捕捉

# 捕获关闭信号
trap "你敢关我,就不关,气死你" SIGINT SIGTERM
trap "走了,不送" EXIT

# 检测逻辑效果
while true
do
    read -p "请输入一个数据:" value
    echo "您输入的数据是: ${value}"
done
脚本执行效果
[root@localhost ~]# /bin/bash signal_trap_test1.sh
请输入一个数据:4
您输入的数据是: 4
请输入一个数据:^Csignal_trap_test1.sh:行1: 你敢关我,就不关,气死你: 未找到命令

您输入的数据是:
请输入一个数据:^Z
[1]+  已停止               /bin/bash signal_trap_test1.sh
[root@localhost ~]#
[root@localhost ~]# jobs
[1]+  已停止               /bin/bash signal_trap_test1.sh
[root@localhost ~]# fg 1
/bin/bash signal_trap_test1.sh

您输入的数据是:
请输入一个数据:3
您输入的数据是: 3
另开一个终端,直接kill进程
[root@localhost ~]# ps aux | grep sign
root      39142  0.0  0.0 113288  1460 pts/0    S+   17:43   0:00 /bin/bash signal_trap_test1.sh
[root@localhost ~]# kill -9 39142

回到之前的终端查看效果
[root@localhost ~]# fg 1
/bin/bash signal_trap_test1.sh

您输入的数据是:
请输入一个数据:3
您输入的数据是: 3
请输入一个数据:已杀死

实践2-捕获正常退出

查看脚本内容
[root@localhost ~]# cat signal_trap_test2.sh
#!/bin/bash
# 功能:脚本信号捕捉

# 捕获关闭信号
trap "echo '走了.不送'" EXIT

value="0"
# 检测逻辑效果
while true
do
    read -p "请输入一个数据:" value
    if [ ${value} == "9" ]
    then
        exit
    else
        echo "您输入的数据是: ${value}"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash signal_trap_test2.sh
请输入一个数据:3
您输入的数据是: 3
请输入一个数据:9
走了.不送

实践3-移除捕获

查看脚本内容
[root@localhost ~]# cat signal_trap_test3.sh
#!/bin/bash
# 功能:移除脚本信号捕捉

# 捕获关闭信号
trap "echo '走了.不送'" EXIT

i=1
# 检测逻辑效果
while [ $i -le 3 ]
do
    read -p "请输入一个数据:" value
    if [ ${value} == "9" ]
    then
        exit
    else
        echo "您输入的数据是: ${value}"
    fi
    let i+=1
done

# 移除捕获信号
trap - EXIT
echo "移除了捕获信号"
脚本执行效果
[root@localhost ~]# /bin/bash signal_trap_test3.sh
请输入一个数据:9
走了.不送
[root@localhost ~]# /bin/bash signal_trap_test3.sh
请输入一个数据:1
您输入的数据是: 1
请输入一个数据:2
您输入的数据是: 2
请输入一个数据:3
您输入的数据是: 3
移除了捕获信号

结果显示:
	在没有走到信号捕获移除的时候,捕获仍然生效

12.2 expect

12.2.1 expect基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

在日常工作中,经常会遇到各种重复性的"手工交互"操作,虽然没有什么技术含量,但是相当的重要。在实际的工作场景中,这种重复性的手工操作动作,非常的繁多,但是对于量大的工作来说,效率就非常低效了。所以我们就需要有一种工具,能够简化我们重复的手工操作。

expect简介

expect是一个免费的编程工具,由DonLibes制作,作为Tcl脚本语言的一个扩展,它可以根据程序的提示,模拟标准输入提供给程序,从而实现自动的交互式任务,而无需人为干预,可以用作Unix系统中进行应用程序的自动化控制和测试的软件工具。

说白了,expect就是一套用来实现自动交互功能的软件。它主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信。在使用的过程中,主要是以脚本文件的样式来存在

官方网站:
	https://www.nist.gov/services-resources/software/expect
工具手册:
	man expect

软件部署

安装软件
[root@localhost ~]# yum install expect -y

查看效果
[root@localhost ~]# expect -v
expect version 5.45
进入专用的命令交互界面
[root@localhost ~]# expect
expect1.1>  ls
anaconda-ks.cfg
expect1.2> exit
命令帮助
    -c:	执行脚本前先执行的命令,可多次使用,多个命令之间使用;隔开
    -d:	debug模式,可以在运行时输出一些诊断信息,与在脚本开始处使用exp_internal 1相似。
    -D:	启用交换调式器,可设一整数参数。
    -f:	从文件读取命令,仅用于使用#!时。如果文件名为"-",则从stdin读取(使用"./-"从文件名为-的文件读取)。
    -i:	交互式输入命令,使用"exit"或"EOF"退出输入状态。
    --:	标示选项结束(如果你需要传递与expect选项相似的参数给脚本时),可放到#!行:#!/usr/bin/expect --。
    -v:	显示expect版本信息

简单实践

语法解读

	在进行expect脚本编写的时候,我们需要记住 -- expect 用的不是我们普通的shell或者python语法,它使用的是tlc语法。

	Tcl 全称是 Tool command Language。它是一个基于字符串的命令语言,基础结构和语法非常简单,易于学习和掌握。Tcl 语言是一个解释性语言,所谓解释性是指不象其他高级语言需要通过编译和联结,它象其他 shell 语言一样,直接对每条语句顺次解释执行。

	Tcl 数据类型简单。对 Tcl 来说,它要处理的数据只有一种——字符串。Tcl 将变量值以字符串的形式进行存储,不关心它的实际使用类型。

输出语法

输出:tcl使用”puts"关键字来作为输出语句
样式:puts <-nonewline> string
属性解析:
	如果string中间有特殊字符,可以使用 {} 或者 "" 将其作为一个小组,共同输出
	-nonewline 代表输出结果的时候,不输出换行符
	put 和 puts 都可以在命令行使用,但是脚本中,最好用puts
[root@localhost ~]# expect
expect1.1> puts hello				# 输出一个字符串内容
hello
expect1.2> puts "hello world"		# 输出包含特殊字符的字符串,不能用单引号
hello world
expect1.3> puts {hello world}		# 输出包含特殊字符的字符串
hello world
expect1.4> puts -nonewline "hello world"  # 输出内容的时候,不换行
hello worldexpect1.5>

脚本基础

1 文件名后缀   
	.expect 作为标识符
2 文件首行,要指定命令的执行解释器 
	#!/usr/bin/expect
3 脚本文件的执行
	expect 脚本名
脚本内容示例
[root@localhost ~]# cat expect_test.expect
#!/usr/bin/expect
# 设定一个环境变量
set var nihao
# 输出环境变量
puts $var

脚本执行效果
[root@localhost ~]# expect expect_test.expect
nihao

12.2.2 语法实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

赋值语法

赋值:tcl 使用“set”关键字来定义参数,不必指定变量值的类型,因为变量值的类型仅一种——字符串
样式:set varName [value] 
注意:
	变量名是由 数字、下划线、字符组成,数字不能开头,大小写敏感。
expect1.7> set a Hello			# 设定一个变量名a
Hello
expect1.8> put $a				# 使用$ 符号获取变量名的存储值
Hello
expect1.9> put "$a"				# 使用 "" 方式打印变量的值
Hello
expect1.10> put {$a}			# {} 有别于"" 的特点在于原字符输出
$a
expect1.11> set b $a			# 变量的传递
Hello
expect1.12> puts $b
Hello

替换语法

替换(解析):
	- $符号可以实现引用替换,用于引用变量名代替的参数值,但是TCL对嵌套的”$”不于理睬
	- [] 方括号“[]”完成命令替换。用“[]”将一条命令括起来,命令执行完成后,返回结果
expect1.20> set b [set a 5]					# 相当于 set b $a,传递赋值
5
expect1.21> puts $b
5
expect1.22> set c [expr 5 * 10 ]			# expr是执行系统命令,将计算结果交给c
50
expect1.24> puts $c
50

注意事项

变量的设定
expect1.13> set var value					# 设定一个普通变量名
value
expect1.14> puts $var						# 获取变量名的值
value

不支持嵌套$
expect1.15> set var1 $$value				# TCL不支持嵌套的$
can't read "value": no such variable
    while executing
"set var1 $$value"
expect1.16> set var1 $$var					# 由于$var 已经是变量,所以前面的$就无效了
$value
expect1.17> puts $var1
$value

原字符输出
expect1.18> set var2 {$var1}				# {} 代表原字符输出
$var1
expect1.19> puts $var2
$var1

脚本实践

内置变量

对于tcl来说,它内部包含了大量的内置变量,可以让我们实现快速的功能操作。

常见的内置变量有:
	argc	指命令行参数的个数。
	argv	指包含命令行参数的列表。
	argv0	是指被解释的文件或由调用脚本的名称的文件名。
	env		用于表示是系统环境变量的内容,普通变量我们还是使用$即可
	tcl_version	返回Tcl解释器的最新版本,注意不是expect的版本号

内置参数实践

[root@localhost ~]# cat expect_test1.expect
#!/usr/bin/expect
# 查看当前文件传递的参数数量
puts "当前文件传递的参数数量: $argc"

# 查看当前文件传递的参数
puts "当前文件传递的参数: $argv"

# 查看当前文件名称
puts "当前文件名称: $argv0"

# 获取变量值
puts "当前系统变量PATH的值是: $env(PATH)"
set key value
puts "普通变量 key 的值是: $key"

# 查看版本信息
puts "当前tcl版本信息: $tcl_version"
脚本执行效果
[root@localhost ~]# expect expect_test1.expect
当前文件传递的参数数量: 0
当前文件传递的参数:
当前文件名称: expect_test1.expect
当前系统变量PATH的值是: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
普通变量 key 的值是: value
当前tcl版本信息: 8.5

12.2.3 交互基础

这一节,我们从 脚本基础、简单实践、小结 三个方面来学习。

脚本基础

命令解释器

命令解释器
#!/usr/bin/expect
#!/usr/bin/expect -f    从文件中读取自动化命令
#!/usr/bin/expect -     如果文件名为 - ,那么从终端输入中读取
#!/usr/bin/expect -i	交互式输入命令
#!/usr/bin/expect -- 	脚本传递的选项参数和expect选项相似的参数给脚本
注意:
	#!是对脚本的解释器程序路径,脚本的内容是由解释器解释的
	
注释信息
# 被注释的信息

常见符号

{ }:
	作用1:保留所有字符原有的意思,而不做解释,类似于shell中的单引号
		样式:set var {"nihao hehehe"}
	作用2:代码块儿,但是两个 {} 边界必须在一起。
		正确样式:
		if {代码块1 } {
   			代码块2
			}
		错误示例:
		if {$count < 0}
        {
           break;
        }
	注意:
		无论什么时候,{}边界符号与其他内容都最好有空格隔开,尤其是边界外的内容
[]:
	作用:执行命令,类似shell中的 ``(反引号)或者 $()
	样式:set count [expr $count - 1 ]
	
注意:
	在expect 中,没有小括号的概念和应用

常用命令

set 		设定环境变量
				格式:set 变量名 变量值
				样式:set host "192.168.8.12"

				
spawn 		启动新的进程,模拟手工在命令行启动服务
				格式:spawn 手工执行命令
				样式:spawn ssh python@$host

expect 		接收一个新进程的反馈信息,我们根据进程的反馈,再发送对应的交互命令
				格式:expect "交互界面用户输入处的关键字"
				样式:expect "*password*"
				
send 		接收一个字符串参数,并将该参数发送到新进程。
				格式:send "用户输入的信息"	
				样式:send "$password\r"
				
interact 	退出自动化交互界面,进入用户交互状态,如果需要用户交互的话,这条命令必须在最后一行
				格式:interact
				样式:interact
				
其他命令
	exit		退出expect脚本
	expect eof	expect执行内容的结束标识符,退出当前脚本,与interact只能存在一个
	puts		输出变量的信息,相当于linux命令中的echo
	wait		退出程序后,等待时间,回收僵尸进程
	disconnect	断开一个进程连接,但让它在后台继续运行。
	exp_continue 	expect获取期望后,还会有另外的期望,那么我们就把多个期望连续执行

简单实践

实践1-简单的登录交互脚本

查看脚本内容
[root@localhost ~]# cat login_test.expect
#!/usr/bin/expect

# 1 设定环境变量
set username python

# 2 发起远程登录请求
spawn ssh $username@10.0.0.12

# 3 识别用户输入的位置关键字
expect "yes/no"

# 4 发送正确的信息
send "yes\r"

# 5 识别密码关键字,并传递密码信息
send "\r"
expect "password:"
send "123456\r"

# 6 切换回用户交互界面
interact

注意:
	由于password前面会涉及到一次Enter操作,所以在password匹配前,输入一次 \r
清理历史记录
[root@localhost ~]# rm -f .ssh/know_hosts

执行脚本内容
[root@localhost ~]# expect login_test.expect
spawn ssh python@10.0.0.12
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes

Warning: Permanently added '10.0.0.12' (ECDSA) to the list of known hosts.
python@10.0.0.12's password:
[python@localhost ~]$ id
uid=1000(python) gid=1000(python) 组=1000(python)

实践2-脚本结合

expect 除了使用专用的expect脚本来实现特定功能之外,它还可以与其他脚本嵌套在一起进行使用。最常用的结合方式就是 shell结合。

在于shell结合使用的时候,无非就是将expect的执行命令使用 <<-EOF  。。。 EOF 包装在一起即可。
样式:
/usr/bin/expect<<-EOF
spawn ...
...
expect eof 
EOF

注意:
	由于expect在shell中是作为一个子部分而存在的,所以,一般情况下,expect结束的时候,使用eof命令表示expect的内容到此结束
查看脚本内容
[root@localhost ~]# cat expect_auto_login.sh
#!/bin/bash
# 功能:shell自动登录测试
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
host="$1"
username="$2"
password="$3"

/usr/bin/expect <<-EOF
# 发出连接进程
spawn ssh ${username}@${host}

# - 正常登陆
expect {
    "yes/no*" {  send "yes\n"; exp_continue  }
    "password:" {send "${password}\n";}
}
puts "测试完毕!!!"
expect eof
EOF
脚本测试效果
[root@localhost ~]# /bin/bash expect_auto_login.sh 10.0.0.12 python 123456
spawn ssh python@10.0.0.12
python@10.0.0.12's password: 测试完毕!!!

[python@localhost ~]$ exit
[root@localhost ~]#

12.2.4 综合案例

这一节,我们从 自动分区、用户实践、小结 三个方面来学习。

基础知识

简介

	当系统配置完毕后,我们可以采用fdisk命令对额外的磁盘进行磁盘分区。而expect可以实现这个效果。

手工演示

[root@localhost ~]# fdisk /dev/sdc
欢迎使用 fdisk (util-linux 2.23.2)。

更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。

Device does not contain a recognized partition table
使用磁盘标识符 0x17fc4c8a 创建新的 DOS 磁盘标签。

命令(输入 m 获取帮助):n					# 输入n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p				 # 输入p
分区号 (1-4,默认 1):					# 输入 Enter
起始 扇区 (2048-41943039,默认为 2048):# 输入 Enter
将使用默认值 2048
Last 扇区, +扇区 or +size{K,M,G} (2048-41943039,默认为 41943039):# 输入Enter
将使用默认值 41943039
分区 1 已设置为 Linux 类型,大小设为 20 GiB

命令(输入 m 获取帮助):wq				# 输入wq
The partition table has been altered!

Calling ioctl() to re-read partition table.
正在同步磁盘。
[root@localhost ~]# mkfs -t ext4 /dev/sdc
mke2fs 1.42.9 (28-Dec-2013)
/dev/sdc is entire device, not just one partition!
无论如何也要继续? (y,n) y
文件系统标签=
OS type: Linux
块大小=4096 (log=2)
分块大小=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
1310720 inodes, 5242880 blocks
262144 blocks (5.00%) reserved for the super user
第一个数据块=0
Maximum filesystem blocks=2153775104
160 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
        4096000

Allocating group tables: 完成
正在写入inode表: 完成
Creating journal (32768 blocks): 完成
Writing superblocks and filesystem accounting information: 完成

[root@localhost ~]# mkdir /haha
[root@localhost ~]# mount /dev/sdc /haha
[root@localhost ~]# echo nihao > /haha/h.txt
[root@localhost ~]# ls /haha
h.txt  lost+found

脚本实践

查看脚本内容
[root@localhost ~]# cat expect_auto_partition.sh
#!/bin/bash
# 功能:shell自动磁盘分区格式化测试
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
mount_dir='/disk_check'

# 检测基本环境
[ -f /usr/bin/expect ] && echo "expect 环境正常" || ("expect 环境异常" && exit)

# 查看磁盘列表
fdisk -l | grep "磁盘 /dev/s"

# 定制磁盘分区操作
read -p "请输入需要挂载得硬盘路径: " disk_name
	
# expect自动分区操作
/usr/bin/expect << EOF
set timeout 30
spawn bash -c "fdisk ${disk_name}"
expect "命令*" {send "n\r"} 
expect "*default p*" {send "p\r"}
expect "*默认 1*" {send "\r"}
expect "起始 扇区*" {send "\r"}
expect "Last 扇区*" {send "\r"}
expect "命令*" {send "wq\r"} 
expect eof
interact	
EOF

# expect自动格式化操作
read -p "请输入硬盘的类型: " disk_type
/usr/bin/expect << EOF
set timeout 30
spawn bash -c "mkfs -t ${disk_type} ${disk_name}"
expect "*y,n*" {send "y\r"} 
expect eof
interact	
EOF

# 磁盘挂载测试
[ -d ${mount_dir} ] && rm -rf ${mount_dir} 
mkdir ${mount_dir}
mount ${disk_name} ${mount_dir}
echo disk_check > ${mount_dir}/check.txt
[ -f ${mount_dir}/check.txt ] && echo "${mount_dir} 挂载成功" || (echo "${mount_dir} 挂载失败" && exit)
umount ${mount_dir}
[ ! -f ${mount_dir}/check.txt ] && echo "${mount_dir} 卸载成功" || (echo "${mount_dir} 卸载失败" && exit)
rm -rf ${mount_dir}

创建用户实践

案例需求

	借助于expect实现在特定的主机上批量创建用户

脚本实践

查看脚本内容
[root@localhost ~]# cat expect_auto_register.sh
#!/bin/bash
# 功能:shell自动远程主机创建用户测试
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制初始变量
login_user='root'
login_pass='123456'
host_file='ip.txt'
new_user='shuji-1'
new_pass='123456'

# 批量创建用户
cat ${host_file} | while read ip
do
    expect <<-EOF
        set timeout 30
        spawn ssh $login_user@$ip
        expect {
            "yes/no" { send "yes\n";exp_continue }
            "password" { send "${login_pass}\n" }
        }

        expect "]#" { send "useradd ${new_user}\n" }
        expect "]#" { send "echo ${new_pass} |passwd --stdin ${new_user}\n" }
        expect "]#" { send "who\n" }
        expect "]#" { send "exit\n" }
        expect eof
	EOF
done
脚本执行后效果
[root@localhost ~]# /bin/bash expect_auto_register.sh
spawn ssh root@10.0.0.12
root@10.0.0.12's password:
[root@localhost ~]# useradd shuji-1
[root@localhost ~]# echo 123456 |passwd --stdin shuji-1
更改用户 shuji-1 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@localhost ~]# who
root     pts/1        2022-06-26 08:47 (10.0.0.12)
root     pts/2        2022-06-26 07:15 (10.0.0.1)
[root@localhost ~]# exit
登出
Connection to 10.0.0.12 closed.
spawn ssh root@10.0.0.13
The authenticity of host '10.0.0.13 (10.0.0.13)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.13' (ECDSA) to the list of known hosts.
root@10.0.0.13's password:
Last login: Sun Jun 26 07:16:20 2022 from 10.0.0.1
[root@localhost ~]# useradd shuji-1
[root@localhost ~]# echo 123456 |passwd --stdin shuji-1
更改用户 shuji-1 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@localhost ~]# who
root     tty1         2022-06-25 23:57
root     pts/0        2022-06-26 08:47 (10.0.0.12)
root     pts/1        2022-06-26 07:16 (10.0.0.1)
[root@localhost ~]# exit
登出
Connection to 10.0.0.13 closed.
校验用户创建
[root@localhost ~]# id shuji-1
uid=1001(shuji-1) gid=1001(shuji-1) 组=1001(shuji-1)
[root@localhost ~]# ssh root@10.0.0.13 id shuji-1
root@10.0.0.13's password:
uid=1001(shuji-1) gid=1001(shuji-1) 组=1001(shuji-1)

13 正则表达式

13.1 基础实践

13.1.1 基础知识

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

需求

	我们之前的一些操作,很大程度上都是基于特定的关键字来进行实践的,尤其是面对一些灵活的场景,我们因为过于限定一些关键字,导致灵活性上表现比较差。在shell中,它其实有一种机制,能够让我们结合特定的符号,实现非常灵活的内容操作。
	这就是正则表达式,正则表达式是用于描述字符排列和匹配模式的一种语法规则,通过它我们可以实现字符串的模式分割、匹配、查找及替换等操作。从而在各种业务逻辑的基础上,扩充数据层面的匹配,让脚本的适用性更大。

简介

	REGEXP 全称Regular Expressions,它是我们通过一些字符所定义的’linux程序用来筛选文本的模式模板。linux相关程序(比如sed、awk、grep、等)在输入数据的时候,使用正则表达式对数据内容进行匹配,将匹配成功的信息返回给我们。
	正则表达式被非常多的程序和开发语言支持:你能够想象到的编程语言,linux几乎所有编辑信息、查看信息的命令 等。

基本逻辑

	正则表达式模式,可以接收大量的数据来源,然后借助通配符、元字符、关键字等来标识数据流中的信息,将匹配成功的数据留存下来,为我们使用。

表达式分类

基本正则表达式:
	BRE Basic Regular Expressions
	- 借助于基本的属性信息实现内容的精准匹配
扩展正则表达式:
	ERE Extended Regular Expressions
	- 借助于扩展符号的能力,实现更大范围的信息匹配

简单实践

通配符和正则

1 正则表达式用来在文件中匹配符合条件的字符串,主要是目的是包含匹配。
	- grep、awk、sed 等命令可以支持正则表达式。
2 通配符用来匹配符合条件的文件名,通配符是完全匹配。
	- ls、find、cp 之类命令不支持正则表达式,可以借助于shell通配符来进行匹配。
		 .:匹配任意一个字符
         *:匹配任意内容
         ?:匹配任意一个内容
        []:匹配中括号中的一个字符

通配符实践

创建基本环境
[root@localhost ~]# touch user-{1..3}.sh  {a..d}.log
[root@localhost ~]# ls
a.log  b.log  d.log c.log 
user-1.sh  user-2.sh  user-3.sh

*匹配任意字符
[root@localhost ~]# ls *.log
a.log  b.log  c.log  d.log
[root@localhost ~]# ls u*
user-1.sh  user-2.sh  user-3.sh

?匹配一个字符
[root@localhost ~]# ls user?3*
user-3.sh

[]匹配中括号中的一个字符
[root@localhost ~]# ls user-[13]*
user-1.sh  user-3.sh

13.1.2 字符匹配

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	正则存在的根本就是对数据的匹配,而数据基本上都是有字符组成的,而正则表达式提供了非常多的字符匹配表达式,常见的表达式模式有:
单字符匹配
	.   匹配任意单个字符,当然包括汉字的匹配
	[]  匹配指定范围内的任意单个字符
		- 示例:[shuji]、[0-9]、[a-z]、[a-zA-Z]
	[^] 匹配指定范围外的任意单个字符
		- 示例:[^shuji] 
	 |  匹配管道符左侧或者右侧的内容

简单实践

准备配置文件

[root@localhost ~]# cat keepalived.conf
! Configuration File for keepalived

global_defs {
   router_id kpmaster
}

vrrp_instance VI_1 {
    state MASTER
    interface ens33
    virtual_router_id 50
    nopreempt
    priority 100
    advert_int 1
    virtual_ipaddress {
        192.168.8.100
    }
}

实践1-单字符过滤

.过滤单个字符
[root@localhost ~]# grep 'st..e' keepalived.conf
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
[root@localhost ~]# grep 'ens..' keepalived.conf
    interface ens33

实践2-范围单字符过滤

[] 过滤范围字符
[root@localhost ~]# grep 'i[a-z]t' keepalived.conf
    interface ens33
    virtual_router_id 50
    advert_int 1
    virtual_ipaddress {
[root@localhost ~]# grep 'i[a-n]t' keepalived.conf
    interface ens33
    advert_int 1
[root@localhost ~]# grep '[b-c]' keepalived.conf
global_defs {
vrrp_instance VI_1 {
    interface ens33
[root@localhost ~]# egrep '[x-z]' keepalived.conf
    priority 100

实践3-反向单字符过滤

只要包括的内容,都不要显示
[root@localhost ~]# grep '[^a-Z_ }{0-5]' keepalived.conf
! Configuration File for keepalived
        192.168.8.100

实践4-过滤特定的字符范围

[root@localhost ~]# egrep 'state|priority' keepalived.conf
    state MASTER
    priority 100
    
[root@localhost ~]# egrep 'st|pri' keepalived.conf
   router_id kpmaster
vrrp_instance VI_1 {
    state MASTER
    priority 100

13.1.3 锚定匹配

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的锚定匹配,主要是在字符匹配的前提下,增加了字符位置的匹配
常见符号
    ^ 					行首锚定, 用于模式的最左侧
    $ 					行尾锚定,用于模式的最右侧
    ^PATTERN$ 			用于模式匹配整行
    ^$ 					空行
    ^[[:space:]]*$ 		空白行
    \< 或 \b   		   词首锚定,用于单词模式的左侧
    \> 或 \b        	   词尾锚定,用于单词模式的右侧
    \<PATTERN\>     	匹配整个单词
注意: 
	单词是由字母,数字,下划线组成

简单实践

准备实践文件

[root@localhost ~]# cat nginx.conf
#user  nobody;
worker_processes  1;

http {
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践1-行首位地址匹配

行首位置匹配
[root@localhost ~]# grep '^wor' nginx.conf
worker_processes  1;

行尾位置匹配
[root@localhost ~]# grep 'st;$' nginx.conf
        server_name  localhost;

实践2-关键字匹配

关键字符串匹配
[root@localhost ~]# grep '^http {$' nginx.conf
http {
[root@localhost ~]# grep '^w.*;$' nginx.conf
worker_processes  1;

实践3-空行匹配

空行匹配
[root@localhost ~]# grep '^$' nginx.conf


[root@localhost ~]# grep  '^[[:space:]]*$' nginx.conf


# 反向过滤空行
[root@localhost ~]# grep -v '^$' nginx.conf
#user  nobody;
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践4-单词匹配

单词首部匹配
[root@localhost ~]# grep '\bloca' nginx.conf
        server_name  localhost;
        location / {
[root@localhost ~]# grep '\<loca' nginx.conf
        server_name  localhost;
        location / {
        
单词尾部匹配
[root@localhost ~]# grep 'ion\>' nginx.conf
        location / {
[root@localhost ~]# grep 'ion\b' nginx.conf
        location / {
        
单词内容匹配
[root@localhost ~]# grep '\<index\>' nginx.conf
            index  index.html index.htm;
[root@localhost ~]# grep '\<sendfile\>' nginx.conf
    sendfile        on;

13.1.4 分组符号

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	当我们使用正则模式匹配到的内容有很多项的时候,默认会全部输出。如果我们仅仅需要特定顺序的一个匹配内容的话,就用到我们这一节的知识点 -- 分组。
所谓的分组,其实指的是将我们正则匹配到的内容放到一个()里面
    - 每一个匹配的内容都会在一个独立的()范围中
    - 按照匹配的先后顺序,为每个()划分编号
    - 第一个()里的内容,用 \1代替,第二个()里的内容,用\2代替,依次类推
    - \0 代表正则表达式匹配到的所有内容
注意:
	() 范围中支持|等字符匹配内容。从而匹配更多范围的信息
	关于()信息的分组提取依赖于文件的编辑工具,我们可以借助于 sed、awk功能来实现
	提示: sed -r 's/原内容/修改后内容/'
示例:
	(M|m)any  	可以标识 Many 或者 many

简单实践

准备配置文件

准备zookeeper的配置文件
[root@localhost ~]# cat zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/server/zookeeper/data
dataLogDir=/data/server/zookeeper/logs
clientPort=2181
server.1=10.0.0.12:2182:2183
server.2=10.0.0.13:2182:2183
server.3=10.0.0.14:2182:2183:observer
4lw.commands.whitelist=stat, ruok, conf, isro

实践1-分组信息匹配实践

获取zookeeper的集群相关信息
[root@localhost ~]# egrep  '(server.[0-9])' zoo.cfg
server.1=10.0.0.12:2182:2183
server.2=10.0.0.13:2182:2183
server.3=10.0.0.14:2182:2183:observer

[root@localhost ~]# egrep  '(init|sync)Limit' zoo.cfg
initLimit=10
syncLimit=5

实践2-信息的提取

借助于sed的编辑文件功能,实现特定信息的提取
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\1/"
server.1
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\2/"
10.0.0.12
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\3/"
2182
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\4/"
2183

13.2 进阶知识

13.2.1 限定符号

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的限定符号,主要指的是,我们通过正则表达式匹配到内容后,前面内容重复的次数,常见的服号如下:
常见符号
	* 		匹配前面的字符任意次,包括0次,贪婪模式:尽可能长的匹配
    .* 		任意长度的任意字符
    ? 		匹配其前面的字符出现0次或1次,即:可有可无
    + 		匹配其前面的字符出现最少1次,即:肯定有且 >=1 次
    {m} 	匹配前面的字符m次
    {m,n} 	匹配前面的字符至少m次,至多n次
    {,n}  	匹配前面的字符至多n次,<=n
    {n,}  	匹配前面的字符至少n次

简单实践

准备文件

[root@localhost ~]# cat file.txt
ac
abbcd
abbbce
abbbbbc
abcf

实践1-精确匹配

精确匹配 以a开头 c结尾 中间是有b或者没有b 长度不限的字符串
[root@localhost ~]# egrep "^ab*c$" file.txt
ac
abbbbbc

精确匹配 以a开头 c结尾 中间只出现一次b或者没有b的字符串
[root@localhost ~]# egrep "^ab?c$" file.txt
ac

精确匹配 以a开头 中间是有b且至少出现一次 长度不限的字符串
[root@localhost ~]# egrep "^ab+" file.txt
abbcd
abbbce
abbbbbc
abcf

精确匹配 以a开头 中间是有b且至少出现两次最多出现四次 长度不限的字符串
[root@localhost ~]# egrep "^ab{2,4}" file.txt
abbcd
abbbce
abbbbbc

精确匹配 以a开头 中间是有b且正好出现三次的字符串
[root@localhost ~]# egrep "^ab{3}" file.txt
abbbce
abbbbbc

精确匹配 以a开头 中间是有b且至少出现两次的字符串
[root@localhost ~]# egrep "^ab{2,}" file.txt
abbcd
abbbce
abbbbbc

14 正则表达式

14.1 进阶知识

14.1.1 扩展符号

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

字母模式匹配
    [:alnum:] 字母和数字
    [:alpha:] 代表任何英文大小写字符,亦即 A-Z, a-z
    [:lower:] 小写字母,示例:[[:lower:]],相当于[a-z]
    [:upper:] 大写字母
数字模式匹配
    [:digit:] 十进制数字
    [:xdigit:]十六进制数字
符号模式匹配
    [:blank:] 空白字符(空格和制表符)
    [:space:] 包括空格、制表符(水平和垂直)、换行符、回车符等各种类型的空白
    [:cntrl:] 不可打印的控制字符(退格、删除、警铃...)
    [:graph:] 可打印的非空白字符
    [:print:] 可打印字符
    [:punct:] 标点符号
    
注意:
	在使用该模式匹配的时候,一般用[[ ]],
		- 第一个中括号是匹配符[] 匹配中括号中的任意一个字符
		- 第二个[]是格式 如[:digit:]
属性模式匹配
    \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [\f\r\t\v]。
    \S 匹配任何非空白字符。等价于 [^\f\r\t\v]
    \w 匹配一个字母,数字,下划线,汉字,其它国家文字的字符,等价于[_[:alnum:]字]
    \W 匹配一个非字母,数字,下划线,汉字,其它国家文字的字符,等价于[^_[:alnum:]字]

简单实践

准备文件

[root@localhost ~]# cat file1.txt
acd
abc
a_c
aZc
aZd
a c
a3c

精确匹配实践

以a开头c结尾  中间a-zA-Z0-9任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:alnum:]]c$" file1.txt
abc
aZc
a3c

以a开头c结尾  中间是a-zA-Z任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:alpha:]]c$" file1.txt
abc
aZc

以a开头c结尾  中间是0-9任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:digit:]]c$" file1.txt
a3c

以a开头c结尾  中间是a-z任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:lower:]]c$" file1.txt
abc



以a开头c结尾  中间是A-Z任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:upper:]]c$" file1.txt
aZc

以a开头c结尾  中间是可打印符号  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:print:]]c$" file1.txt
abc
a_c
aZc
a c
a3c

以a开头c结尾  中间是符号字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:punct:]]c$" file1.txt
a_c

以a开头c结尾  中间是空格或者TAB符字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:blank:]]c$" file1.txt
a c
[root@localhost ~]# egrep "^a[[:space:]]c$" file1.txt
a c

以a开头c结尾  中间是十六进制字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:xdigit:]]c$" file1.txt
abc
a3c

14.1.2 目标检测

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

需求

	定制站点或目标主机的检测平台,在对站点域名和主机ip检测之前,判断输入的语法是否正确。

ip检测

定制ip地址文件
[root@localhost ~]# cat testip.txt
112.456.44.55
256.18.56.1
10.0.0.12

匹配ip地址
[root@localhost ~]# egrep '(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|2[0-5][0-9]|25[0-4])$'  testip.txt
10.0.0.12

网址检测

定制ip地址文件
[root@localhost ~]# cat testsite.txt
http://www.baidu.com
www.126.com
163.com
http.example.comcom

匹配ip地址
[root@localhost ~]# egrep '((http|https|ftp):\/\/)?(www\.)?([0-Z]+\.)([a-Z]{2,5})$'  testsite.txt
http://www.baidu.com
www.126.com
163.com

简单实践

脚本内容

查看脚本内容
[root@localhost ~]# cat target_check.sh
#!/bin/bash
# 功能:定制主机存活的检测功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制目标类型变量
target_type=(主机 网站)

# 定制检测ip地址格式的函数
check_ip(){
    # 接收函数参数
    IP=$1
    ip_regex='(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|2[0-5][0-9]|25[0-4])$'
    # 判断ip地址是否有效
    echo $IP | egrep "${ip_regex}" >/dev/null && echo "true" || echo "false"
}

# 定制网址的格式检测函数
check_url(){
    # 接收函数参数
    site=$1
    site_regex='((http|https|ftp):\/\/)?(www\.)?([0-Z]+\.)([a-Z]{2,5})$'
    # 判断网址地址是否有效
    echo $site | egrep "${site_regex}" >/dev/null && echo "true" || echo "false"
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------确定检测目标类型---------------"
    echo -e " 1: 主机  2: 网站"
    echo -e "-------------------------------------------\033[0m"
}

# 目标主机检测过程
host_ip_check(){
    read -p "> 请输入要检测的主机ip: " ip_addr
    result=$(check_ip ${ip_addr})
    if [ ${result} == "true" ];then
       ping -c1 -W1 ${ip_addr} &> /dev/null && echo "${ip_addr} 状态正常" || echo "${ip_addr} 状态不可达"
    else
       echo "目标ip格式异常"
    fi
}

# 目标站点检测过程
net_site_check(){
    read -p "> 请输入要检测的网站地址: " site_addr
    result=$(check_url ${site_addr})
    if [ ${result} == "true" ];then
        curl -s -o /dev/null ${site_addr} && echo "${site_addr} 状态正常" || echo "${site_addr} 状态异常"
    else
        echo "目标网址格式异常"
    fi
}

# 定制帮助信息
Usage(){
    echo "请输入正确的检测目标类型"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要检测的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "主机" ];then
        host_ip_check
    elif [ ${target_type[$target_id-1]} == "网站" ];then
        net_site_check
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash target_check.sh
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 1
> 请输入要检测的主机ip: 1aaa
目标ip格式异常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 1
> 请输入要检测的主机ip: 10.0.0.12
10.0.0.12 状态正常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 1
> 请输入要检测的主机ip: 10.0.0.13
10.0.0.13 状态不可达
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 2
> 请输入要检测的网站地址: www
目标网址格式异常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 2
> 请输入要检测的网站地址: www.baidu.com
www.baidu.com 状态正常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 2
> 请输入要检测的网站地址: www.nihaoxxxxxx.com
www.nihaoxxxxxx.com 状态异常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: ^C
[root@localhost ~]#

14.1.3 登录检测

这一节,我们从 需求简介、简单实践、小结 三个方面来学习

基础知识

简介

	在很多的应用交互页面,经常会出现一些用户输入的信息:
		账号登录场景: 比如用户名、密码、手机号、邮箱之类的校验信息

手机号匹配

准备手机号文件
[root@localhost ~]# cat phone.txt
13412345678
135666666667
13a12345678
198123456

过滤真正的手机号
[root@localhost ~]# egrep '\<1[3-9][0-9]{9}\>' phone.txt
13412345678

邮箱地址匹配

定制邮箱地址文件
[root@localhost ~]# cat testemail.txt
admin@qq.com
1881111@gmail.eduedu
10.0.0.12
"shuji@qq.com
123_shuji@12306.cn

匹配邮箱地址
[root@localhost ~]# egrep  "^[0-Z_]+\@[0-Z]+\.[0-Z]{2,5}$" testemail.txt
admin@qq.com
123_shuji@12306.cn

简单实践

脚本内容

查看脚本内容
[root@localhost ~]# cat register_login_manager.sh
#!/bin/bash
# 功能:定制管理界面的登录注册功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制目标类型变量
target_type=(登录 注册)

# 定制普通变量
user_regex='^[0-Z_@.]{6,15}$'
passwd_regex='^[0-Z.]{6,8}$'
phone_regex='^\<1[3-9][0-9]{9}\>$'
email_regex='^[0-Z_]+\@[0-Z]+\.[0-Z]{2,5}$'

# 检测用户名规则
check_func(){
    # 接收函数参数
    target=$1
    target_regex=$2
    # 判断目标格式是否有效
    echo $target | egrep "${target_regex}" >/dev/null && echo "true" || echo "false"
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台登录界面---------------"
    echo -e " 1: 登录  2: 注册"
    echo -e "-------------------------------------------\033[0m"
}

# 定制帮助信息
Usage(){
    echo "请输入正确的操作类型"
}

# 管理平台用户注册过程
user_register_check(){
    read -p "> 请输入用户名: " login_user
    user_result=$(check_func ${login_user} ${user_regex})
    if [ ${user_result} == "true" ];then
        read -p "> 请输入密码: " login_passwd
        passwd_result=$(check_func ${login_passwd} ${passwd_regex})
        if [ ${passwd_result} == "true" ];then
            read -p "> 请输入手机号: " login_phone
            phone_result=$(check_func ${login_phone} ${phone_regex})
            if [ ${phone_result} == "true" ];then
                read -p "> 请输入邮箱: " login_email
                email_result=$(check_func ${login_email} ${email_regex})
                if [ ${email_result} == "true" ];then
                    echo -e "\e[31m----用户注册信息内容----"
                    echo -e " 用户名称: ${login_user}"
                    echo -e " 登录密码: ${login_passwd}"
                    echo -e " 手机号码: ${login_phone}"
                    echo -e " 邮箱地址: ${login_email}"
                    echo -e "------------------------\033[0m"
                    read -p "> 是否确认注册[yes|no]: " login_status
                    [ ${login_status} == "yes" ] && echo "用户 ${login_user} 注册成功" && exit || return
                else
                   echo "邮箱地址格式不规范"
                fi
            else
                echo "手机号码格式不规范"
            fi
        else
            echo "登录密码格式不规范"
        fi
    else
        echo "用户名称格式不规范"
    fi
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "登录" ];then
        echo "开始登录管理平台..."
    elif [ ${target_type[$target_id-1]} == "注册" ];then
        user_register_check
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash register_login_manager.sh
---------------管理平台登录界面---------------
 1: 登录  2: 注册
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入用户名: root12345
> 请输入密码: 12345678
> 请输入手机号: 13412345678
> 请输入邮箱: qq@123.com
----用户注册信息内容----
 用户名称: root12345
 登录密码: 12345678
 手机号码: 13412345678
 邮箱地址: qq@123.com
------------------------
> 是否确认注册[yes|no]: yes
用户 root12345 注册成功
[root@localhost ~]# /bin/bash register_login_manager.sh
---------------管理平台登录界面---------------
 1: 登录  2: 注册
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入用户名: admin123
> 请输入密码: 12345678
> 请输入手机号: 14456789090
> 请输入邮箱: qq@qq.com
----用户注册信息内容----
 用户名称: admin123
 登录密码: 12345678
 手机号码: 14456789090
 邮箱地址: qq@qq.com
------------------------
> 是否确认注册[yes|no]: no
---------------管理平台登录界面---------------
 1: 登录  2: 注册
-------------------------------------------
> 请输入要操作的目标类型:

15 sed命令

15.1 基础实践

15.1.1 基础语法

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

场景

	shell脚本虽然功能很多,但是它最常用的功能还是处理文本文件,尤其是在正常的业务操作流程场景中,比如检查日志文件、读取配置、处理数据等现象,虽然我们能够使用echo、cat、<<、>>、|等符号实现文件内容的操作,但是整个过程有些繁琐。所以我们需要一种更为轻便的文本编辑工具,sed就是其中的一种。

简介

	sed(Stream EDitor) 属于一种数据流式的行文件编辑工具。因为它编辑文件的时候,在内存中开辟一块额外的模式空间(pattern space),然后以行为单位读取文件内容到该空间中,接着sed命令处理该空间中的内容,默认在当前终端界面打印内容,然后清空模式空间内容,再来读取第二行内容,依次循环下去。
    作用:用来自动编辑一个或多个文件,简化对文件的反复操作,编写转换程序等。
    参考:http://www.gnu.org/software/sed/manual/sed.html

语法格式

基本格式	
	sed [参数] '<匹配条件> [动作]' [文件名]
注意:
	匹配条件和动作两侧有'
	动作可以有多个,彼此间使用;隔开,比如 '2p;4p'
参数详解:
    参数为空	 表示sed的操作效果,实际上不对文件进行编辑,缓存区所有信息都显示
    -n			不输出模式空间内容到屏幕,即不自动打印所有内容
    -e			基于命令实现对文件的多点编辑操作
    -f			从指定文件中读取编辑文件的”匹配条件+动作”
    -r			支持使用扩展正则表达式
    -i.bak		复制文件原内容到备份文件,然后对原文件编辑
    -i			表示对文件进行编辑

注意:
   mac版本的bash中使用 -i参数,必须在后面单独加个东西: -i ''
    -i -r 支持  -ri   支持
    -ir   不支持
    -ni   危险选项,会清空文件
匹配条件分为两种:数字行号或者关键字匹配
数字行号:
	空 表示所有行				n 表示第n行				$ 表示末尾行
	n,m 表示第n到m行内容		n,+m 表示第n到n+m行
	~步进	1~2 表示奇数行		2~2 表示偶数行

关键字匹配格式:
	'/关键字/'
    注意:
        隔离符号 / 可以更换成 @、#、!等符号
        根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。
        /关键字1/,/关键字2/ 表示关键字1所在行到关键字2所在行之间的内容
        n,/关键字2/ 表示从第n行到关键字2所在行之间的内容
动作详解
    -a[\text]			在匹配到的内容下一行增加内容,支持\n实现多行追加
    -i[\text]			在匹配到的内容当前行增加内容
    -c[\text]			在匹配到的内容替换内容
    -d|p				删除|打印匹配到的内容
    -s					替换匹配到的内容
    W /path/somefile 	保存模式匹配的行至指定文件
    r /path/somefile	读取指定文件的文本至模式空间中
    =					为模式空间中的行打印行号
    !					模式空间中匹配行取反处理
注意:
	上面的动作应该在参数为-i的时候使用,不然的话不会有效果

简单实践

准备工作

模板文件内容
[root@localhost ~]# cat sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed7 sed8 sed9

实践1-打印信息

默认打印信息
[root@localhost ~]# sed '2p' sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed4 sed5 sed6			# 这一行才是操作的内容,其他的都是缓存区自动输出
nihao sed7 sed8 sed9

打印第2行,不输出缓存区默认的其他信息
[root@localhost ~]# sed -n '2p' sed.txt 
nihao sed4 sed5 sed6

打印第1,3行,不输出缓存区默认的其他信息
[root@localhost ~]# sed -n '1p;3p' sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

打印网卡信息
[root@localhost ~]# ifconfig eth0 | sed -n '2p'
        inet 10.0.0.12  netmask 255.255.255.0  broadcast 10.0.0.255
        
打印磁盘信息
[root@localhost ~]# df | sed -n '/^\/dev\/sd/p'
/dev/sda2      19911680 2772988 17138692   14% /
/dev/sda1       1038336  145380   892956   15% /boot

实践2-匹配内容打印

打印包含sed4的行
[root@localhost ~]# sed -n '/sed4/p' sed.txt 
nihao sed4 sed5 sed6

打印奇数行
[root@localhost ~]# sed -n '1~2p' sed.txt      
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

打印偶数行
[root@localhost ~]# sed -n '0~2p' sed.txt
nihao sed4 sed5 sed6

实践3-文件编辑

-e实现多次文件编辑动作
[root@localhost ~]# sed -n -e '1p' -e '3p' sed.txt    
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

将文件操作命令输出到一个文件
[root@localhost ~]# echo -e "1p\n3p" > sed_script
借助文件里面的命令实现文件编辑
[root@localhost ~]# sed -n -f sed_script sed.txt 
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

实践4-其他信息显示

取反显示
[root@localhost ~]# sed -n '2!p' sed.txt            
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9


查看内容属于第几行
[root@localhost ~]# sed -n '/sed4/=' sed.txt  
2

15.1.2 内容替换

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	sed的文本替换动作是使用频率最高的一种样式。它的基本表现样式如下:
命令格式:
	sed -i [替换格式] [文件名]
	源数据 | sed -i [替换格式]
	
注意:替换命令的写法
	's###'  --->  's#原内容##' ---> 's#原内容#替换后内容#'
	隔离符号 / 可以更换成 @、#、!等符号
表现样式:
    样式一:替换指定匹配的内容
        sed -i '行号s#原内容#替换后内容#列号' [文件名]
        echo "源数据" | sed -i '行号s#原内容#替换后内容#列号'
    样式二:替换所有的内容
        sed -i 's#原内容#替换后内容#g' [文件名]
    	echo "源数据" | sed -i '行号s#原内容#替换后内容#g'
    样式三: 替换指定的内容
    	sed -i '行号s#原内容#&新增信息#列号' [文件名]
    	- 这里的&符号代表源内容,实现的效果是 '原内容+新内容'

简单实践

实践1-替换每行首个匹配内容

格式:sed -i 's#原内容#替换后内容#' 文件名

替换首每行的第1个sed为SED
[root@localhost ~]# sed -i 's#sed#SED#' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED1 sed2 sed3
nihao SED4 sed5 sed6
nihao SED7 sed8 sed9

实践2-替换全部匹配内容

格式:sed -i 's#原内容#替换后内容#g' 文件名

替换全部sed为des
[root@localhost ~]# sed -i 's#sed#SED#g' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED1 SED2 SED3
nihao SED4 SED5 SED6
nihao SED7 SED8 SED9

关于全部替换还有另外一种命令叫直接转换 y
[root@localhost ~]# sed 'y/SED/sed/' sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed7 sed8 sed9

实践3-指定行号替换首个匹配内容

格式:sed -i '行号s#原内容#替换后内容#' 文件名

替换第2行的首个SED为sed
[root@localhost ~]# sed -i '2s#SED#sed#' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED1 SED2 SED3
nihao sed4 SED5 SED6
nihao SED7 SED8 SED9

实践4-首行指定列号替换匹配内容

格式:sed -i 's#原内容#替换后内容#列号' 文件名

替换每行的第2个SED为sed
[root@localhost ~]# sed -i 's#SED#sed#2' sed.txt
[root@localhost ~]# cat sed.txt 
nihao SED1 sed2 SED3
nihao sed4 SED5 sed6
nihao SED7 sed8 SED9

实践5-指定行号列号匹配内容

格式:sed -i '行号s#原内容#替换后内容#列号' 文件名

替换第3行的第2个SED为sed
[root@localhost ~]# sed -i '3s#SED#sed#2' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED sed2 SED3
nihao sed4 SED5 sed6
nihao SED7 sed8 sed9

实践6-综合实践

借助正则的分组功能实现ip地址获取
[root@localhost ~]# ifconfig eth0 | sed -n '2p' | sed -r 's#.*inet (.*) net.*#\1#'
10.0.0.12
[root@localhost ~]# ifconfig eth0 | sed -n '2p' | sed -r 's#.*inet ##' | sed -r 's# net.*##' 
10.0.0.12

借助正则的分组功能实现信息的精确获取
[root@localhost ~]# echo '/etc/sysconfig/network' | sed -r 's#(.*\/)([^/]+\/?$)#\2#'
network
[root@localhost ~]# echo '/etc/sysconfig/network' | sed -r 's#(.*\/)([^/]+\/?$)#\1#'
/etc/sysconfig/

15.1.3 增加操作

这一节,我们从 追加实践、插入实践、小结 三个方面来学习

追加实践

基本语法

作用:
	在指定行号的下一行增加内容
格式:
	sed -i '行号a\增加的内容' 文件名
注意:
    如果增加多行,可以在行号位置写个范围值,彼此间使用逗号隔开,例如
    sed -i '1,3a\增加内容' 文件名

实践1-基于行号实践

指定行号增加内容
[root@localhost ~]# sed -i '2a\zengjia-2' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED sed2 SED3
nihao sed4 SED5 sed6
zengjia-2
nihao SED7 sed8 sed9
指定1~3每行都增加内容
[root@localhost ~]# sed -i '1,3a\tongshi-2' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED sed2 SED3
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

插入实践

基本语法

作用:
	在指定行号的当行增加内容
格式:
	sed -i '行号i\增加的内容' 文件名
注意:
    如果增加多行,可以在行号位置写个范围值,彼此间使用逗号隔开,例如
    sed -i '1,3i\增加内容' 文件名

实践1-基于行号实践

指定行号增加内容
[root@localhost ~]# sed -i '1i\insert-1' sed.txt 
[root@localhost ~]# cat sed.txt 
insert-1
nihao SED sed2 SED3
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

指定1~3每行都增加内容
[root@localhost ~]# sed -i '1,3i\insert-2' sed.txt 
[root@localhost ~]# cat sed.txt
insert-2
insert-1
insert-2
nihao SED sed2 SED3
insert-2
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

15.1.4 删除替换

这一节,我们从 删除实践、替换实践、小结 三个方面来学习

删除实践

基本语法

作用:
	指定行号删除
格式:
	sed -i '行号d' 文件名
注意:
    如果删除多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3d' 文件名

实践1-基于行号实践

删除第4行内容
[root@localhost ~]# sed -i '4d' sed.txt 
[root@localhost ~]# cat sed.txt 
insert-2
insert-1
insert-2
insert-2
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

删除多行(1-6行)内容
[root@localhost ~]# sed -i '1,6d' sed.txt
[root@localhost ~]# cat sed.txt
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

替换实践

基本语法

作用:
	指定行号进行整行替换
格式:
	sed -i '行号c\内容' 文件名
注意:
    如果替换多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3c\内容' 文件名

实践1-基于行号实践

替换第3行内容
[root@localhost ~]# sed -i '3c\tihuan-1' sed.txt
[root@localhost ~]# cat sed.txt
tongshi-2
zengjia-2
tihuan-1
nihao SED7 sed8 sed9


指定1~3行都替换成一行内容
[root@localhost ~]# sed -i '1,3c\tihuan-3' sed.txt
[root@localhost ~]# cat sed.txt
tihuan-3
nihao SED7 sed8 sed9

15.1.5 加载保存

这一节,我们从 加载实践、保存实践、小结 三个方面来学习

加载实践

基本语法

作用:
	加载文件内容到指定行号的位置
格式:
	sed -i '行号r 文件名1' 文件名
注意:
    如果在多行位置加载,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3r 文件名1' 文件名

实践1-基于行号实践

加载第3行内容
[root@localhost ~]# sed -i '2r sed.txt' sed.txt
[root@localhost ~]# cat sed.txt
tihuan-3
nihao SED7 sed8 sed9
tihuan-3
nihao SED7 sed8 sed9
注意;
	由于缓存区中文件内容的顺序变化,导致加载的内容顺序不一致

制定内容文件,加载到2-4行下面
[root@localhost ~]# sed -i '2,4r sed_script' sed.txt
[root@localhost ~]# cat sed_script
1p
3p
[root@localhost ~]# cat sed.txt
tihuan-3
nihao SED7 sed8 sed9
1p
3p
tihuan-3
1p
3p
nihao SED7 sed8 sed9
1p
3p

保存实践

基本语法

作用:
	指定行号保存到其他位置
格式:
	sed -i '行号w 文件名' 文件名
注意:
    如果多行保存,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3w 文件名' 文件名
    文件名已存在,则会覆盖式增加

实践1-基于行号实践

保存第3行内容
[root@localhost ~]# sed -i '2w sed_test' sed.txt
[root@localhost ~]# cat sed_test
nihao SED7 sed8 sed9



指定2~4行内容保存到一个文件中
[root@localhost ~]# sed -i '1,4w sed_test' sed.txt
[root@localhost ~]# cat sed_test
tihuan-3
nihao SED7 sed8 sed9
1p
3p

15.2 进阶实践

15.2.1 匹配进阶

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	我们之前的所有操作基本上都是基于行的操作,其实本质上还有另外一些操作 -- 基于内容的操作。语法格式如下:
内容匹配:
	'/关键字内容/'
    注意:
        隔离符号 / 可以更换成 @、#、!等符号
        根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。
        /关键字1/,/关键字2/ 表示关键字1所在行到关键字2所在行之间的内容
        n,/关键字2/ 表示从第n行到关键字2所在行之间的内容
        /关键字1/,n, 表示从关键字1所在行到第n行之间的内容
        /关键字1/,+n, 表示从关键字1所在行到(所在行+n行)之间的内容

简单实践

实践1-内容的简单匹配显示

查看匹配的内容
[root@localhost ~]# sed -n '/send/p' nginx.conf
    sendfile        on;

匹配内容间的多行信息
[root@localhost ~]# sed -n '/send/,/server/p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

    server {

查看匹配内容到第6行的内容
[root@localhost ~]# sed -n '/send/,6p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;
 
查看第1行到匹配行的内容
[root@localhost ~]# sed -n '1,/send/p' nginx.conf
#user  nobody;
worker_processes  1;

http {
    sendfile        on;
    
查看匹配内容和下面三行的内容
[root@localhost ~]# sed -n '/send/,+3p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

    server {

通过 !p 去除空行匹配
[root@localhost ~]# sed -n '/^$/!p' nginx.conf
#user  nobody;
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
借助分组功能,实现多信息的剔除
[root@localhost ~]# sed -rn '/^(#|$)/!p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践2-分组信息显示

获取制定文件所在的路径信息
[root@localhost ~]# echo "/etc/sysconfig/network" |sed -r 's#(^/.*/)([^/]+/?)#\1#'
/etc/sysconfig/

获取制定文件名称
[root@localhost ~]# echo "/etc/sysconfig/network" |sed -r 's#(^/.*/)([^/]+/?)#\2#'
network

获取ip地址
[root@localhost ~]# ifconfig eth0 |sed -nr "2s/[^0-9]+([0-9.]+).*/\1/p"
10.0.0.12

获取MAC地址
[root@localhost ~]# ifconfig eth0 |sed -nr "4s/[^0-9]+([0-Z:]+).*/\1/p"
00:0c:29:23:23:8c

15.2.2 修改实践

这一节,我们从 多点操作、增改实践、小结 三个方面来学习

多点操作

简介

我们可以借助 '动作1;动作2' 或者 -e '动作1' -e '动作2' 的方式实现多操作的并行实施

实践1-内容的过滤编辑

不显示所有空行和注释信息
[root@localhost ~]# sed '/^#/d;/^$/d' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

先剔除空行,然后不显示所有包含注释的信息
[root@localhost ~]# sed -rn '/^$/d;/^[[:space:]]*#/!p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践2-借助于 i.bak 方式对有效信息进行过滤

编辑文件的时候,原内容备份到一个额外的文件
[root@localhost ~]# sed -i.bak '/^#/d;/^$/d' nginx.conf
[root@localhost ~]# cat nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
[root@localhost ~]# grep '#' nginx.conf.bak
#user  nobody;

增改实践

实践1-借助于&符号实现内容的扩充式更改编辑

查看原内容
[root@localhost ~]# head -n 1 /etc/passwd
root:x:0:0:root:/root:/bin/bash

对原内容进行扩充替换
[root@localhost ~]# head -n 1 /etc/passwd | sed -n 's/root/&user/1p'
rootuser:x:0:0:root:/root:/bin/bash
[root@localhost ~]# head -n 1 /etc/passwd | sed -n 's/root/&user/gp'
rootuser:x:0:0:rootuser:/rootuser:/bin/bash

实践2-借助于s实现内容的替换式更改编辑

获取没有被注释的信息
[root@localhost ~]# sed -n '/^#/!p' /etc/fstab

UUID=5583bd7c-cc9f-4e19-b453-c224102f3ed5 /      xfs     defaults        0 0
UUID=cbd246cd-1df8-4fe7-9040-823cd0978837 /boot  xfs     defaults        0 0

将注释的信息进行替换
[root@localhost ~]# sed -rn '/^#/!s@^@#@p' /etc/fstab
#
#UUID=5583bd7c-cc9f-4e19-b453-c224102f3ed5 /     xfs     defaults        0 0
#UUID=cbd246cd-1df8-4fe7-9040-823cd0978837 /boot xfs     defaults        0 0

实践3-借助于 i|a 对文件进行 插入|追加 式更改编辑

基于内容匹配相关信息并打印
[root@localhost ~]# sed -n '/listen/p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
基于内容匹配追加1行内容
[root@localhost ~]# sed '/listen/a\\tlisten\t\t80;' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        listen          80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
基于内容匹配插入2行内容 -- 借助于\n的换行功能,将1行变成两行
[root@localhost ~]# sed '/listen/i\\tlisten\t\t80;\n\tlisten\t\t8080;' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen          80;
        listen          8080;
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践4-借助于 环境变量和s|c 对文件进行 修改|替换 式更改编辑

定制环境变量
[root@localhost ~]# port=8080

使用多点修改
[root@localhost ~]# sed -r -e "s/listen.*;/listen\t$port;/" -e '/server_name/c \\tserver_name '$(hostname):$port';' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen  8080;
        server_name localhost:8080;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
注意:
	这里涉及到环境变量的解读,千万不要被单引号转义了

15.2.3 高阶用法1

这一节,我们从 基础知识、缓存实践、小结 三个方面来学习。

基础知识

简介

	对于sed命令来说,除了我们经常使用的模式空间之外,它还支持一个叫暂存空间(Hold Space)的模式,所谓的暂存空间,也就是说,将模式空间中的数据,临时保存到暂存空间,从而实现更为强大的功能。
	相关业务逻辑流程如下:
空间解读:
	缓存空间用于sed的内容模式匹配,一般称为模式空间
		- 模式空间内的信息可以输出到终端界面,除非模式空间的内容被删除或取消打印导致模式空间清空
	附加于缓存空间的附加缓存空间,一般称为暂存空间
		- 通过相关命令可以在模式空间信息清零之前,暂存到附加缓存空间,便于后续使用
	两个空间之间,可以基于一些高阶命令实现信息的传递
常见的高阶命令  
    n 读取匹配到的行的下一行覆盖至模式空间
    N 读取匹配到的行的下一行追加至模式空间
    d 删除模式空间中的行
    D 如果模式空间包含换行符,则循环删除换行符前的内容,直至不包含任何换行符后,执行后续d操作

简单实践

实践1-模式空间覆盖

查看逐行读取的信息
[root@localhost ~]# seq 6 | sed -n "p"
1
2
3
4
5
6

n 读取匹配到的行的下一行覆盖至模式空间
[root@localhost ~]# seq 6 | sed -n "n;p"
2
4
6
解读:
	第一次读的是1,"n;"的作用是读取2,然后覆盖模式空间的1

2次n代表读取到第3行,将前的内容覆盖
[root@localhost ~]# seq 6 | sed -n "n;n;p"
3
6

3次n代表读取到第4行,将前面的内容覆盖
[root@localhost ~]# seq 6 | sed -n "n;n;n;p"
4

获取匹配内容的下一行,覆盖匹配的内容
[root@localhost ~]# seq 6 | sed -n "/3/{n;p}"
4
解读:
	/3/ 代表的是匹配的3内容,然后{} 代表一个表达式区域,n;p 代表下一行覆盖式打印

实践2-模式空间清零

n 读取匹配到的行的下一行覆盖至模式空间
[root@localhost ~]# seq 6 | sed -n "n;p"
2
4
6
解读:
	第一次读的是1,"n;"的作用是读取2,然后覆盖模式空间的1
	
d 删除模式空间中的行
[root@localhost ~]# seq 6 | sed -n "n;d"
[root@localhost ~]#
解读:
	n每覆盖一次,都用d删除一次,最终导致不会输出任何内容

实践3-模式空间扩容

查看默认的信息输出
[root@localhost ~]# seq 6
1
2
3
4
5
6

N 读取匹配到的行的下一行追加至模式空间
[root@localhost ~]# seq 6 | sed 'N;s/\n//'
12
34
56
解读:
	第一次读的是1,"N;"的作用是读取2,然后追加到模式空间的1的后面
	然后使用s将换行\n替换为空,实现1和2的合并
	
通过-e,多一个N,就相当于为模式空间扩容了一个位置
[root@localhost ~]# seq 6 | sed -e 'N;s/\n//' -e 'N;s/\n//'
123
456
可以看到:
	1个空间+2个扩容 一共三个空间内容

15.2.4 高阶用法2

这一节,我们从 暂存实践、其他实践、小结 三个方面来学习。

暂存实践

简介

	我们可以在缓存空间和暂存空间中进行数据的简单读取,还可以对数据进行一些复杂性的编辑操作
常见的高阶命令
    P 打印模式空间开端至\n内容,并追加到默认输出之前
    h 把模式空间中的内容覆盖至暂存空间中
    H 把模式空间中的内容追加至暂存空间中
    g 从暂存空间取出数据覆盖至模式空间
    G 从暂存空间取出内容追加至模式空间
    x 把模式空间中的内容与暂存空间中的内容进行互换

实践1-暂存空间基本实践

获取指定内容信息
[root@localhost ~]# seq 4 > seq.txt
[root@localhost ~]# cat -e seq.txt
1$
2$
3$
4$
结果显示;
	每一行后面都有换行符号$
h 把模式空间中的内容覆盖至暂存空间中 G 从暂存空间取出内容追加至模式空间
[root@localhost ~]# sed -e '/2/h' -e '$G' seq.txt
1
2
3
4
2
解读:
	/2/h 将匹配到的内容存储到 暂存空间
	$ 正常信息输出的时候,不输出暂存空间的信息
	G 代表信息操作完毕后,将暂存区的内容,追加到模式空间

取消$,每次输出信息的时候,同时输出缓存区和暂存区的内容
[root@localhost ~]# sed -e '/2/h' -e 'G' seq.txt
1
					# 此时暂存区为空
2
2					# 此时暂存区内容为2
3
2					# 此时暂存区内容为2
4
2					# 此时暂存区内容为2
结果显示:
	每次输出信息的时候,都会输出暂存区信息

numG 代表仅在num位置输出暂存区信息
[root@localhost ~]# sed -e '/2/h' -e '1G' seq4.txt
1
				# 在第1个位置输出暂存区信息
2
3
4

num1,num2G 代表仅在num1-num2范围的位置输出暂存区信息
[root@localhost ~]# sed -e '/2/h' -e '1,2G' seq4.txt
1
				# 在第1个位置输出暂存区信息
2
2				# 在第2个位置输出暂存区信息
3
4

num!G 代表在num之外的位置输出暂存区信息
[root@localhost ~]# sed -e '/2/h' -e '1!G' seq4.txt
1
2
2
3
2
4
2

实践2-暂存区使用后,清理模式空间内容

查看文件内容
[root@localhost ~]# cat -e seq.txt
1$
2$
3$

将匹配的内容转移至暂存区,然后清理模式空间
[root@localhost ~]# sed -e '/2/{h;d}' -e 'G' seq.txt
1
					# 此时暂存区为空
3					# 缓存区被清理,所以没有输出2
2
4
2

实践3-暂存区使用后,处理清理模式空间内容,

将匹配的内容转移至暂存区,然后清理模式空间,接着将暂存区信息输出到特定的位置
[root@localhost ~]# sed -e '/2/{h;d}' -e '/3/{G;}' seq4.txt
1
3
2
4
解读:
	暂存区的信息在/3/后面显示
	-e '/3/{G;}' 可以简写为 -e '/3/G'

g 从保持空间取出数据覆盖至模式空间
[root@localhost ~]# sed -e '/2/{h;d}' -e '/3/g' seq4.txt
1
2
4
解读:
	g 的作用,是将/3/匹配到的缓存区内容被暂存区的信息覆盖,则缓存区内容是2
	
x 把模式空间中的内容与暂存空间中的内容进行互换
[root@localhost ~]# sed -e '/2/{h;d}' -e '/3/{x;G}' seq4.txt
1
2				# 缓存区的3被暂存区的2替换了
3				# 暂存区的2被缓存区的3替换了
4
解读:
	x 的作用,是将/3/匹配到的缓存区内容和暂存区的信息交换,则缓存区内容是2,暂存区是3

其他实践

实践1-内容倒序实践

查看文件内容
[root@localhost ~]# cat seq.txt
1
2
3
4
除了第1行不输出暂存区,其他都输出暂存区值
[root@localhost ~]# sed -e '1!G' seq.txt
1
2
					# 第2处位置的暂存区为空
3
					# 第3处位置的暂存区为空
4
					# 第4处位置的暂存区为空

h将所有模式空间的内容覆盖到暂存区 
[root@localhost ~]# sed -e '1!G;h' seq.txt
1					# 暂存区在第1处缓存区不输出
----
2					# 缓存空间的1覆盖暂存区,然后在当前缓存区的2之后输出--追加
1					
---
3					# 缓存空间的21覆盖暂存区,然后在当前缓存区的3之后输出--追加
2					
1
--- 
4					# 缓存空间的321覆盖暂存区,然后在当前缓存区的4之后输出--追加
3
2
1

$!d 代表除了最后一个位置内容不删除,其他的都清除掉
[root@localhost ~]# sed -e '1!G;h;$!d' seq.txt
4
3
2
1

实践2-提取关键信息的前一行

查看文件内容
[root@localhost ~]# cat seq4.txt
1
2
3
4

除了第1行不被暂存区覆盖,其他缓存区被暂存区覆盖
[root@localhost ~]# sed -e '1!g' seq4.txt
1
 					# 第2处位置被暂存区的空覆盖
					# 第3处位置被暂存区的空覆盖
					# 第4处位置被暂存区的空覆盖 
将匹配到的3不打印,存放到暂存区,然后仅输出缓存区信息
[root@localhost ~]# sed -n '/3/!p;h' seq4.txt
1
2
4
解读:
	/3/ 匹配到第3行的内容3,使用!p不输出,然后依次将缓存区内容覆盖暂存区
	最终输出的时候仅有一个区显示,另一个区为空
[root@localhost ~]# sed -n '/3/g;p;h' seq4.txt
1		# 条件不匹配,交给h动作,缓存区的1会覆盖到暂存区
2		# 条件不匹配,交给h动作,缓存区的2会覆盖到暂存区
2		# 条件不匹配,交给h动作,暂存区的2通过g覆盖缓存区的3,所以输出2
4

{} 是shell中的一种独立区域,所做操作会直接影响当前的shell环境
[root@localhost ~]# sed -n '/3/{g;p};h' seq4.txt
2	
	{g;p} 代表直接将暂存区域的数据2覆盖缓存空间,由于{} 直接作用于shell环境
	所以p输出的时候,暂存区没有信息,缓存区域有2,仅仅输出2

16 sed命令

16.1 进阶实践

16.1.1 脚本实践

这一节,我们从 案例需求、简单实践、小结 三个方面来学习。

案例需求

需求

案例描述:
	搭建一个ftp服务器
	
属性要求
    1 不支持本地用户登录		
        local_enable=NO
    2 匿名用户可以上传 新建 删除	 
        anon_upload_enable=YES  anon_mkdir_write_enable=YES	
    3 匿名用户限速500KBps  
        anon_max_rate=500000

准备工作

使用sed获取ip地址
ipaddr=$(ifconfig eth0 | sed -n '2p' | sed -e 's/.*inet \(.*\) net.*/\1/g')
iptail=$(echo $ipaddr|cut -d'.' -f4)
ipremote=10.0.0.13
修改主机名
hostname server$iptail.localhost.com
echo "HOSTNAME=server$iptail.localhost.com" >> /etc/sysconfig/network
echo "$ipaddr server$iptail.localhost.com" >> /etc/hosts

环境部署

安装软件
yum -y install vsftpd lftp

备份配置
cp /etc/vsftpd/vsftpd.conf{,.default}

清理无效信息
sed -rn '/^(#|$)/!p' /etc/vsftpd/vsftpd.conf
sed -ri '/^#/d;/^$/d' /etc/vsftpd/vsftpd.conf

禁止本地登录
sed -i '/local_enable/c\local_enable=NO' /etc/vsftpd/vsftpd.conf

允许匿名操作
sed -i '$a anon_upload_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_mkdir_write_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_other_write_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_max_rate=512000' /etc/vsftpd/vsftpd.conf

启动服务
service vsftpd restart

数据操作

测试验证
chmod 777 /var/ftp/pub
cp /etc/hosts /var/ftp/pub

测试下载
cd /tmp
lftp $ipaddr <<end
cd pub
get hosts
exit
end

检查下载后的文件
ls /tmp/hosts
	注意:文件存在则匿名用户下载成功
	

测试上传、创建目录、删除目录等
cd /tmp
lftp $ipaddr << end
cd pub
mkdir test1
mkdir test2
put /etc/group
rmdir test2
exit
end

测试效果
tree /var/ftp/pub/ 
	注意:文件存在则匿名用户操作成功

简单实践

脚本实践

查看脚本内容
[root@localhost ~]# cat vsftpd_install_manager.sh
#!/bin/bash
# 功能:定制vsftpd环境部署功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
net_card="eth0"
vsftpd_conf='/etc/vsftpd/vsftpd.conf'

# 定制命令变量
ipaddr=$(ifconfig ${net_card} | sed -n '2p' | sed -e 's/.*inet \(.*\) net.*/\1/g')
iptail=$(echo $ipaddr|cut -d'.' -f4)

# 定制目标类型变量
target_type=(部署 下载 操作)

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台登录界面---------------"
    echo -e " 1: 部署软件 2: 下载测试  3: 操作测试"
    echo -e "-------------------------------------------\033[0m"
}

# 设定主机名
hostname_set(){
    hostname server$iptail.localhost.com
    echo "HOSTNAME=server$iptail.localhost.com" >> /etc/sysconfig/network
    echo "$ipaddr server$iptail.localhost.com" >> /etc/hosts
}

# 软件安装
softs_install(){
    read -p "请输入需要安装的软件,多个用空格隔开:" soft
    yum -y install $soft &>/dev/null
}

# 修改配置
update_config(){
    cp ${vsftpd_conf}{,.default}
    sed -ri '/^#/d;/^$/d' ${vsftpd_conf}
    sed -i '/local_enable/c\local_enable=NO' ${vsftpd_conf}
    sed -i '$a anon_upload_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_mkdir_write_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_other_write_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_max_rate=512000' ${vsftpd_conf}
    service vsftpd restart &>/dev/null && echo "vsftpd服务启动成功"
}

# 定制部署流程
soft_deploy(){
    hostname_set
    softs_install
    update_config
}

# 匿名用户测试下载动作
download_test(){
    # 准备数据目录及配套文件
    chmod 777 /var/ftp/pub
    cp /etc/hosts /var/ftp/pub

    # 测试演示
    cd /tmp
    lftp $ipaddr <<-end
    cd pub
    get hosts
    exit
        end
}

# 匿名用户测试操作权限
operator_test(){
    #测试上传、创建目录、删除目录等
    cd /tmp
    lftp $ipaddr <<-end
    cd pub
    mkdir test1 test2
    put /etc/group
    rmdir test2
    exit
        end
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始登录管理平台..."
        soft_deploy

    elif [ ${target_type[$target_id-1]} == "下载" ];then
        download_test
        # 测试结果
        if [ -f /tmp/hosts ];then
            echo "匿名用户下载成功"
            rm -f /tmp/hosts
        else
            echo "匿名用户下载失败"
        fi
    elif [ ${target_type[$target_id-1]} == "操作" ];then
        operator_test
        if [ -d /var/ftp/pub/test1 ];then
            if [ ! -d /var/ftp/pub/test2 ];then
                if [ -f /var/ftp/pub/group ];then
                    echo "匿名操作权限正常"
                    rm -rf /var/ftp/pub
                fi
            fi
        fi
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash vsftpd_install_manager.sh
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型: 1
开始登录管理平台...
请输入需要安装的软件,多个用空格隔开:vsftpd lftp
vsftpd服务启动成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型: 2
匿名用户下载成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型: 3
匿名操作权限正常
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型:

小结


17 awk实践

17.1 基础实践

17.1.1 基础知识

这一节,我们从 基础知识、语法解读、小结 三个方面来学习

基础知识

简介

	在日常计算机管理中,总会有很多数据输出到屏幕或者文件,这些输出包含了标准输出、标准错误输出。默认情况下,这些信息全部输出到默认输出设备---屏幕。然而,大量的数据输出中,只有一小部分是我们需要重点关注的,我们需要把我们需要的或者关注的这些信息过滤或者提取以备后续需要时调用。早先的学习中,我们学过使用grep来过滤这些数据,使用cut、tr命令提出某些字段,但是他们都不具备提取并处理数据的能力,都必须先过滤,再提取转存到变量,然后在通过变量提取去处理,比如:
内存使用率的统计步骤
	1) 通过free -m提取出内存总量,赋值给变量 memory_totle
	2)通过free -m提取出n内存使用量,赋值给变量memory_use
	3)通过数学运算计算内存使用率
	
	需要执行多步才能得到内存使用率,那么有没有一个命令能够集过滤、提取、运算为一体呢?当然,就是今天我要给大家介绍的命令:awk

awk简介

	awk全称 Aho Weinberger Kernighan报告生成器,awk的三个字母是来自于三个作者的首字母。它是一个功能非常强大的文档编辑工具,它不仅能以行为单位还能以列为单位处理文件,并且还具有格式化文本输出功能。目前它受自由软件基金会(FSF)进行开发和维护,通常也称它为 GNU AWK,AWK有多种版本:
    AWK:原先来源于 AT & T 实验室的的AWK
    NAWK:New awk,AT & T 实验室的AWK的升级版
    GAWK:即GNU AWK。所有的GNU/Linux发布版都自带GAWK,它与AWK和NAWK完全兼容	
原理解读
	awk 认为文件中的每一行是一条记录,记录与记录的分隔符为换行符,每一列是一个字段 字段与字段的分隔符默认是一个或多个空格或tab制表符.
	
	awk的工作方式是逐行读取文本数据,将每一行数据视为一条记录(record)每条记录以字段分隔符分成若干字段,然后输出各个字段的值.然后以查找匹配某个特定模式的文本行,并对这些文本执行制定动作。

语法解读

基本格式

格式:
    awk [参数] '[动作]' [文件名]
    awk [参数] –f 动作文件 var=value [文件名]
    awk [参数] 'BEGIN段 [动作] END段' [文件名]
注意:
	动作的格式  '匹配条件{打印动作}'
常见参数:
    -F				指定列的分隔符,默认一行数据的列分隔符是空格
    -f file 		指定读取程序的文件名
    -v var=value	自定义变量
awk程序运行优先级是:
    1 BEGIN: 在开始处理数据流之前执行,可选项
    2 动作: 如何处理数据流,必选项
    3 END: 处理完数据流后执行,可选项
常见动作
    print	显示内容
    $0		显示当前行所有内容
    $n		显示当前行的第n列内容,如果存在多个$n,它们之间使用逗号(,)隔开
注意:
	如果打印的内容是变量,则无需在变量两侧加上双引号,其他的都应该加双引号

其他功能

printf 格式化显示内容
	printf [-v var] format [item1,item2,...]
	注意:
		printf输出需要指定换行符号,format的格式必须与后面item对应
		常见格式:
			%c		显示字符的ASCII码		%d|i 	显示十进制整数		%e|E	显示科学计数法数值
			%f		显示浮点数			 %s		 显示字符串			%u	  显示无符号整数
			%%		显示%本身				
		修饰符:
			%#[.#]	第一个#控制显示宽度,第二个#表示小数点后的精度,例如%3.1f
			%-		左对齐,%-15s
			%+		显示数值的正负符号,%+d
常见内置变量
    FILENAME 	当前输入文件的文件名,该变量是只读的
    NR 			指定显示行的行号
    FNR			多文件时候,分别计数
    NF 			表示字段数量
    OFS 		输出格式的列分隔符,缺省是空格
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,指定输入时的换行符,原换行符($)仍有效
    ORS			输出记录分隔符,输出时用指定符号代替换行符
    ARGC|ARGV[n] 获取命令的参数个数|参数内容

准备工作

[root@localhost ~]# cat awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

17.1.2 基础语法

这一节,我们从 信息查看、定制查看、小结 三个方面来学习

信息查看

字段提取

字段提取:提取一个文本中的一列数据并打印输出,它提供了相关的内置变量。
    $0 表示整行文本
    $1 表示文本行中的第一个数据字段
    $2 表示文本行中的第二个数据字段
    $N 表示文本行中的第N个数据字段
    $NF 表示文本行中的最后一个数据字段
    NR 代表行的行号,在动作外部表示特定行
注意:
   如果打印多列信息,需要使用逗号隔开,否则是内容合并

实践1-打印列信息

打印第1列的内容
[root@localhost ~]# awk '{print $1}' awk.txt
nihao
nihao
nihao

打印第3列内容
[root@localhost ~]# awk '{print $3}' awk.txt
awk2
awk5
awk8

打印最后一列信息
[root@localhost ~]# awk '{print $NF}' awk.txt
awk3
awk6
awk9

打印所有内容
[root@localhost ~]# awk '{print $0}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

实践2-打印多列信息

打印第3,8列内容
[root@localhost ~]# awk '{print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8

打印信息时候,合并信息
[root@localhost ~]# awk '{print $1$3}' awk.txt
nihaoawk2
nihaoawk5
nihaoawk8

使用\t实现内容的分割,需要用""扩住
[root@localhost ~]# awk '{print $1"\t"$3}' awk.txt
nihao   awk2
nihao   awk5
nihao   awk8

打印列外普通信息
[root@localhost ~]# awk '{print "hello awk"}' awk.txt
hello awk
hello awk
hello awk
注意:
	如果没有$n的话,表示 print动作执行的次数与文件行数一致
	
打印fstab的关键信息
[root@localhost ~]# grep "^UUID" /etc/fstab |awk {'print $1,$3'}
UUID=5583bd7c-cc9f-4e19-b453-c224102f3ed5 xfs
UUID=cbd246cd-1df8-4fe7-9040-823cd0978837 xfs

实践3-行号信息输出

打印每列的行号信息
[root@localhost ~]# awk '{print NR,$0}' awk.txt
1 nihao awk1 awk2 awk3
2 nihao awk4 awk5 awk6
3 nihao awk7 awk8 awk9

按照行号打印对应列的内容
[root@localhost ~]# awk '{print NR, $NR}' awk.txt
1 nihao
2 awk4
3 awk8

实践4-打印特定行内容

制定行号打印信息
[root@localhost ~]# awk  'NR==1 {print NR,$1,$3}' awk.txt
1 nihao awk2
[root@localhost ~]# awk  'NR==2 {print NR,$1,$3}' awk.txt
2 nihao awk5

定制查看

简介

	awk默认的信息查看是以空格作为列分隔符的,而对于非空格作为分隔符的内容,我们需要借助于专门的语法实现信息的分割,这里主要用到以下知识:
常见参数:
    -F			指定列的分隔符,默认一行数据的列分隔符是空格
常见内置变量
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,指定输入时的换行符,原换行符($)仍有效
    注意:
    	一般情况下,在输出信息之前进行格式的调整,需要在BEGIN{}部分设定

实践1-简单演示

准备文件内容
[root@localhost ~]# head -n1 /etc/passwd > passwd.txt
[root@localhost ~]# cat passwd.txt
root:x:0:0:root:/root:/bin/bash

使用普通awk命令展示
[root@localhost ~]# awk '{print $1}' passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk '{print $2}' passwd.txt

[root@localhost ~]# awk '{print $0}' passwd.txt
root:x:0:0:root:/root:/bin/bash
结果显示:
	文件中只有一列,无法被awk默认分离

实践2-定制分隔符

设定分隔符实现信息的分隔效果
[root@localhost ~]# awk -F ':' '{print $0}' passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F ':' '{print $1}' passwd.txt
root
[root@localhost ~]# awk -F ':' '{print $1,$2}' passwd.txt
root x
[root@localhost ~]# awk -F ':' '{print $1,$7}' passwd.txt
root /bin/bash

基于-v方式设定输入分隔符的环境变量FS
[root@localhost ~]# awk -v FS=":" '{print $1FS$7}' passwd.txt
root:/bin/bash
虽然-v FS 和 -F 都可以设定入口数据的列分隔符,如果混用的话,-F的优先级高一点
[root@localhost ~]# awk -v FS=":" -F":" '{print $1FS$7}' passwd.txt
root:/bin/bash
[root@localhost ~]# awk -F":" -v FS=":"  '{print $1FS$7}' passwd.txt
root:/bin/bash

实践3-统计案例

获取网址域名信息
[root@localhost ~]# cat domain.txt
http://www.example.org/index.html
http://www.example.org/1.html
http://api.example.org/index.html
http://upload.example.org/index.html
http://img.example.org/3.html
http://search.example.org/2.html

对相关信息进行统计
[root@localhost ~]# awk -F[/]+ '{print $2}' domain.txt  | uniq -c
      2 www.sswang.org
      1 api.sswang.org
      1 upload.sswang.org
      1 img.sswang.org
      1 search.sswang.org
[root@localhost ~]# awk -F[/]+ '{print $(NF-1)}' domain.txt  | uniq -c 
      2 www.sswang.org
      1 api.sswang.org
      1 upload.sswang.org
      1 img.sswang.org
      1 search.sswang.org

17.1.3 显示语法

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk支持格式化输出相关信息。它主要依赖两种方法:
属性方法
    OFS 		输出格式的列分隔符,缺省是空格
    ORS			输出记录分隔符,输出时用指定符号代替换行符
print方法
	printf [-v var] format [item1,item2,...]
	注意:
		printf输出需要指定换行符号,format的格式必须与后面item对应
		常见格式:
			%c		显示字符的ASCII码		%d|i 	显示十进制整数		
			%e|E	显示科学计数法数值	  %u	  显示无符号整数
			%f		显示浮点数			 %s		 显示字符串			
			%%		显示%本身				
		修饰符:
			%#[.#]	第一个#控制显示宽度,第二个#表示小数点后的精度,例如%3.1f
			%-		左对齐,%-15s
			%+		显示数值的正负符号,%+d

简单实践

实践1-列输出分隔符实践

借助于-v方式为命令行输出相关信息
[root@localhost ~]# awk -F ':' -v OFS="~~~" '{print $1,$7}' passwd.txt
root~~~/bin/bash

借助于BEGIN语句设定环境变量
[root@localhost ~]# awk -F":" 'BEGIN{OFS="~"} {print NR,$1,$7}' passwd.txt
1~root~/bin/bash

在BEGIN内部同时实现多个环境变量
[root@localhost ~]# awk 'BEGIN{FS=":";OFS="-"}NR==1{print $1,$3,$NF}' passwd.txt
root-0-/bin/bash

实践2-行输入输出分隔符实践

定制输入分隔符
[root@server12 ~]# seq 7 | awk 'BEGIN{RS=""}{print $1,$2,$3,$4,$5,$6,$7}'
1 2 3 4 5 6 7

借助于-v方式为行分隔符输出相关信息
[root@localhost ~]# awk -F ':' -v ORS="|" '{print $NR,$0}' awk.txt
nihao awk1 awk2 awk3 nihao awk1 awk2 awk3| nihao awk4 awk5 awk6| nihao awk7 awk8 awk9|[root@localhost ~]#

借助于BEGIN语句设定环境变量
[root@localhost ~]# awk -F":" 'BEGIN{ORS="|"} {print NR,$0}' awk.txt
nihao awk1 awk2 awk3 nihao awk1 awk2 awk3|2 nihao awk4 awk5 awk6|3 nihao awk7 awk8 awk9|[root@localhost ~]#

实践3-printf格式化输出实践

使用print的格式信息
[root@localhost ~]# awk '{printf "%s\n",$1}' awk.txt
nihao
nihao
nihao
[root@localhost ~]# awk '{printf "%s",$1}' awk.txt; echo
nihaonihaonihao

多信息格式化嵌套
[root@localhost ~]# awk '{printf "%d--%s--%s\n", NR,$1,$NR}' awk.txt
1--nihao--nihao
2--nihao--awk4
3--nihao--awk8

数字的格式化输出
[root@localhost ~]# awk '{printf "%4.2f--%s\n", NR,$1}' awk.txt
1.00--nihao
2.00--nihao
3.00--nihao

字符的格式化输出
[root@localhost ~]# awk '{printf "%-8s%s\n", NR,$1}' awk.txt
1       nihao
2       nihao
3       nihao

17.1.4 优先级实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk为了体现程序的逻辑顺序,划分了三个代码段,其结构如下:

格式显示:
	BEGIN{}: 读入第一行文本之前执行的语句,一般用来初始化操作
    {}: 逐行处理的执行命令
    END{}: 处理完最后以行文本后执行,一般用来处理输出结果

简单实践

实践1-分结构实践

BEGIN设定数据处理的前置准备
[root@localhost ~]# awk 'BEGIN{OFS=":"} {print NR,$0}' awk.txt
1:nihao awk1 awk2 awk3
2:nihao awk4 awk5 awk6
3:nihao awk7 awk8 awk9

{}定制输出的内容样式
[root@localhost ~]# awk '{ print "第一列:"$1,"第二列:"$2 }' awk.txt
第一列:nihao 第二列:awk1
第一列:nihao 第二列:awk4
第一列:nihao 第二列:awk7

END设定信息处理完毕后的收尾动作
[root@localhost ~]# awk 'END{printf "----------------\n行数总计: %2d\n", NF}' awk.txt
----------------
行数总计:  4

实践2-优先级演示

[root@localhost ~]# awk -F: 'BEGIN{print "begin中的NR值:" NR}  NR==11 {print "命令中的NR值: " NR}END{print "END中的NR值: " NR}' /etc/passwd
begin中的NR值:0
命令中的NR值: 11
END中的NR值: 23
结果可知:
	begin的优先级 > 命令优先级 > END的优先级

实践3-组合演练

BEGIN 和 {} 实现信息的头部格式化
[root@localhost ~]# awk 'BEGIN{print "第一列\t第二列\n----------------"}{print $1"\t"$2}' awk.txt
第一列  第二列
----------------
nihao   awk1
nihao   awk4
nihao   awk7
完全组合实现信息的头部和尾部格式化操作
[root@localhost ~]# awk 'BEGIN{print "第一列\t第二列\n----------------";total=0;}{print $1"\t"$2;total = NR}END{printf "----------------\n行数总计: %2d\n", total}' awk.txt
第一列  第二列
----------------
nihao   awk1
nihao   awk4
nihao   awk7
----------------
行数总计:  3
统计文件格式化
[root@localhost ~]# awk -F":" 'BEGIN{printf "---------------------------\n%-12s|%9s|\n---------------------------\n","用户名","shell类型"}{printf "%-15s|%10s|\n---------------------------\n",$1,$7}END{printf "用户总数总: %2d\n", NR}' passwd.txt
---------------------------
用户名         |  shell类型|
---------------------------
root           | /bin/bash|
---------------------------
用户总数总:  1
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89

格式化显示
[root@localhost ~]# awk 'BEGIN{printf "----------------------\n|%-3s|%2s|%2s|%2s|\n----------------------\n","姓名","语文","数学","历史";} NR>=2 {printf "|%-3s|%4d|%4d|%4d|\n",$1,$2,$3,$4} END{printf "----------------------\n学生 总数总: %2d\n", NR}' course_scores.txt
----------------------
|姓名 |语文|数学|历史|
----------------------
|张三 | 100|  56|  99|
|李四 |  90|  68|  89|
|王五 |  50|  78|  67|
|赵六 |  80|  99|  89|
----------------------
学生总数总:  5

17.1.5 变量实践

这一节,我们从 基础知识、数组实践、小结 三个方面来学习

基础知识

简介

	在shell中,除了通用的字符串操作之外,还具有一些丰富灵活性的小功能,比如变量操作、数组操作类的。awk所支持的变量主要有两种类型:内置变量和自定义变量

变量类型

内置变量
	所谓的内置变量主要就是awk内部已经定制好的变量,我们可以直接拿过来使用,这些常见的方法我们基本上都演示过了,比如:
	FILENAME 	当前输入文件的文件名,该变量是只读的
	FIELDWIDTHS	以空格分隔的数字列表,用空格定义每个数据字段的精确宽度
    NR 			指定显示行的行号
    FNR			多文件时候,分别计数
    NF 			表示字段数量
    OFS 		输出格式的列分隔符,缺省是空格
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,指定输入时的换行符,原换行符($)仍有效
    ORS			输出记录分隔符,输出时用指定符号代替换行符
    ARGC|ARGV[n] 获取命令的参数个数|参数内容
自定义变量
	所谓的自定义变量,主要是根据实际情况,自己定义一些所谓的变量,然后再awk逻辑操作的过程中作为辅助性的措施。自定义变量的定制方法:
	-v var=value
	它可以在 命令行、BEGIN、{}、END 等位置进行使用

简单实践

实践1-内置变量的进阶使用

NF 查看当前所在目录
[root@localhost ~]# echo $PWD | awk -F / '{print $NF}'
root
[root@localhost ~]# cd /etc/sysconfig/
[root@localhost /etc/sysconfig]# echo $PWD | awk -F / '{print $NF}'
sysconfig

查看文件名称
[root@localhost ~]# echo /etc/sysconfig/network-scripts/ifcfg-eth0 | awk -F / '{print $NF}'
ifcfg-eth0

查看文件所在路径
[root@localhost ~]# echo /etc/sysconfig/network-scripts/ifcfg-eth0 | awk -F / '{print $(NF-1)}'
network-scripts

实践2-NR行号的作用

快速获取ip地址
[root@localhost ~]# ifconfig eth0 | awk 'NR==2{print $2}'
10.0.0.12
[root@localhost ~]# ifconfig eth0 | awk '/netmask/{print $2}'
10.0.0.12

快速获取文件行数
[root@localhost ~]# awk -F: 'END{print NR}' /etc/passwd
23
[root@localhost ~]# awk -F: 'END{print NR}' /etc/sysconfig/network-scripts/ifcfg-eth0
20

实践3-FNR 和 FILENAME 获取文件基本信息

获取文件基本信息
[root@localhost ~]# awk '{print FNR,FILENAME,$0}' /etc/issue /etc/redhat-release
1 /etc/issue \S
2 /etc/issue Kernel \r on an \m
3 /etc/issue
1 /etc/redhat-release CentOS Linux release 7.9.2009 (Core)
解析:
	FNR 获取文件内容,同时在前面增加行号
	FILENAME 获取文件名称
	
FIELDWIDTHS:重定义列宽并打印,注意不可以使用$0打印所有,因为$0是打印本行全内容,不会打印你定义的字段
[root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="5 2 8"}NR==1{print $1,$2,$3}' passwd.txt
root: x: 0:0:root

实践4-命令参数

ARGC 获取命令行参数的个数,包括awk命令
[root@localhost ~]# awk  'NR==1 {print ARGC }' awk.txt
2

ARGV:将命令行所有参数放到一个数组中,ARGV[下标] 获取所有参数
[root@localhost ~]# awk  'NR==1 {print ARGV[0] }' awk.txt
awk
[root@localhost ~]# awk  'NR==1 {print ARGV[1] }' awk.txt
awk.txt

实践5-自定义变量

变量实践
[root@localhost ~]# awk -v name='shuji' 'BEGIN{print name}'
shuji
[root@localhost ~]# awk 'BEGIN{name="shuji";print name}'
shuji

定制格式化输出
[root@localhost ~]# awk -F: '{age=36; address="beijing";print $1,age,address}' passwd.txt
root 36 beijing

17.2 进阶知识

17.2.1 赋值运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk本质上属于一种编程语言,所以它具有编程语言的一般功能,表达式、流程控制等基本上都在awk中具有想当程度的使用。这一节我们学习awk进阶到流程控制的一个基础知识点 -- 表达式。

表达式分类

	awk的表达式包括很多种类,常见的表达式有:
	算术操作符:+ - * / ^ %
	赋值操作符:= += -= /= ++ -- %= ^=
	比较操作符:== != > >= < <= 
	模式匹配符:~ 左边是否与右边匹配包含,!~ 是否不匹配
	逻辑操作符:与&&、或||、非!

简单实践

实践1-字符串赋值

[root@localhost ~]# awk -v name='shuji' 'BEGIN{print name}'
shuji

[root@localhost ~]# awk 'BEGIN{school="xigongda";print school}'
xigongda

实践2-数据赋值

命令区域段内进行数据赋值操作
[root@localhost ~]# echo | awk '{i=10;print i+=1}'     
11
[root@localhost ~]# echo | awk '{i=10;print i++,i}'
10 11
[root@localhost ~]# echo | awk '{i=10;print ++i,i}'
11 11
[root@localhost ~]# echo | awk '{i=10;print --i,i}'
9 9
[root@localhost ~]# echo | awk '{i=10;print i--,i}'
10 9

在BEGIN段是可以的,由于END段主要是收尾的信息显示,所以基本不做计算层次的功能
[root@localhost ~]# awk 'BEGIN{i=0;print i++,i}'
0 1
[root@localhost ~]# awk 'BEGIN{i=0;print ++i,i}'
1 1

实践3-变量赋值

-v 设定变量进行赋值操作
[root@localhost ~]# awk -v n=0 'n++' awk.txt
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9
[root@localhost ~]# awk -v n=0 '!n++' awk.txt
nihao awk1 awk2 awk3
结果显示:
	当递增与!同时存在的时候,!优先生效

实践4-数组赋值

在awk中可以设定数组
[root@localhost ~]# awk 'BEGIN{array[0]=100;print array[0]}'
100

17.2.2 数学运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的数学运算,其实就是我们平常所说的二元运算,常见的运算符号有:
		+ - * / ^ %

简单实践

实践1-普通数学运算

[root@localhost ~]# awk 'BEGIN{print 100+3 }'
103
[root@localhost ~]# awk 'BEGIN{print 100-3 }'
97
[root@localhost ~]# awk 'BEGIN{print 100*3 }'
300
[root@localhost ~]# awk 'BEGIN{print 100/3 }'
33.3333
[root@localhost ~]# awk 'BEGIN{print 100**3 }'
1000000
[root@localhost ~]# awk 'BEGIN{print 100%3 }'
1

实践2-BEGIN和变量运算

[root@localhost ~]# awk -v 'count=0' 'BEGIN{count++;print count}'
1
[root@localhost ~]# awk -v 'count=0' 'BEGIN{count--;print count}'
-1

实践3-案例解读

在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
[root@localhost ~]# awk 'BEGIN{printf "---------------------------\n|%-3s|%2s|%2s|%2s|%2s|\n---------------------------\n","姓名","语文","数学","历史","总分";yu=0;shu=0;li=0;total} NR>=2 {yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4; printf "|%-3s|%4d|%4d|%4d|%4d|\n",$1,$2,$3,$4,$2+$3+$4} END{printf "---------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n学生总数总: %2d\n","合计",yu,shu,li,total,NR}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 268|
学生总数总:  4

17.2.3 逻辑运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的逻辑运算,其实指的就是 与或非的操作。基本语法格式如下:
		与&& - 并且关系
        或|| - 或者关系
        非! - 取反关系

简单实践

实践1-基本逻辑运算

与运算:真真为真,真假为假,假假为假
[root@localhost ~]# awk 'BEGIN{print 100>=2 && 100>=3 }'
1
[root@localhost ~]# awk 'BEGIN{print 100>=2 && 1>=100 }'
0

或运算:真真为真,真假为真,假假为假
[root@localhost ~]# awk 'BEGIN{print 100>=2 || 1>=100 }'
1
[root@localhost ~]# awk 'BEGIN{print 100>=200 || 1>=100 }'
0

实践2-文件逻辑运算

[root@localhost ~]# awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
root
python
[root@localhost ~]# awk -F: '$3==0 || $3>=1000 {print $1,$3}' /etc/passwd 
root 0
python 1000
[root@localhost ~]# awk -F: '$3==0 && $3>=1000 {print $1,$3}' /etc/passwd   
[root@localhost ~]# awk -F: '!($3<1000) {print $1,$3}' /etc/passwd           
python 1000
[root@localhost ~]# awk -F: 'NR>=1&&NR<=2{print NR,$0}' /etc/passwd 
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin

实践3-非关系

[root@localhost ~]# awk 'BEGIN{print i}'

[root@localhost ~]# awk 'BEGIN{print !i}'
1
[root@localhost ~]# awk -v i=10 'BEGIN{print !i}'
0
[root@localhost ~]# awk -v i=-3 'BEGIN{print !i}'
0
[root@localhost ~]# awk -v i=0 'BEGIN{print !i}'
1
[root@localhost ~]# awk -v i=abc 'BEGIN{print !i}'
0
[root@localhost ~]# awk -v i='' 'BEGIN{print !i}'
1

17.2.4 匹配运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的匹配运算,主要指的是关键字无法精确性的匹配相关信息了,但是我们可以结合一些关键字信息进行模糊的匹配。对于匹配运算来说,它有一些需要注意的事情,具体内容如下:
注意事项:
	如果没有指定,为空模式,匹配每一行
	如果指定”/匹配条件/”,则表示仅处理能够匹配到的内容
	如果指定关系表达式,只有结果为真的情况下,才会被处理
		真:结果为非0值,非空字符串,空格也是真
		假:结果为空字符串或0值,数值不用加””

简单实践

实践1-真假值匹配

假值匹配
[root@localhost ~]# awk '"" {print $1,$3}' awk.txt
[root@localhost ~]# awk '0 {print $1,$3}' awk.txt

非零真值匹配
[root@localhost ~]# awk '"aaa" {print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8
[root@localhost ~]# awk '9 {print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8
[root@localhost ~]# awk -v n=8 'n{print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8

实践2-内容匹配

内容匹配
[root@localhost ~]# awk -F ':' '$1 ~ "^ro" {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F ':' '$1 ~ "ftp" {print $0}' /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
[root@localhost ~]# awk -F ':' '$1 ~ "^[a-d].*" {print $0}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin

内容不匹配
[root@localhost ~]# awk -F ':' '$1 !~ "^ro" {print $0}' /etc/passwd
[root@localhost ~]# awk -F ':' '$1 !~ "^[a-r].*" {print $0}' /etc/passwd
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin

实践3-简单案例

准备不规则格式文件
[root@localhost ~]# cat zhengli.txt
  http://www.sswang.org/index.html
      http://www.sswang.org/1.html
    http://api.sswang.org/index.html
asdfasdfsd
  adfsdf

文件的规整
[root@localhost ~]# awk '/^[ \t]*/{print NR"--->"$1}' zhengli.txt
1--->http://www.sswang.org/index.html
2--->http://www.sswang.org/1.html
3--->http://api.sswang.org/index.html
4--->asdfasdfsd
5--->adfsdf

实践4-扩展实践

多值匹配打印
[root@localhost ~]# awk 'i=1;j=1{print $1,$3}' awk.txt
nihao awk1 awk2 awk3
nihao awk2
nihao awk4 awk5 awk6
nihao awk5
nihao awk7 awk8 awk9
nihao awk8
[root@localhost ~]# awk 'i=1{print $0};j=1{print $1,$3}' awk.txt
nihao awk1 awk2 awk3
nihao awk2
nihao awk4 awk5 awk6
nihao awk5
nihao awk7 awk8 awk9
nihao awk8

假值匹配
[root@localhost ~]# awk '0' awk.txt
真值匹配
[root@localhost ~]# awk '!0' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

非零真值匹配
[root@localhost ~]# awk '0-2' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9
零值匹配
[root@localhost ~]# awk '0-0' awk.txt

非零真值匹配
[root@localhost ~]# awk -v n=0 '++n' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

17.2.5 内置函数1

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	在awk内部预制了一些函数,借助于这些函数,我们可以实现相关场景的快速操作。这些内置函数的常见类型有:
数值类内置函数
	int(expr)     截断为整数:int(123.45)和int("123abc")都返回123,int("a123")返回0
    sqrt(expr)    返回平方根
    rand()        返回[0,1)之间的随机数,默认使用srand(1)作为种子值
    srand([expr]) 设置rand()种子值,省略参数时将取当前时间的epoch值(精确到秒的epoch)作为种子值
字符串类内置函数
	sprintf(format, expression1, ...):返回格式化后的字符串
	length():返回字符串字符数量、数组元素数量、或数值转换为字符串后的字符数量
	strtonum(str):将字符串转换为十进制数值
        如果str以0开头,则将其识别为8进制
        如果str以0x或0X开头,则将其识别为16进制
	tolower(str):转换为小写
	toupper(str):转换为大写
	index(str,substr):从str中搜索substr(子串),返回搜索到的索引位置,搜索不到则返回0
数据操作内置函数
	substr(string,start[,length]):从string中截取子串
	split(string, array [, fieldsep [, seps ] ]):将字符串分割后保存到数组array中
	match(string,reg[,arr]):使用reg正则规则匹配string信息,默认返回匹配的索引,可以将内容存到数组

简单实践

实践1-数据值函数实践

int数据取整
[root@localhost ~]# awk 'BEGIN{print int(123.45)}'
123
[root@localhost ~]# awk 'BEGIN{print int(123.565)}'
123

sqrt数据求平方根
[root@localhost ~]# awk 'BEGIN{print sqrt(9)}'
3

rand()求0-1的随机数
[root@localhost ~]# awk 'BEGIN{print rand()}'
0.237788
srand() 设定rand()的随机权重,权重固定,rand()值固定
[root@localhost ~]# awk 'BEGIN{srand();print rand()}'
0.858697
[root@localhost ~]# awk 'BEGIN{srand();print rand()}'
0.587366
[root@localhost ~]# awk 'BEGIN{srand(2);print rand()}'
0.610198
[root@localhost ~]# awk 'BEGIN{srand(2);print rand()}'
0.610198

结合srand()获取随机整数
[root@localhost ~]# awk 'BEGIN{srand();print int(22*rand())}'
10
[root@localhost ~]# awk 'BEGIN{srand();print int(22*rand())}'
7

实践2-字符串实践

sprintf设定数据基本样式
[root@localhost ~]# awk 'BEGIN{a=sprintf("%s-%d-%s","abc",23,"ert"); print a}'
abc-23-ert

length获取字符串长度
[root@localhost ~]# awk 'BEGIN{v="nsfadsafdsaf";print length(v)}'
12

tolower转换为小写
[root@localhost ~]# awk 'BEGIN{v="ADMIN";print tolower(v)}'
admin

toupper转换为大写
[root@localhost ~]# awk 'BEGIN{v="nsfad";print toupper(v)}'
NSFAD

index查找子字符串的位置
[root@localhost ~]# awk 'BEGIN{str="nsfad";print index(str, "fa")}'
3
[root@localhost ~]# awk 'BEGIN{str="nsfad";print index(str, "ns")}'
1
[root@localhost ~]# awk 'BEGIN{str="nsfad";print index(str, "ad")}'
4	

实践3-其他操作

substr字符串截取
[root@localhost ~]# awk 'BEGIN{v="abcdefgh";print substr(v,3)}'
cdefgh
[root@localhost ~]# awk 'BEGIN{v="abcdefgh";print substr(v,3,3)}'
cde

split切割字符串
[root@localhost ~]# awk 'BEGIN{split("abc-def-gho-pq",arr,"-",seps); print length(arr), arr[3], seps[1]}'
4 gho -
[root@localhost ~]# awk 'BEGIN{split("abcde",arr,"-");print arr[1]}'
abcde
[root@localhost ~]# echo "12:34:56" | awk '{split($0,a,":");print a[1],a[2],a[3]}'
12 34 56

match字符串匹配
[root@localhost ~]# awk 'BEGIN{str="safdsajfkdsajlfjdsl";print match(str,"j.*s")}'
7
[root@localhost ~]# awk 'BEGIN{str="safdsajfkdsajlfjdsl";match(str,"j.*s",arry);print arry[0]}'
jfkdsajlfjds

小结

17.2.6 内置函数2

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk还内置了一些关于时间和数据类型的内置函数,具体信息如下
时间类内置函数
	mktime("YYYY MM DD HH mm SS [DST]"):构建一个时间,构建失败则返回-1
	systime():返回当前系统时间点,返回的是秒级epoch值
	strftime([format [, timestamp [, utc-flag] ] ]):将时间格式转换为字符串
数据类型相关内置函数:
    isarray(var):测试var是否是数组,返回1(是数组)或0(不是数组)
    typeof(var):返回var的数据类型,有以下可能的值:
        “array”:是一个数组
        “regexp”:是一个真正表达式类型,强正则字面量才算是正则类型,如@/a.*ef/
        “number”:是一个number
        “string”:是一个string
        “strnum”:是一个strnum,参考strnum类型
        “unassigned”:曾引用过,但未赋值,例如”print f;print typeof(f)”
        “untyped”:从未引用过,也从未赋值过

简单实践

实践1-时间函数实践

systime返回当前系统时间点
[root@localhost ~]# awk 'BEGIN{print systime()}'
1655618798

mktime设置一个时间
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 11 32 19")}'
1966822339

strftime获取格式日期
[root@localhost ~]# awk 'BEGIN{print strftime()}'
日 6月 19 14:11:35 CST 2022
结合xargs设定时间格式
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 11 32 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 11:32:19

# 设定2032-04-29 11:00:19基础上减1分钟
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 11 -1 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 10:59:19

# 设定2032-04-29 00:23:19基础上加1小时
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 +1 23 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 01:23:19

# 设定时间格式
[root@localhost ~]# awk 'BEGIN{print strftime("%F %T %z", mktime("2032 4 29 11 32 19"))}'
2032-04-29 11:32:19 +0800

实践2-格式判断实践


isarray判断是否是一个数组
awk 'BEGIN{v="abcdefgh";print isarray(v)}'
awk 'BEGIN{v=("aa" "bb" 11);print isarray(v)}'

awk '
  BEGIN{
    for(idx in PROCINFO){
      if(typeof(PROCINFO[idx]) == "array"){
        continue
      }
      print idx " -> "PROCINFO[idx]
    }
  }'

17.3 逻辑控制

17.3.1 if条件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的条件判断,与我们之前学习的shell条件表达式基本效果一致,所以awk里面仍然会有if条件控制语句,只不过表现样式是shell的if语句的单命令格式而已。在shell中,条件控制主要有两类表现样式:
样式1:普通的非语句
	if(条件){执行语句;...}else {执行语句;...}
	if(条件1){执行语句1}else if(条件2){执行语句2}else{执行语句3}
样式2:三元表达式
	条件表达式?真值表达式:假值表达式
	可以类比为:
        if(条件){
            执行语句
        } else {
            执行语句
        }

简单实践

实践1-单if表达式

[root@localhost ~]# awk -F: '{if($0 ~ "/bin/bash")print$1}' /etc/passwd
root
python
[root@localhost ~]# awk -F: '{if($NF=="/bin/bash")print$1}' /etc/passwd
root
python
[root@localhost ~]# awk  -F' ' '{if(NR>9)print $1}' /etc/fstab
UUID=0f398cc3-6e97-4b25-9666-d848f4dd302b
统计只有四个字母的用户
[root@localhost ~]# awk -F":" 'length($1)==4{i++;print $1} END{print "count is "i}' /etc/passwdroot
sync
halt
mail
dbus
sshd
lisi
count is 7
[root@localhost ~]# awk -F":" '{if(length($1)==4){i++;print $1}} END{print "count is "i}' /etc/passwd
root
sync
halt
mail
dbus
sshd
lisi
count is 7

统计磁盘的使用量
[root@localhost ~]# df -h | awk '{if($(NF-1)>10){print $NF":"$(NF-1)}}'
挂载点:已用%
/:17%
/boot:15%

实践2-双分支if实践

判断内存使用量
[root@localhost ~]# free -m | awk '/^Mem/{if($3 / $2 *100 >= 10){print "内存使用量过高"} else{print "内存使用量正常"}}'
内存使用量正常

[root@localhost ~]# free -m | awk '/^Mem/{if($3 / $2 *100 >= 2){print "内存使用量过高"} else{print "内存使用量正常"}}'
内存使用量过高

实践3-多if表达式

标准格式:
[root@localhost ~]# awk '
  BEGIN{
    score = 999
    if (score >=0 && score < 60) {
      print "不及格"
    } else if (score >= 60 && score < 90) {
      print "优秀"
    } else if (score >= 90 && score <= 100) {
      print "你还是人么"
    } else {
      print "你觉得我信么?"
    }
  }
'
你觉得我信么?

简单格式:
[root@localhost ~]# awk -F: 'BEGIN{i=0;j=0}{if($3<=500){i++}else{j++}}END{print "uid小于500:"i,"uid大于500:"j}' /etc/passwd          
uid小于500:20 uid大于500:3
[root@localhost ~]# echo "nan" | awk '{if ($1=="nan") print "你是男性"; else if ($1=="nv") print "你是男性"; else print "你的性别未知"}'
你是男性

实践4-三元表达式

判断用户类型
[root@localhost ~]# awk -F":" '{$3>=1000?usertype="普通用户":usertype="系统用户";printf "%-6s:%6s\n",$1,usertype}' /etc/passwd | head -n 4
root  :  系统用户
bin   :  系统用户
daemon:  系统用户
adm   :  系统用户

判断磁盘的容量
[root@localhost ~]# df -h | awk '/^\/dev\/sd/{$(NF-1)>10?disk="full":disk="OK";print $1,$(NF-1),disk}'
/dev/sda2 14% full
/dev/sda1 15% full
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
姓名  语文	数据  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89

学生基本信息统计
[root@localhost ~]# awk -v total=0 'NR>=2, $2+$3+$4>=240?type="优秀":type="良好" {total=$2+$3+$4; printf "姓名: %s, 总分: %4d, 状态: %s\n",$1,$2+$3+$4,type}' course_scores.txt
姓名: 张三, 总分:  255, 状态: 优秀
姓名: 李四, 总分:  247, 状态: 优秀
姓名: 王五, 总分:  195, 状态: 良好
姓名: 赵六, 总分:  268, 状态: 优秀

结合BEGIN和END对每个学生的总分进行统计判断后格式化输出
[root@localhost ~]# awk 'BEGIN{printf "--------------------------------\n|%-3s|%2s|%2s|%2s|%2s|%2s|\n--------------------------------\n","姓名","语文","数学","历史","总分","状态";yu=0;shu=0;li=0;total} NR>=2, $2+$3+$4>=240?type="优秀":type="良好" {yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4; printf "|%-3s|%4d|%4d|%4d|%4d|%-2s|\n",$1,$2,$3,$4,$2+$3+$4,type} END{printf "--------------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n学生总数总: %2d\n","合计",yu,shu,li,total,NR}' course_scores.txt
--------------------------------
|姓名 |语文|数学|历史|总分|状态|
--------------------------------
|张三 | 100|  56|  99| 255|优秀|
|李四 |  90|  68|  89| 247|优秀|
|王五 |  50|  78|  67| 195|良好|
|赵六 |  80|  99|  89| 268|优秀|
--------------------------------
|合计 | 320| 301| 344| 268|
学生总数总:  4

17.3.2 switch条件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中有一种简单的条件控制语法switch,相较于if来说,switch分支语句功能较弱,只能进行等值比较或正则匹配,一般结合case方式来使用。
语法格式
    switch (表达式) {
        case 值1|regex1 : 执行语句1;break
        case 值2|regex2 : 执行语句2;break
        case 值3|regex3 : 执行语句3;break
        ...
        [ default: 执行语句 ]
    }
注意:
	表达式的结果符合awk的逻辑运算,表达式成功返回0,表达式失败返回1
	因为switch本身外侧没有流程控制,所以,一般情况下,当外侧没有循环控制的时候,会结合break来使用
	一旦遇到break,代表退出当前循环

简单实践

实践1-简单实践

无break效果
[root@localhost ~]# seq 2 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
    case "0":
        print "偶数: ", $1
    default:
        print "嘿嘿"
}}'
奇数:  1
偶数:  1
嘿嘿
偶数:  2
嘿嘿
结果显示:
	没有break的时候,每一次都会走一个switch的完整循环
结合break的基本语法实践
[root@localhost ~]# seq 5 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
        break
    case "0":
        print "偶数: ", $1
        break
    default:
        print "嘿嘿"
        break
}}'
奇数:  1
偶数:  2
奇数:  3
偶数:  4
奇数:  5

结果显示:
	有了break效果好多了

实践2-结合文本来进行实践

[root@localhost ~]# awk '{switch (NR % 2) {
    case "1":
        print "奇数行: ", $0
        break
    case "0":
        print "偶数行: ", $0
        break
}}' awk.txt
奇数行:  nihao awk1 awk2 awk3
偶数行:  nihao awk4 awk5 awk6
奇数行:  nihao awk7 awk8 awk9
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
姓名  语文	数据  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89
[root@localhost ~]# awk -v total=0 '
BEGIN{
    printf "\t学生成绩信息统计\n"
    printf "-----------------------------------\n"
}
NR>=2 {switch ($2+$3+$4 >= 240) {
    case 0:
        type="优秀";break
    case 1:
        type="良好";break
}
total=$2+$3+$4
printf "姓名: %-3s 总分: %4d,状态: %-2s\n",$1,$2+$3+$4,type
}' course_scores.txt
        学生成绩信息统计
-----------------------------------
姓名: 张三  总分:  255,状态: 良好
姓名: 李四  总分:  247,状态: 良好
姓名: 王五  总分:  195,状态: 优秀
姓名: 赵六  总分:  268,状态: 良好

17.3.3 for循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中,支持一些逻辑循环的功能,比如 for、while等。实际的过程中,我们往往会结合数组元素进行信息的统计
for语句标准格式
	for (三元表达式) {
        执行语句
    }

    for (变量 in 列表) {
        执行语句
    }
    
for语句简写格式
	for(表达式) {执行语句;...}
	
注意:
	for语句可以结合if语句进行操作

简单实践

实践1-for循环语法

普通for循环实践
[root@localhost ~]# for((i=1,sum=0;i<=100;i++));do let sum+=i;done;echo $sum
5050

awk的for标准语法
[root@localhost ~]# awk 'BEGIN {
    sum=0
    for (i=1;i<=100;i++) {
        sum+=i
    }
    print sum
}'
5050

awk for循环单行实践
[root@localhost ~]# awk 'BEGIN{ sum=0; for(i=1;i<=100;i++) {sum+=i}; print sum}'
5050

实践2-文本实践

文本信息的基本统计
[root@localhost ~]# seq 10 | paste -s | tr -s "\t" " " > num.txt
[root@localhost ~]# cat num.txt
1 2 3 4 5 6 7 8 9 10
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){sum+=$i};print sum}' num.txt
55
学生信息统计
[root@localhost ~]# awk 'NR>=2 {
    total=0
    for (i=2;i<=NF;i++) {
        total+=$i
    }
    print "学生姓名: "$1", 课程总分: "total
}' course_scores.txt
学生姓名: 张三, 课程总分: 255
学生姓名: 李四, 课程总分: 247
学生姓名: 王五, 课程总分: 195
学生姓名: 赵六, 课程总分: 268

17.3.4 while循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk支持while相关的循环处理,它主要有两种表现样式
样式1
    while(条件){
        执行语句
    }

样式2
    do {
        执行语句
    } while(条件)
    
简写样式
	while(condition){执行语句;...}
	do {条件;...} while(条件)

简单实践

实践1-while实践

while标准格式
[root@localhost ~]# awk 'BEGIN {
    i=1;sum=0
    while(i<=100) {
        sum+=i
        i++
    }
    print sum
}'
5050

求和1~100
[root@localhost ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print "sum="sum}'
sum=5050

指定数据进行求和
[root@localhost ~]# read -p "请输入一个数字 : " NUM;awk -v num=$NUM 'BEGIN{i=1;sum=0;while(i<=num){sum+=i;i++};print "1~$NUM的和为="sum}'
请输入一个数字 :34
1~$NUM的和为=595

最大值和最小值
[root@localhost ~]# echo '0 234 252 3246 2245 2345 4536 3754 32 345 323 234 3 1' > num.txt
[root@localhost ~]# awk '{min=$1;max=$1;while(i<=NF){if(max<$i)max=$i;if(min>$i)min=$i;i++};print "max:"max,"min:"min}' num.txt
max:4536 min:0

标准求和
[root@localhost ~]# awk '{
sum=0
i=1
while (i<=NF) {
   sum+=$i
   i++
}
print sum
}'  num.txt
17550

实践2-dowhile实践

基本格式
[root@localhost ~]# awk 'BEGIN{i=0;do {print i;i++} while(i<5)}'
0
1
2
3
4

求和1~100
[root@localhost ~]# awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print total}'
5050

17.3.5 流程控制

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk提供了很多的功能实践,尤其是与逻辑控制相关的,其实awk为了更好的进行这些流程的控制,它也提供了很多的控制语法,这些语法如下:
	continue 	中断本次循环
	break 		中断整个循环
	next 		可以提前结束对匹配行处理而直接进入下一行处理
	nextfile 	进阶版的next,可以提前结束对匹配行处理,直接读取下一个文件进行循环处理
	exit 		退出awk程序
		END代码段属于exit一部分,可以在BEGIN或main段中执行exit操作--执行END语句块。

简单实践

实践1-continue实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5)continue
    print(i)
  }
}'
0
1
2
3
4
6
7
8
9
continue 求奇|偶数和
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2!=0)continue;sum+=i}print sum}'
2550

实践2-break实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5){
      break
    }
    print(i)
  }
}'
0
1
2
3
4

循环求和,当加值为66的时候,停止运算
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)break;sum+=i}print sum}'
2145

实践3-next实践

查看文件内容
[root@localhost ~]# cat awk.txt -n
     1  nihao awk1 awk2 awk3
     2  nihao awk4 awk5 awk6
     3  nihao awk7 awk8 awk9
     
输出指定行外的其他行   
[root@localhost ~]# awk 'NR==3{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
[root@localhost ~]# awk 'NR==2{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk7 awk8 awk9

实践4-nextfile实践

nextfile代表在
[root@localhost ~]# awk 'FNR==3{nextfile}{print}' awk.txt awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6

实践5-exit实践

处理一个文件就退出awk程序
[root@localhost ~]# awk '{print $2}' awk.txt
awk1
awk4
awk7
[root@localhost ~]# awk '{print $2; exit}' awk.txt
awk1

只处理三行匹配的内容,后续结束
[root@localhost ~]# awk '/nologin/{i++;if(i<=3){print $0}else{exit;}}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
多个阶段实践exit
[root@localhost ~]# awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
1
[root@localhost ~]# awk 'BEGIN{exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
2
[root@localhost ~]# echo | awk 'BEGIN{}{exit 111}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
111
[root@localhost ~]# echo | awk 'BEGIN{}{}END{if(flag){exit}}'
[root@localhost ~]# echo $?
0

小结


17.3.6 数组实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在使用awk的时候,其实在一些业务的数据分析场景下,使用频率最多的应该是数组,而且数据往往与前面学习的逻辑流程控制组合在一起使用,数组一般很少单独使用。
	其基本语法格式如下:
定义数组:
	array[index表达式]
	index表达式:
        可使用任意字符串;字符串要使用双引号括起来
        如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为空串
        若要判断数组中是否存在某元素,要使用index in array格式进行遍历

遍历数组中的元素,要使用for循环
	for(var in array){for-body}
	注意:
		var会遍历array的每个索引,var不能用关键字index

简单实践

实践1-数组简单实践

数组的简单定义和调用
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;print array["yuwen"],array["shuxue"],array["lishi"]}'
78 89 99

数组的遍历操作
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;for(i in array){print array[i]}}'
78
89
99

内容自动遍历
[root@localhost ~]# cat passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){arrar[i]=i} print $7}' passwd.txt
/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){array[i]=$i};for(j in array){print array[j]}}' passwd.txt
0
root
/root
root
x
0

实践2-数组数据去重

文件内容准备
[root@localhost ~]# echo -e "a\nb\na\ncc" > array.txt
[root@localhost ~]# cat array.txt
a
b
a
cc

数组去重的逻辑
[root@localhost ~]# awk 'array[$0]++' array.txt
a
[root@localhost ~]# awk '!array[$0]++' array.txt
a
b
cc
语法解读
	array[$0]++  第一次执行array[$0],由于是array[a]不为空,所以为真,然后array[a]"++"计数后变成1,并打印数组元素a
	!array[$0]++ 第一次效果与上面一样,执行到第三行的时候,因为存在array[a]已存在,为真,然后!array[a]为假,不再输出打印当前内容,从而达到去重的效果

实践3-统计计数

统计计数
[root@localhost ~]# echo "a.b.c,c.d" |awk -F'[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1

17.3.7 自定义函数

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

需求

	虽然awk提供了内置的函数来实现相应的内置函数,但是有些功能场景,还是需要我们自己来设定,这就用到了awk的自定义函数功能了。
	awk的函数目的,与shell的函数目的一致,都是提高代码的复用能力和功能灵活性

函数

语法格式
    function 函数名(参数1, 参数2, ...)
    {
        函数体代码
    }
注意:
	函数名不能用awk的关键字信息

简单实践

实践1-简单函数实践

[root@localhost ~]# awk '
function add_func(num1, num2)
{
  return num1 + num2
}
function sub_func(num1, num2)
{
  if (num1 > num2)
    return num1 - num2
  return num2 - num1
}
BEGIN {
  sum_result=add_func(10, 20)
  print "两值之和为: "sum_result
  sub_result=sub_func(10, 20)
  print "两值之差为: "sub_result
}'
两值之和为: 30
两值之差为: 10

实践2-数据统计计算

在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
[root@localhost ~]# awk '
function head_func() {
  printf "---------------------------\n|%-3s|%2s|%2s|%2s|%2s|\n---------------------------\n","姓名","语文","数学","历史","总分"
}
function body_func(arg1, arg2, arg3, arg4,arg5){
  printf "|%-3s|%4d|%4d|%4d|%4d|\n",arg1,arg2,arg3,arg4,arg5
}
function tail_func(arg1, arg2, arg3, arg4,arg5){
  printf "---------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n---------------------------\n学生总数总: %2d\n","合计",arg1,arg2,arg3,arg4,arg5
}
BEGIN {
  head_func()
  yu=0;shu=0;li=0;total
}{
  yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4
  body_func($1,$2,$3,$4,$2+$3+$4)
}END{
  tail_func(yu,shu,li,total,NR)
}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 268|
---------------------------
学生总数总:  4

小结


17.3.8 综合实践

这一节,我们从 网络实践、文件实践、小结 三个方面来学习

网络实践

简介

	所谓的网络实践,主要是借助于awk的数组功能,进行站点的信息统计操作。

准备网络环境

安装软件
yum install nignx -y

重启nginx
[root@localhost ~]# systemctl restart nginx.service

重置网站首页
[root@localhost /etc/nginx]# echo 'hello nginx' > /usr/share/nginx/html/index.html
[root@localhost /etc/nginx]# curl localhost
hello nginx
[root@localhost /etc/nginx]# curl localhost/nihao -I -s | head -1
HTTP/1.1 404 Not Found

模拟外网访问
[root@localhost ~]# curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: 2.2.2.2" | head -1
HTTP/1.1 200 OK
[root@localhost ~]# tail -n1 /var/log/nginx/access.log
10.0.0.12 - - [19/Jun/2022:18:04:20 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "2.2.2.2"

准备ip地址文件
[root@localhost ~]# cat ip.txt
112.64.233.130
114.101.40.170
123.15.24.200
125.46.0.62
223.243.252.155
122.228.19.92
218.2.226.42
124.205.143.213
218.60.8.99
125.123.120.130
123.139.56.238
218.60.8.83
222.240.184.126
222.90.110.194
1.196.160.46
222.217.125.153
163.125.156.249
27.50.142.132
61.145.182.27
222.249.238.138
218.64.69.79
103.10.86.203
14.155.112.17
27.191.234.69
60.211.218.78
124.237.83.14
59.44.247.194
114.249.119.45
125.123.65.177
14.115.106.222
准备站点访问测试脚本
[root@localhost /etc/nginx]# cat curl_web_site.sh
#!/bin/bash
# 功能:模拟外网访问网站
while true
do
  cat ip.txt | while read ip
  do
   NUM=$(echo $ip | cut -d"." -f 4)
   for i in $(seq $NUM)
   do
     curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: $ip" >> /dev/null
     curl http://10.0.0.12/$NUM/ -s >> /dev/null
   done
   sleep 1
  done
done
脚本测试效果
[root@localhost ~]# /bin/bash curl_web_site.sh
...

实践1-基本信息统计

查看当前系统的链接状态数量
[root@localhost ~]# ss  -ant
State       Recv-Q Send-Q  Local Address:Port   Peer Address:Port
LISTEN      0      128                 *:22                *:*
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60856
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60857
ESTAB       0      64          10.0.0.12:22         10.0.0.1:64059
ESTAB       0      0           10.0.0.12:22         10.0.0.1:64061
LISTEN      0      32               [::]:21             [::]:*
LISTEN      0      128              [::]:22             [::]:*
统计当前主机的连接状态信息
[root@localhost ~]# ss -tan|awk '!/State/{state[$1]++}END{for(i in state){print i,state[i]}}'
LISTEN 5
ESTAB 4
TIME-WAIT 3960
发现异常ip地址,进行杜绝恶意ip地址访问
[root@localhost ~]# ss -nt | awk -F'[ :]+' '!/State/{ip[$(NF-2)]++}END{for(i in ip){print i,ip[i]}}' | while read line; do ip=$(echo $line | awk '{if($2>1)print $1}');[ -z "$ip" ] || echo "iptables -A INPUT -s $ip -j REJECT"; done
iptables -A INPUT -s 10.0.0.1 -j REJECT
注意:
	这里为了演示成功,故意将恶意ip的频率降低了
	如果不小心真的添加了防火墙策略,则执行下面的命令实现功能恢复
	iptables -vnL INPUT
	iptables -D INPUT 1

实践2-web访问信息统计

获取客户端ip地址信息
[root@localhost ~]# awk -F '"' 'NR==403 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170
统计访问网站的地址信息
[root@localhost ~]# awk -F '"' '{ip[$(NF-1)]++}END{for(i in ip){print i,ip[i]}}' /var/log/nginx/access.log
60.211.218.78 624
222.217.125.153 1377
124.205.143.213 1917
14.115.106.222 1776
14.155.112.17 153
...
统计站点的访问页面信息
[root@localhost ~]# awk '{a[$7]++}END{for(v in a)print v,a[v]|"sort -k1 -nr|head -n10"}' /var/log/nginx/access.log
/nihao 3
/img/html-background.png 1
/img/header-background.png 1
/img/centos-logo.png 1
/favicon.ico 1
/99/ 396
/92/ 368
/83/ 332
/79/ 316
/78/ 312

实践3-脚本信息统计

查看脚本内容
[root@localhost ~]# cat net.sh
#!/bin/bash
# 功能: 脚本统计主机网络信息

# TCP连接数量
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
# UDP连接数量
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
# Listen监听状态的TCP端口数量
Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print count}')
# ESTABLlSHED状态的TCP连接数量
Estab_Total=$(ss -antpH | awk 'BEGIN{count=0}/^ESTAB/{count++}END{print count}')
# TIME-WAIT状态的TCP连接数量
TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0}/^TIME-WAIT/{count++}END{print count}')

#显示主机连接相关信息
echo "TCP连接总数:$TCP_Total"
echo "UDP连接总数:$UDP_Total"
echo "LISTEN状态的TCP端口数量:$Listen_Toatl"
echo "ESTAB状态的TCP连接数量:$Estab_Toatl"
echo "TIME-WAIT状态的TCP连接数量:$TIME_WAIT_Total"

文件实践

简介

	所谓的文件实践,主要是借助于awk的数组功能,实现文件的合并格式化等工作.

查看日志的样式

默认日志格式
	10.0.0.12 - - [19/Jun/2022:18:13:51 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "114.101.40.170"
	
期望统计信息
--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|

准备工作

获取ip地址
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170

获取访问页面
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-13)}' /var/log/nginx/access.log
/170/

输出统计信息

[root@localhost ~]# awk -F '("| )' '
  BEGIN{
    printf "--------------------------------------------\n|%-14s|%-4s|%-4s|%-4s|\n--------------------------------------------\n","     ip地址","访问次数","访问url","访问次数"
  }
  {a[$(NF-1)][$(NF-13)]++}
  END{
    # 遍历数组,统计每个ip的访问总数
    for(ip in a){
      for(uri in a[ip]){
        b[ip] += a[ip][uri]
      }
    }
    # 再次遍历
    for(ip in a){
      for(uri in a[ip]){
        printf "|%16s|%8d|%7s|%8d|\n", ip, b[ip], uri, a[ip][uri]
      }
    }
    printf "--------------------------------------------\n"
  }
' /var/log/nginx/access.log

--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|
| 124.205.143.213|    3408|      /|    3408|
|  14.115.106.222|    3330|      /|    3330|
|   14.155.112.17|     272|      /|     272|
--------------------------------------------

18 变量进阶

18.1 变量实践

18.1.1 高级赋值

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的高级赋值,是另外的一种变量值获取方法,这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分的内容主要还是在字符串的基础上,如何更精细的获取特定的信息内容:主要涉及到的内容样式如下:
字符串截取按分隔符截取: # 右  % 左
    ${file#/}	   	删除匹配结果,保留第一个/右边的字符串
    ${file##/}		删除匹配结果,保留最后一个/右边的字符串
    ${file%/}		删除匹配结果,保留第一个/左边的字符串
    ${file%%/}		删除匹配结果,保留最后一个/左边的字符串
    注意:
        匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
    ${file/dir/path}	把第一个dir替换成path:/path1/dir2/dir3/n
    ${file//dir/path}	把所有dir替换成path:/path1/path2/path3/n
    ${file/#dir/path} 	将从左侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
    ${file/%dir/path} 	将从右侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
    注意:
		如果匹配内容使用的是正则符号,应该注意正则符号的写法
字符串转换
    ${file^^}		把file中的所有小写字母转换为大写
    ${file,,}		把file中的所有大写字母转换为小写

简单实践

实践1-字符串截取

字符串截取示例
[root@localhost ~]# string=abc12342341
[root@localhost ~]# echo ${string#a*3}
42341
[root@localhost ~]# echo ${string#c*3}
abc12342341
[root@localhost ~]# echo ${string#*c1*3}
42341
[root@localhost ~]# echo ${string##a*3}
41
[root@localhost ~]# echo ${string%3*1}
abc12342
[root@localhost ~]# echo ${string%%3*1}
abc12
字符串截取赋值
[root@localhost ~]# file=/var/log/nginx/access.log
[root@localhost ~]# filename=${file##*/}
[root@localhost ~]# echo $filename
access.log
[root@localhost ~]# filedir=${file%/*}
[root@localhost ~]# echo $filedir
/var/log/nginx

实践2-字符串替换

字符串替换示例
[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# echo ${str/apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str//apple/APPLE}
APPLE, tree, APPLE tree, APPLE
[root@localhost ~]# echo ${str/#apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str/%apple/APPLE}
apple, tree, apple tree, APPLE

使用正则的情况下,代表尽可能多的匹配
[root@localhost ~]# file=dir1@dir2@dir3@n.txt
[root@localhost ~]# echo ${file/#d*r/DIR}
DIR3@n.txt
[root@localhost ~]# echo ${file/%3*/DIR}
dir1@dir2@dirDIR

实践3-字符串转换

[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# upper_str=${str^^}
[root@localhost ~]# echo ${upper_str}
APPLE, TREE, APPLE TREE, APPLE
[root@localhost ~]# lower_str=${upper_str,,}
[root@localhost ~]# echo ${lower_str}
apple, tree, apple tree, apple

18.1.2 嵌套变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

场景现象

场景1:我们知道,命令变量的的表现样式:
	ver=$(命令)
	-- 执行原理是,当`` 或者 $() 范围中存在能够正常解析的命令的话,会先执行命令,然后将命令执行的结果交个一个变量名。
场景2:它还有另外一种样式 -- 普通变量的第三种样式双引号 
	ming=shuji; name="wang-$ming"
	-- 解析原理:双引号会首先查看变量值范围内是否有可以解析的变量名,如果有的话,将解析后的结果放到变量值范围内,组合成一个新的变量值,然后交给变量名。
	上面的两种场景的特点就在于,一个命令行中,借助于$() 或者 "" 发起一次隐藏的命令执行,但是有些场景下,表面上的一个命令需要发起更深一层的命令执行才可以实现指定的功能。在这种场景下,无论是$() 还是 ""都无法满足要求了

场景示例

循环遍历演示
[root@localhost ~]# for i in {1..10}; do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读:这里出现一层隐藏命令执行
	1 {1..10} 会自动进行命令解析
		[root@localhost ~]# echo {1..10}
  	  	1 2 3 4 5 6 7 8 9 10
  	然后执行for命令
  		for i in 1 2 3 4 5 6 7 8 9 10
双层隐藏命令解读
[root@localhost ~]# n=10
[root@localhost ~]# for i in {1..$n}; do  echo "$i "; done
{1..10}

示例解读:
	在for语句中,其实我们的目的与上面的演示一样,但是区别在于这里有两层隐藏的命令执行
	1 $n 需要解析成 10
	2 {1..10} 需要解析成 1 2 3 4 5 6 7 8 9 10
	最后执行 for命令 for 1 2 3 4 5 6 7 8 9 10
问题:
	linux命令行,默认情况下是无法执行两层隐藏命令的执行的,在有些场景中,我们可以通过$() 来实现多层命令的解读,示例如下:
	[root@localhost ~]# cmd=who
    [root@localhost ~]# echo $(${cmd}ami)
    root
	但是,这里我们无法实现,因为 {1..10} 不是命令。
	[root@localhost ~]# for i in $({1..$n}); do  echo "$i "; done
	-bash: {1..10}: 未找到命令

解决方法

	在shell中,它提供了一个专属的命令,可以实现多层隐藏命令的解析,不仅仅能够解析,还能够将相关环境的属性重现,从而实现多层隐藏命令的顺利执行。这个命令就是 eval。
eval原理
	1 eval命令将会首先扫描命令行整体
	2 发现解析则解析,发现执行则预先执行,实现所有隐藏命令的成功执行
	3 将隐藏命令执行的最终结果进行置换
	4 最后执行命令行表面的命令。

简单实践

实践1-eval简单实践

for循环演示
[root@localhost ~]# n=10
[root@localhost ~]# for i in $(eval echo {1..$n}); do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读
	1 命令改造$(eval echo {1..$n})
		1-1 $n先解析为10,命令替换为 {1..10}
		1-2 通过 eval 带入 echo 命令环境
		1-3 $() 执行 echo {1..10} 输出为 1 2 3 4 5 6 7 8 9 10
	2 整体置换命令结果
		for i in 1 2 3 4 5 6 7 8 9 10

实践2-eval的命令扩展演示

查看文件内容
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world

脚本内容演示
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world
[root@localhost ~]# cmd="cat infile.txt"
[root@localhost ~]# echo $(${cmd})
hello-in-world
[root@localhost ~]# echo ${cmd}				
cat infile.txt

不是我们想要的,我们可以借助于eval 和 $() 方式来实现隐藏命令的解读
[root@localhost ~]# eval ${cmd}
hello-in-world
[root@localhost ~]# echo $(${cmd})
hello-in-world

实践3-eval变量名的预制解析

定制嵌套的环境变量
[root@localhost ~]# str=a
[root@localhost ~]# num=1
[root@localhost ~]# $str$num=hello
-bash: a1=hello: 未找到命令

借助于eval命令来实现
[root@localhost ~]# eval $str$num=hello
[root@localhost ~]# echo $a1
hello

借助于eval实现变量名的嵌套
[root@localhost ~]# eval $str=$a1
[root@localhost ~]# echo $a
hello
解读:
	$str 就是 a,$a1就是hello,
	eval执行的命令就是  a=hello

18.1.3 综合案例

这一节,我们从 免密认证、脚本实践、小结 三个方面来学习

免密认证

案例需求

	A 以主机免密码认证 连接到 远程主机B
我们要做主机间免密码认证需要做三个动作
    1、本机生成密钥对
    2、对端机器使用公钥文件认证
    3、验证

手工演示

本地主机生成秘钥对
[root@localhost ~]# ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Ncra/fPpaVs+M18l9Kn7CQq33zmWQSoJ/ujuugCkNjM root@localhost
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|   .      o   .  |
|  o    . + . . o.|
| E .    S . . +.o|
|. + .  o o o ..o.|
|     .. ..+..o  =|
|      .  .oo+ =%+|
|       o*+ ooBO*O|
+----[SHA256]-----+
将公钥信息传递给远程主机的指定用户
[root@localhost ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
本地主机测试验证效果
[root@localhost ~]# ssh root@10.0.0.12 "ifconfig eth0 | grep netmas"
        inet 10.0.0.12  netmask 255.255.255.0  broadcast 10.0.0.255

简单实践

remotehost_sshkey_auth.sh
#!/bin/bash
# 功能:设置ssh跨主机免密码认证
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
user_dir="/root"
login_uesr='root'
login_pass='123456'

# 定制数组变量
target_type=(部署 免密 退出)

# 定制安装软件的函数
expect_install(){
    yum install expect -y >> /dev/null
    echo "软件安装完毕"
}

# 定制ssh秘钥对的生成
sshkey_create(){
    # 清理历史秘钥
    [ -d ${user_dir}/.ssh ] && rm -rf ${user_dir}/.ssh
    # 生成新的秘钥
    ssh-keygen -t rsa -P "" -f ${user_dir}/.ssh/id_rsa >> /dev/null
    echo "秘钥生成完毕"
}

# 定制expect的认证逻辑
expect_process(){
    # 注意:这里不要乱用$1,可以参考函数和脚本间的数组传参
    command="$@"
    expect -c "
        spawn ${command}
        expect {
            \"*yes/no*\" {send \"yes\r\"; exp_continue}
            \"*password*\" {send \"${login_pass}\r\"; exp_continue}
            \"*Password*\" {send \"${login_pass}\r\";}
        }"
}

# 跨主机密码认证
sshkey_auth(){
    local host_list="$1"
    for i in ${host_list}
    do
        command="/usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub"
        remote="${login_uesr}@$i"
        expect_process ${command} ${remote}
    done
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台操作界面---------------"
    echo -e " 1: 秘钥准备  2: 免密认证  3: 退出操作"
    echo -e "-------------------------------------------\033[0m"
}

# 定制脚本帮助信息
Usage(){
    echo "请输入有效的操作标识!!!"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始部署秘钥环境..."
        expect_install
        sshkey_create
    elif [ ${target_type[$target_id-1]} == "免密" ];then
        read -p "> 请输入免密10.0.0网段主机的范围,示例{12..19}: " num_list
        # eval的隐藏命令解析
        ip_list=$(eval echo 10.0.0.${num_list})
        sshkey_auth ${ip_list}
    elif [ ${target_type[$target_id-1]} == "退出" ];then
        echo "准备退出管理操作界面..."
        exit
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash remotehost_sshkey_auth.sh
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署秘钥环境...
软件安装完毕
秘钥生成完毕
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入免密10.0.0网段主机的范围,示例{12..19}: {12..13}
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.

---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 3
准备退出管理操作界面...

小结


19 awk实践

19.1 逻辑控制

19.1.1 switch条件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中有一种简单的条件控制语法switch,相较于if来说,switch分支语句功能较弱,只能进行等值比较或正则匹配,一般结合case方式来使用。
语法格式
    switch (表达式) {
        case 值1|regex1 : 执行语句1;break
        case 值2|regex2 : 执行语句2;break
        case 值3|regex3 : 执行语句3;break
        ...
        [ default: 执行语句 ]
    }
注意:
	表达式的结果符合awk的逻辑运算,表达式成功返回0,表达式失败返回1
	因为switch本身外侧没有流程控制,所以,一般情况下,当外侧没有循环控制的时候,会结合break来使用
	一旦遇到break,代表退出当前循环

简单实践

实践1-简单实践

无break效果
[root@localhost ~]# seq 2 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
    case "0":
        print "偶数: ", $1
    default:
        print "嘿嘿"
}}'
奇数:  1
偶数:  1
嘿嘿
偶数:  2
嘿嘿
结果显示:
	没有break的时候,每一次都会走一个switch的完整循环
结合break的基本语法实践
[root@localhost ~]# seq 5 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
        break
    case "0":
        print "偶数: ", $1
        break
    default:
        print "嘿嘿"
        break
}}'
奇数:  1
偶数:  2
奇数:  3
偶数:  4
奇数:  5

结果显示:
	有了break效果好多了

实践2-结合文本来进行实践

[root@localhost ~]# awk '{switch (NR % 2) {
    case "1":
        print "奇数行: ", $0
        break
    case "0":
        print "偶数行: ", $0
        break
}}' awk.txt
奇数行:  nihao awk1 awk2 awk3
偶数行:  nihao awk4 awk5 awk6
奇数行:  nihao awk7 awk8 awk9
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
姓名  语文	数据  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89
[root@localhost ~]# awk -v total=0 '
BEGIN{
    printf "\t学生成绩信息统计\n"
    printf "-----------------------------------\n"
}
NR>=2 {switch ($2+$3+$4 >= 240) {
    case 0:
        type="优秀";break
    case 1:
        type="良好";break
}
total=$2+$3+$4
printf "姓名: %-3s 总分: %4d,状态: %-2s\n",$1,$2+$3+$4,type
}' course_scores.txt
        学生成绩信息统计
-----------------------------------
姓名: 张三  总分:  255,状态: 良好
姓名: 李四  总分:  247,状态: 良好
姓名: 王五  总分:  195,状态: 优秀
姓名: 赵六  总分:  268,状态: 良好

19.1.2 for循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中,支持一些逻辑循环的功能,比如 for、while等。实际的过程中,我们往往会结合数组元素进行信息的统计
for语句标准格式
	for (三元表达式) {
        执行语句
    }

    for (变量 in 列表) {
        执行语句
    }
    
for语句简写格式
	for(表达式) {执行语句;...}
	
注意:
	for语句可以结合if语句进行操作

简单实践

实践1-for循环语法

普通for循环实践
[root@localhost ~]# for((i=1,sum=0;i<=100;i++));do let sum+=i;done;echo $sum
5050

awk的for标准语法
[root@localhost ~]# awk 'BEGIN {
    sum=0
    for (i=1;i<=100;i++) {
        sum+=i
    }
    print sum
}'
5050

awk for循环单行实践
[root@localhost ~]# awk 'BEGIN{ sum=0; for(i=1;i<=100;i++) {sum+=i}; print sum}'
5050

实践2-文本实践

文本信息的基本统计
[root@localhost ~]# seq 10 | paste -s | tr -s "\t" " " > num.txt
[root@localhost ~]# cat num.txt
1 2 3 4 5 6 7 8 9 10
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){sum+=$i};print sum}' num.txt
55
学生信息统计
[root@localhost ~]# awk 'NR>=2 {
    total=0
    for (i=2;i<=NF;i++) {
        total+=$i
    }
    print "学生姓名: "$1", 课程总分: "total
}' course_scores.txt
学生姓名: 张三, 课程总分: 255
学生姓名: 李四, 课程总分: 247
学生姓名: 王五, 课程总分: 195
学生姓名: 赵六, 课程总分: 268

19.1.3 while循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk支持while相关的循环处理,它主要有两种表现样式
样式1
    while(条件){
        执行语句
    }

样式2
    do {
        执行语句
    } while(条件)
    
简写样式
	while(condition){执行语句;...}
	do {条件;...} while(条件)

简单实践

实践1-while实践

while标准格式
[root@localhost ~]# awk 'BEGIN {
    i=1;sum=0
    while(i<=100) {
        sum+=i
        i++
    }
    print sum
}'
5050

求和1~100
[root@localhost ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print "sum="sum}'
sum=5050

指定数据进行求和
[root@localhost ~]# read -p "请输入一个数字 : " NUM;awk -v num=$NUM 'BEGIN{i=1;sum=0;while(i<=num){sum+=i;i++};print "1~$NUM的和为="sum}'
请输入一个数字 :34
1~$NUM的和为=595

最大值和最小值
[root@localhost ~]# echo '0 234 252 3246 2245 2345 4536 3754 32 345 323 234 3 1' > num.txt
[root@localhost ~]# awk '{min=$1;max=$1;while(i<=NF){if(max<$i)max=$i;if(min>$i)min=$i;i++};print "max:"max,"min:"min}' num.txt
max:4536 min:0

标准求和
[root@localhost ~]# awk '{
sum=0
i=1
while (i<=NF) {
   sum+=$i
   i++
}
print sum
}'  num.txt
17550

实践2-dowhile实践

基本格式
[root@localhost ~]# awk 'BEGIN{i=0;do {print i;i++} while(i<5)}'
0
1
2
3
4

求和1~100
[root@localhost ~]# awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print total}'
5050

19.1.4 流程控制

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk提供了很多的功能实践,尤其是与逻辑控制相关的,其实awk为了更好的进行这些流程的控制,它也提供了很多的控制语法,这些语法如下:
	continue 	中断本次循环
	break 		中断整个循环
	next 		可以提前结束对匹配行处理而直接进入下一行处理
	nextfile 	进阶版的next,可以提前结束对匹配行处理,直接读取下一个文件进行循环处理
	exit 		退出awk程序
		END代码段属于exit一部分,可以在BEGIN或main段中执行exit操作--执行END语句块。

简单实践

实践1-continue实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5)continue
    print(i)
  }
}'
0
1
2
3
4
6
7
8
9
continue 求奇|偶数和
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2!=0)continue;sum+=i}print sum}'
2550

实践2-break实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5){
      break
    }
    print(i)
  }
}'
0
1
2
3
4

循环求和,当加值为66的时候,停止运算
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)break;sum+=i}print sum}'
2145

实践3-next实践

查看文件内容
[root@localhost ~]# cat awk.txt -n
     1  nihao awk1 awk2 awk3
     2  nihao awk4 awk5 awk6
     3  nihao awk7 awk8 awk9
     
输出指定行外的其他行   
[root@localhost ~]# awk 'NR==3{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
[root@localhost ~]# awk 'NR==2{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk7 awk8 awk9

实践4-nextfile实践

nextfile代表在
[root@localhost ~]# awk 'FNR==3{nextfile}{print}' awk.txt awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6

实践5-exit实践

处理一个文件就退出awk程序
[root@localhost ~]# awk '{print $2}' awk.txt
awk1
awk4
awk7
[root@localhost ~]# awk '{print $2; exit}' awk.txt
awk1

只处理三行匹配的内容,后续结束
[root@localhost ~]# awk '/nologin/{i++;if(i<=3){print $0}else{exit;}}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
多个阶段实践exit
[root@localhost ~]# awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
1
[root@localhost ~]# awk 'BEGIN{exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
2
[root@localhost ~]# echo | awk 'BEGIN{}{exit 111}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
111
[root@localhost ~]# echo | awk 'BEGIN{}{}END{if(flag){exit}}'
[root@localhost ~]# echo $?
0

小结


19.1.5 数组实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在使用awk的时候,其实在一些业务的数据分析场景下,使用频率最多的应该是数组,而且数据往往与前面学习的逻辑流程控制组合在一起使用,数组一般很少单独使用。
	其基本语法格式如下:
定义数组:
	array[index表达式]
	index表达式:
        可使用任意字符串;字符串要使用双引号括起来
        如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为空串
        若要判断数组中是否存在某元素,要使用index in array格式进行遍历

遍历数组中的元素,要使用for循环
	for(var in array){for-body}
	注意:
		var会遍历array的每个索引,var不能用关键字index

简单实践

实践1-数组简单实践

数组的简单定义和调用
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;print array["yuwen"],array["shuxue"],array["lishi"]}'
78 89 99

数组的遍历操作
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;for(i in array){print array[i]}}'
78
89
99

内容自动遍历
[root@localhost ~]# cat passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){arrar[i]=i} print $7}' passwd.txt
/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){array[i]=$i};for(j in array){print array[j]}}' passwd.txt
0
root
/root
root
x
0

实践2-数组数据去重

文件内容准备
[root@localhost ~]# echo -e "a\nb\na\ncc" > array.txt
[root@localhost ~]# cat array.txt
a
b
a
cc

数组去重的逻辑
[root@localhost ~]# awk 'array[$0]++' array.txt
a
[root@localhost ~]# awk '!array[$0]++' array.txt
a
b
cc
语法解读
	array[$0]++  第一次执行array[$0],由于是array[a]不为空,所以为真,然后array[a]"++"计数后变成1,并打印数组元素a
	!array[$0]++ 第一次效果与上面一样,执行到第三行的时候,因为存在array[a]已存在,为真,然后!array[a]为假,不再输出打印当前内容,从而达到去重的效果

实践3-统计计数

统计计数
[root@localhost ~]# echo "a.b.c,c.d" |awk -F'[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1

19.1.6 自定义函数

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

需求

	虽然awk提供了内置的函数来实现相应的内置函数,但是有些功能场景,还是需要我们自己来设定,这就用到了awk的自定义函数功能了。
	awk的函数目的,与shell的函数目的一致,都是提高代码的复用能力和功能灵活性

函数

语法格式
    function 函数名(参数1, 参数2, ...)
    {
        函数体代码
    }
注意:
	函数名不能用awk的关键字信息

简单实践

实践1-简单函数实践

[root@localhost ~]# awk '
function add_func(num1, num2)
{
  return num1 + num2
}
function sub_func(num1, num2)
{
  if (num1 > num2)
    return num1 - num2
  return num2 - num1
}
BEGIN {
  sum_result=add_func(10, 20)
  print "两值之和为: "sum_result
  sub_result=sub_func(10, 20)
  print "两值之差为: "sub_result
}'
两值之和为: 30
两值之差为: 10

实践2-数据统计计算

在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
[root@localhost ~]# awk '
function head_func() {
  printf "---------------------------\n|%-3s|%2s|%2s|%2s|%2s|\n---------------------------\n","姓名","语文","数学","历史","总分"
}
function body_func(arg1, arg2, arg3, arg4,arg5){
  printf "|%-3s|%4d|%4d|%4d|%4d|\n",arg1,arg2,arg3,arg4,arg5
}
function tail_func(arg1, arg2, arg3, arg4,arg5){
  printf "---------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n---------------------------\n学生总数总: %2d\n","合计",arg1,arg2,arg3,arg4,arg5
}
BEGIN {
  head_func()
  yu=0;shu=0;li=0;total
}{
  yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4
  body_func($1,$2,$3,$4,$2+$3+$4)
}END{
  tail_func(yu,shu,li,total,NR)
}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 268|
---------------------------
学生总数总:  4

小结


19.1.7 综合实践

这一节,我们从 网络实践、文件实践、小结 三个方面来学习

网络实践

简介

	所谓的网络实践,主要是借助于awk的数组功能,进行站点的信息统计操作。

准备网络环境

安装软件
yum install nignx -y

重启nginx
[root@localhost ~]# systemctl restart nginx.service

重置网站首页
[root@localhost /etc/nginx]# echo 'hello nginx' > /usr/share/nginx/html/index.html
[root@localhost /etc/nginx]# curl localhost
hello nginx
[root@localhost /etc/nginx]# curl localhost/nihao -I -s | head -1
HTTP/1.1 404 Not Found

模拟外网访问
[root@localhost ~]# curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: 2.2.2.2" | head -1
HTTP/1.1 200 OK
[root@localhost ~]# tail -n1 /var/log/nginx/access.log
10.0.0.12 - - [19/Jun/2022:18:04:20 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "2.2.2.2"

准备ip地址文件
[root@localhost ~]# cat ip.txt
112.64.233.130
114.101.40.170
123.15.24.200
125.46.0.62
223.243.252.155
122.228.19.92
218.2.226.42
124.205.143.213
218.60.8.99
125.123.120.130
123.139.56.238
218.60.8.83
222.240.184.126
222.90.110.194
1.196.160.46
222.217.125.153
163.125.156.249
27.50.142.132
61.145.182.27
222.249.238.138
218.64.69.79
103.10.86.203
14.155.112.17
27.191.234.69
60.211.218.78
124.237.83.14
59.44.247.194
114.249.119.45
125.123.65.177
14.115.106.222
准备站点访问测试脚本
[root@localhost /etc/nginx]# cat curl_web_site.sh
#!/bin/bash
# 功能:模拟外网访问网站
while true
do
  cat ip.txt | while read ip
  do
   NUM=$(echo $ip | cut -d"." -f 4)
   for i in $(seq $NUM)
   do
     curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: $ip" >> /dev/null
     curl http://10.0.0.12/$NUM/ -s >> /dev/null
   done
   sleep 1
  done
done
脚本测试效果
[root@localhost ~]# /bin/bash curl_web_site.sh
...

实践1-基本信息统计

查看当前系统的链接状态数量
[root@localhost ~]# ss  -ant
State       Recv-Q Send-Q  Local Address:Port   Peer Address:Port
LISTEN      0      128                 *:22                *:*
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60856
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60857
ESTAB       0      64          10.0.0.12:22         10.0.0.1:64059
ESTAB       0      0           10.0.0.12:22         10.0.0.1:64061
LISTEN      0      32               [::]:21             [::]:*
LISTEN      0      128              [::]:22             [::]:*
统计当前主机的连接状态信息
[root@localhost ~]# ss -tan|awk '!/State/{state[$1]++}END{for(i in state){print i,state[i]}}'
LISTEN 5
ESTAB 4
TIME-WAIT 3960
发现异常ip地址,进行杜绝恶意ip地址访问
[root@localhost ~]# ss -nt | awk -F'[ :]+' '!/State/{ip[$(NF-2)]++}END{for(i in ip){print i,ip[i]}}' | while read line; do ip=$(echo $line | awk '{if($2>1)print $1}');[ -z "$ip" ] || echo "iptables -A INPUT -s $ip -j REJECT"; done
iptables -A INPUT -s 10.0.0.1 -j REJECT
注意:
	这里为了演示成功,故意将恶意ip的频率降低了
	如果不小心真的添加了防火墙策略,则执行下面的命令实现功能恢复
	iptables -vnL INPUT
	iptables -D INPUT 1

实践2-web访问信息统计

获取客户端ip地址信息
[root@localhost ~]# awk -F '"' 'NR==403 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170
统计访问网站的地址信息
[root@localhost ~]# awk -F '"' '{ip[$(NF-1)]++}END{for(i in ip){print i,ip[i]}}' /var/log/nginx/access.log
60.211.218.78 624
222.217.125.153 1377
124.205.143.213 1917
14.115.106.222 1776
14.155.112.17 153
...
统计站点的访问页面信息
[root@localhost ~]# awk '{a[$7]++}END{for(v in a)print v,a[v]|"sort -k1 -nr|head -n10"}' /var/log/nginx/access.log
/nihao 3
/img/html-background.png 1
/img/header-background.png 1
/img/centos-logo.png 1
/favicon.ico 1
/99/ 396
/92/ 368
/83/ 332
/79/ 316
/78/ 312

实践3-脚本信息统计

查看脚本内容
[root@localhost ~]# cat net.sh
#!/bin/bash
# 功能: 脚本统计主机网络信息

# TCP连接数量
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
# UDP连接数量
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
# Listen监听状态的TCP端口数量
Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print count}')
# ESTABLlSHED状态的TCP连接数量
Estab_Total=$(ss -antpH | awk 'BEGIN{count=0}/^ESTAB/{count++}END{print count}')
# TIME-WAIT状态的TCP连接数量
TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0}/^TIME-WAIT/{count++}END{print count}')

#显示主机连接相关信息
echo "TCP连接总数:$TCP_Total"
echo "UDP连接总数:$UDP_Total"
echo "LISTEN状态的TCP端口数量:$Listen_Toatl"
echo "ESTAB状态的TCP连接数量:$Estab_Toatl"
echo "TIME-WAIT状态的TCP连接数量:$TIME_WAIT_Total"

文件实践

简介

	所谓的文件实践,主要是借助于awk的数组功能,实现文件的合并格式化等工作.

查看日志的样式

默认日志格式
	10.0.0.12 - - [19/Jun/2022:18:13:51 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "114.101.40.170"
	
期望统计信息
--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|

准备工作

获取ip地址
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170

获取访问页面
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-13)}' /var/log/nginx/access.log
/170/

输出统计信息

[root@localhost ~]# awk -F '("| )' '
  BEGIN{
    printf "--------------------------------------------\n|%-14s|%-4s|%-4s|%-4s|\n--------------------------------------------\n","     ip地址","访问次数","访问url","访问次数"
  }
  {a[$(NF-1)][$(NF-13)]++}
  END{
    # 遍历数组,统计每个ip的访问总数
    for(ip in a){
      for(uri in a[ip]){
        b[ip] += a[ip][uri]
      }
    }
    # 再次遍历
    for(ip in a){
      for(uri in a[ip]){
        printf "|%16s|%8d|%7s|%8d|\n", ip, b[ip], uri, a[ip][uri]
      }
    }
    printf "--------------------------------------------\n"
  }
' /var/log/nginx/access.log

--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|
| 124.205.143.213|    3408|      /|    3408|
|  14.115.106.222|    3330|      /|    3330|
|   14.155.112.17|     272|      /|     272|
--------------------------------------------

20 变量进阶

20.1 变量实践

20.1.1 高级赋值

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的高级赋值,是另外的一种变量值获取方法,这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分的内容主要还是在字符串的基础上,如何更精细的获取特定的信息内容:主要涉及到的内容样式如下:
字符串截取按分隔符截取: # 右  % 左
    ${file#/}	   	删除匹配结果,保留第一个/右边的字符串
    ${file##/}		删除匹配结果,保留最后一个/右边的字符串
    ${file%/}		删除匹配结果,保留第一个/左边的字符串
    ${file%%/}		删除匹配结果,保留最后一个/左边的字符串
    注意:
        匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
    ${file/dir/path}	把第一个dir替换成path:/path1/dir2/dir3/n
    ${file//dir/path}	把所有dir替换成path:/path1/path2/path3/n
    ${file/#dir/path} 	将从左侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
    ${file/%dir/path} 	将从右侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
    注意:
		如果匹配内容使用的是正则符号,应该注意正则符号的写法
字符串转换
    ${file^^}		把file中的所有小写字母转换为大写
    ${file,,}		把file中的所有大写字母转换为小写

简单实践

实践1-字符串截取

字符串截取示例
[root@localhost ~]# string=abc12342341
[root@localhost ~]# echo ${string#a*3}
42341
[root@localhost ~]# echo ${string#c*3}
abc12342341
[root@localhost ~]# echo ${string#*c1*3}
42341
[root@localhost ~]# echo ${string##a*3}
41
[root@localhost ~]# echo ${string%3*1}
abc12342
[root@localhost ~]# echo ${string%%3*1}
abc12
字符串截取赋值
[root@localhost ~]# file=/var/log/nginx/access.log
[root@localhost ~]# filename=${file##*/}
[root@localhost ~]# echo $filename
access.log
[root@localhost ~]# filedir=${file%/*}
[root@localhost ~]# echo $filedir
/var/log/nginx

实践2-字符串替换

字符串替换示例
[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# echo ${str/apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str//apple/APPLE}
APPLE, tree, APPLE tree, APPLE
[root@localhost ~]# echo ${str/#apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str/%apple/APPLE}
apple, tree, apple tree, APPLE

使用正则的情况下,代表尽可能多的匹配
[root@localhost ~]# file=dir1@dir2@dir3@n.txt
[root@localhost ~]# echo ${file/#d*r/DIR}
DIR3@n.txt
[root@localhost ~]# echo ${file/%3*/DIR}
dir1@dir2@dirDIR

实践3-字符串转换

[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# upper_str=${str^^}
[root@localhost ~]# echo ${upper_str}
APPLE, TREE, APPLE TREE, APPLE
[root@localhost ~]# lower_str=${upper_str,,}
[root@localhost ~]# echo ${lower_str}
apple, tree, apple tree, apple

20.1.2 嵌套变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

场景现象

场景1:我们知道,命令变量的的表现样式:
	ver=$(命令)
	-- 执行原理是,当`` 或者 $() 范围中存在能够正常解析的命令的话,会先执行命令,然后将命令执行的结果交个一个变量名。
场景2:它还有另外一种样式 -- 普通变量的第三种样式双引号 
	ming=shuji; name="wang-$ming"
	-- 解析原理:双引号会首先查看变量值范围内是否有可以解析的变量名,如果有的话,将解析后的结果放到变量值范围内,组合成一个新的变量值,然后交给变量名。
	上面的两种场景的特点就在于,一个命令行中,借助于$() 或者 "" 发起一次隐藏的命令执行,但是有些场景下,表面上的一个命令需要发起更深一层的命令执行才可以实现指定的功能。在这种场景下,无论是$() 还是 ""都无法满足要求了

场景示例

循环遍历演示
[root@localhost ~]# for i in {1..10}; do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读:这里出现一层隐藏命令执行
	1 {1..10} 会自动进行命令解析
		[root@localhost ~]# echo {1..10}
  	  	1 2 3 4 5 6 7 8 9 10
  	然后执行for命令
  		for i in 1 2 3 4 5 6 7 8 9 10
双层隐藏命令解读
[root@localhost ~]# n=10
[root@localhost ~]# for i in {1..$n}; do  echo "$i "; done
{1..10}

示例解读:
	在for语句中,其实我们的目的与上面的演示一样,但是区别在于这里有两层隐藏的命令执行
	1 $n 需要解析成 10
	2 {1..10} 需要解析成 1 2 3 4 5 6 7 8 9 10
	最后执行 for命令 for 1 2 3 4 5 6 7 8 9 10
问题:
	linux命令行,默认情况下是无法执行两层隐藏命令的执行的,在有些场景中,我们可以通过$() 来实现多层命令的解读,示例如下:
	[root@localhost ~]# cmd=who
    [root@localhost ~]# echo $(${cmd}ami)
    root
	但是,这里我们无法实现,因为 {1..10} 不是命令。
	[root@localhost ~]# for i in $({1..$n}); do  echo "$i "; done
	-bash: {1..10}: 未找到命令

解决方法

	在shell中,它提供了一个专属的命令,可以实现多层隐藏命令的解析,不仅仅能够解析,还能够将相关环境的属性重现,从而实现多层隐藏命令的顺利执行。这个命令就是 eval。
eval原理
	1 eval命令将会首先扫描命令行整体
	2 发现解析则解析,发现执行则预先执行,实现所有隐藏命令的成功执行
	3 将隐藏命令执行的最终结果进行置换
	4 最后执行命令行表面的命令。

简单实践

实践1-eval简单实践

for循环演示
[root@localhost ~]# n=10
[root@localhost ~]# for i in $(eval echo {1..$n}); do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读
	1 命令改造$(eval echo {1..$n})
		1-1 $n先解析为10,命令替换为 {1..10}
		1-2 通过 eval 带入 echo 命令环境
		1-3 $() 执行 echo {1..10} 输出为 1 2 3 4 5 6 7 8 9 10
	2 整体置换命令结果
		for i in 1 2 3 4 5 6 7 8 9 10

实践2-eval的命令扩展演示

查看文件内容
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world

脚本内容演示
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world
[root@localhost ~]# cmd="cat infile.txt"
[root@localhost ~]# echo $(${cmd})
hello-in-world
[root@localhost ~]# echo ${cmd}				
cat infile.txt

不是我们想要的,我们可以借助于eval 和 $() 方式来实现隐藏命令的解读
[root@localhost ~]# eval ${cmd}
hello-in-world
[root@localhost ~]# echo $(${cmd})
hello-in-world

实践3-eval变量名的预制解析

定制嵌套的环境变量
[root@localhost ~]# str=a
[root@localhost ~]# num=1
[root@localhost ~]# $str$num=hello
-bash: a1=hello: 未找到命令

借助于eval命令来实现
[root@localhost ~]# eval $str$num=hello
[root@localhost ~]# echo $a1
hello

借助于eval实现变量名的嵌套
[root@localhost ~]# eval $str=$a1
[root@localhost ~]# echo $a
hello
解读:
	$str 就是 a,$a1就是hello,
	eval执行的命令就是  a=hello

20.1.3 综合案例

这一节,我们从 免密认证、脚本实践、小结 三个方面来学习

免密认证

案例需求

	A 以主机免密码认证 连接到 远程主机B
我们要做主机间免密码认证需要做三个动作
    1、本机生成密钥对
    2、对端机器使用公钥文件认证
    3、验证

手工演示

本地主机生成秘钥对
[root@localhost ~]# ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Ncra/fPpaVs+M18l9Kn7CQq33zmWQSoJ/ujuugCkNjM root@localhost
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|   .      o   .  |
|  o    . + . . o.|
| E .    S . . +.o|
|. + .  o o o ..o.|
|     .. ..+..o  =|
|      .  .oo+ =%+|
|       o*+ ooBO*O|
+----[SHA256]-----+
将公钥信息传递给远程主机的指定用户
[root@localhost ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
本地主机测试验证效果
[root@localhost ~]# ssh root@10.0.0.12 "ifconfig eth0 | grep netmas"
        inet 10.0.0.12  netmask 255.255.255.0  broadcast 10.0.0.255

简单实践

remotehost_sshkey_auth.sh
#!/bin/bash
# 功能:设置ssh跨主机免密码认证
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
user_dir="/root"
login_uesr='root'
login_pass='123456'

# 定制数组变量
target_type=(部署 免密 退出)

# 定制安装软件的函数
expect_install(){
    yum install expect -y >> /dev/null
    echo "软件安装完毕"
}

# 定制ssh秘钥对的生成
sshkey_create(){
    # 清理历史秘钥
    [ -d ${user_dir}/.ssh ] && rm -rf ${user_dir}/.ssh
    # 生成新的秘钥
    ssh-keygen -t rsa -P "" -f ${user_dir}/.ssh/id_rsa >> /dev/null
    echo "秘钥生成完毕"
}

# 定制expect的认证逻辑
expect_process(){
    # 注意:这里不要乱用$1,可以参考函数和脚本间的数组传参
    command="$@"
    expect -c "
        spawn ${command}
        expect {
            \"*yes/no*\" {send \"yes\r\"; exp_continue}
            \"*password*\" {send \"${login_pass}\r\"; exp_continue}
            \"*Password*\" {send \"${login_pass}\r\";}
        }"
}

# 跨主机密码认证
sshkey_auth(){
    local host_list="$1"
    for i in ${host_list}
    do
        command="/usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub"
        remote="${login_uesr}@$i"
        expect_process ${command} ${remote}
    done
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台操作界面---------------"
    echo -e " 1: 秘钥准备  2: 免密认证  3: 退出操作"
    echo -e "-------------------------------------------\033[0m"
}

# 定制脚本帮助信息
Usage(){
    echo "请输入有效的操作标识!!!"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始部署秘钥环境..."
        expect_install
        sshkey_create
    elif [ ${target_type[$target_id-1]} == "免密" ];then
        read -p "> 请输入免密10.0.0网段主机的范围,示例{12..19}: " num_list
        # eval的隐藏命令解析
        ip_list=$(eval echo 10.0.0.${num_list})
        sshkey_auth ${ip_list}
    elif [ ${target_type[$target_id-1]} == "退出" ];then
        echo "准备退出管理操作界面..."
        exit
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash remotehost_sshkey_auth.sh
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署秘钥环境...
软件安装完毕
秘钥生成完毕
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入免密10.0.0网段主机的范围,示例{12..19}: {12..13}
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.

---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 3
准备退出管理操作界面...

小结


21 项目发布

21.1 基础知识

21.1.1 项目交付

这一节,我们从 基础知识、代码发布、小结 三个方面来学习

基础知识

简介

	项目交付是一个涉及到多团队共同协作的事情,它包括 产品团队设计产品、研发团队开发产品、测试团队测试代码、运维团队发布代码和维护站点等工作。
项目交付的过程中,每个团队都有自己特有的一些工作特殊
一般情况下,项目交付在软件工程人员人员眼中的基本逻辑

代码发布

简介

	所谓的代码发布,其实就是一句话:将我们的代码放到一台公司的互联网服务器上。那么我们应该怎么来理解这句话呢?我们从三个方面来理解他。
发布什么?
	代码		经过测试,功能完善,没有问题的代码
发布到哪里?
	服务器		所有人都能访问的到的一台服务器(有公网IP)
				idc机房、阿里云、亚马逊、腾讯云、华为云、....
发布的效果?
	web网页	对外展示

发布方式

常见的代码发布方式有两种:手工方式和脚本方式。
样式1:手工发布代码
	慢、干扰因素多、不安全
样式2:脚本发布代码
	快、干扰因素少、安全

21.1.2 发布解读

这一节,我们从 基本流程、流程解读、小结 三个方面来学习

基本流程

简介

	这里面的代码发布过程是侧重于手工级别的代码发布流程,在其他的一些基础设施环境中,虽然有一些区别,但是整个的流程脉络是一样的。
功能解读:
	获取代码 - 为什么? - 代码在远端服务器上
	打包代码 - 为什么? - 代码量太多,不好传输
	传输代码 - 为什么? - 本地主机没有代码
	关闭应用 - 为什么? - 防止用户访问出错
	解压代码 - 为什么? - 拉过来的是压缩包
	更新代码 - 为什么? - 要发布项目
	开启应用 - 为什么? - 刚才关闭了
	检查效果 - 为什么? - 万一失败了呢?
	对外开放 - 为什么? - 发布过程,没有让网上用户看到

流程详解

获取代码

	一般情况下,软件项目的网站代码都存放在一个稳定的代码服务器上,这个网站服务器叫代码仓库。
仓库分类
	公有仓库 - 使用互联网的代码服务器
	私有仓库 - 内部服务器或者公网服务器
	
实现方式
	集中式:svn的几乎所有操作命令,受本地主机和代码仓库的网络连接状态。
	分布式:git的几乎所有操作命令,不受代码仓库的网络连接状态限制。

打包代码

	所谓的打包代码,其实就是将获取的项目源代码进行打包操作
目的:
	减少传输文件数量
	减小传输文件大小
	增强传输速率
		
常见打包方式:
	windows:	zip、rar...
	linux:	   tar、zip...

传输代码

	在一个临时的服务器环境上,进行项目代码的部署测试,如果没有问题的话,可以将该代码传输到生产服务器上.
	常见的传输方式如下:
有网情况下
	多种方式: git、ftp、scp、共享挂载 cp、rsync
没有网情况下
	物理方式: U盘或者硬盘

关闭应用

	发布代码的时候,肯定会对生产服务器上运行的项目产生影响,为了避免对互联网用户产生不必要的干扰,我们会将应用关闭。	
问题1:关闭什么应用
	代码所在的服务用到了什么应用,就关闭什么应用
问题2:应用相互依赖的情况下,如何确定关闭的顺序
	站在用户访问的角度,先关闭离客户近的,后关闭离客户远的。

解压代码

	这一步不是必须的,有些场景下,需要代码压缩包解压,有些不用

更新代码

	为了避免我们在放置代码过程中,对老文件造成影响,所以我们放置代码一般分为两步:备份老文件和放置新文件。
备份老文件:防止更新失败,导致旧有的成功运行的代码被覆盖。
放置新文件:将新代码放置到旧代码的位置

注意:
	两个文件的名称是一样的,只是内容不同
	对整个应用项目来说,两个文件没有区别

开启应用

	在代码放置完毕后,将刚才关闭了应用,开启就可以了
开启的顺序:
	先开启离客户远的,后开启离客户近的

检查效果

	为了防止发布后的效果不成功,我们手工方式或者其他方式检测网站的功能是否可以正常的访问

对外开放

	项目内部人员检查这次发布没有问题后,开放应用的流量入口,让用户使用新版本的服务

21.1.3 技术要点

这一节,我们从 解压缩、传输、备份、小结 三个方面来学习

解压缩

简介

文件的压缩
	压缩格式:tar zcvf 压缩后的文件名  将要压缩的文件
文件的解压
	解压格式:tar xf 压缩后的文件名
查看压缩文件内容
	查看格式:zcat 压缩文件
命令参数详解
    z	指定压缩文件的格式为 tar.gz
    c	表示压缩
    v	显示详细过程
    f	指定压缩文件
    x	解压
    C   制定解压位置

解压缩实践

压缩实践
[root@localhost ~]# mkdir tar_dir
[root@localhost ~]# echo nihao > tar_dir/nihao.txt
[root@localhost ~]# tar -zcvf nihao.tar.gz tar_dir/
tar_dir/
tar_dir/nihao.txt

查看压缩文件
[root@localhost ~]# zcat nihao.tar.gz
tar_dir/0000...6011213 5ustar rootroottar_dir/nihao.txt 00...4253652266013046 0ustar  rootrootnihao

解压文件
[root@localhost ~]# tar xf nihao.tar.gz -C /tmp/
[root@localhost ~]# cat /tmp/tar_dir/nihao.txt
nihao

传输

简介

scp传输工具:
	命令格式:scp  要传输的文件 要放置的位置
	
远程目标样式:
	远端主机文件放置位置的表示形式:
		远程连接的用户@远程主机:远程主机的目录路径
	远端主机文件位置的表示形式:
		远程连接的用户@远程主机:远程主机的文件路径
传输示例:
	将本地文件推送到远程主机
		scp file.tar.gz root@10.0.0.12:/root/
	将远程主机的文件拉取到本地
		scp root@10.0.0.12:/root/file.tar.gz ./

传输文件实践

本地文件传输远程
[root@localhost ~]# scp nihao.tar.gz root@10.0.0.13:/tmp/
Warning: Permanently added '10.0.0.13' (ECDSA) to the list of known hosts.
root@10.0.0.13's password:
nihao.tar.gz          100%  156   122.3KB/s   00:00

远程文件拉到本地
[root@localhost ~]# rm -f nihao.tar.gz
[root@localhost ~]# scp root@10.0.0.13:/tmp/nihao.tar.gz ./
root@10.0.0.13's password:
nihao.tar.gz        100%  156   112.3KB/s   00:00
[root@localhost ~]# zcat nihao.tar.gz
tar_dir/00007...213 5ustar  rootroottar_dir/nihao.txt0...046 0ustar  rootrootnihao

备份

实践

	文件的备份要有一定的标志符号,我们就使用目前通用的时间戳的形式来表示,关于时间戳,我们可以借助于 date命令来进行获取

date命令详解:
    命令格式:date [option]
    常见参数:
        %F		显示当前日期格式,%Y-%m-%d
        %T		显示当前时间格式,%H:%M:%S
备份命令效果格式:
    方式一:复制备份-源文件不动
    	cp nihao nihao-$(date +%Y%m%d%H%M%S)
    方式二:移动备份-源文件没了
   		mv nihao nihao-$(date +%Y%m%d%H%M%S)	

备份实践

复制备份
[root@localhost ~]# cp nihao.tar.gz nihao.tar.gz-$(date +%Y%m%d%H%M%S)
[root@localhost ~]# cp nihao.tar.gz nihao.tar.gz-$(date +%Y%m%d%H%M%S)

移动备份
[root@localhost ~]# mv nihao.tar.gz nihao.tar.gz-$(date +%Y%m%d%H%M%S)
[root@localhost ~]# ls nihao.tar.gz-*
nihao.tar.gz-20420620010300  nihao.tar.gz-20420620010304  nihao.tar.gz-20420620010311

21.2 手工发布

21.2.1 方案解读

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

案例需求

	实现一套业务环境的项目发布流程,基本的网站架构效果如下:
架构解读:
	负载均衡采用Nginx服务,基于请求内容进行后端跳转
	动态应用使用的nginx 或者 uwsgi服务
	后端服务应用有多套编程语言研发的web项目。
		有基于java语言的sprintCloud框架开发的
		有基于python语言的django框架开发的
	我们要发布的是django后端服务代码

方案分析

项目部署分析

分析:
		2、python环境		--->  3、python虚拟环境
		1、django环境部署
			4、django软件安装
			5、项目基本操作
			6、应用基本操作
			7、view和url配置
				8、问题:只有本机能访问
					9、方案代理---- 10、nginx
		11、nginx实现代理
			12、nginx软件安装
			13、nginx基本操作
			14、nginx代理的配置
				15、目录结构
				16、查看配置文件
				17、找到对应的代理配置项
		18、启动django
		29、启动nginx
		20、整个项目调试

环境部署方案

环境部署方案
一、django环境部署
	1.1 python虚拟环境
	1.2 django环境部署
		1.2.1 django软件安装
		1.2.2 项目基本操作
		1.2.3 应用基本操作
		1.2.4 view和url配置
二、nginx代理django
	2.1 nginx软件安装
		2.1.1 nginx软件安装
		2.1.2 nginx基本操作
	2.2 nginx代理配置
		2.2.1 目录结构查看
		2.2.2 配置文件查看
		2.2.3 编辑代理配置项
三、项目调试
	3.1 启动软件
		3.1.1 启动django
		3.1.2 启动nginx
		3.2 整个项目调试
1、施工方案的分析原理:基于需求关键点-查依赖-查流程
2、施工方案编写的流程:基于需求分析的流程,按结点输出方案

21.2.2 环境部署

这一节,我们从 基础环境、代码环境、web环境、小结 三个方面来学习

基础环境

python软件部署

python软件部署
[root@localhost ~]# yum install python3 -y
[root@localhost ~]# python --version
Python 2.7.5
pip环境配置
(venv) [root@localhost ~]# mkdir ~/.pip
(venv) [root@localhost ~]#  cat ~/.pip/pip.conf
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host = pypi.tuna.tsinghua.edu.cn
(venv) [root@localhost ~]# python -m pip install --upgrade pip

python虚拟环境

安装虚拟环境软件
[root@localhost ~]# yum install python-virtualenv -y
准备虚拟环境目录
[root@localhost ~]# mkdir /data/virtual -p
[root@localhost ~]# cd /data/virtual
创建虚拟环境
[root@localhost /data/virtual]# virtualenv -p /usr/bin/python3.6 venv
Running virtualenv with interpreter /usr/bin/python3.6
Using base prefix '/usr'
New python executable in /data/virtual/venv/bin/python3.6
Also creating executable in /data/virtual/venv/bin/python
Installing setuptools, pip, wheel...done.
[root@localhost /data/virtual]# ll
总用量 0
drwxr-xr-x 5 root root 56 7月   4 00:32 venv
[root@localhost /data/virtual]# ls -a
.  ..  venv
[root@localhost /data/virtual]# source venv/bin/activate
(venv) [root@localhost /data/virtual]#
(venv) [root@localhost /data/virtual]# python --version
Python 3.6.8

其他相关命令

退出虚拟环境
(venv) [root@localhost /data/virtual]# deactivate
[root@localhost /data/virtual]#

删除虚拟环境
[root@localhost /data/virtual]# ls
venv
[root@localhost /data/virtual]# rm -rf venv/
[root@localhost /data/virtual]# ls
[root@localhost /data/virtual]#

代码环境

准备sqlite环境

查看默认版本
(venv) [root@localhost /data/softs]# /usr/bin/sqlite3 -version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668
更新sqlite版本为3.9+版本
(venv) [root@localhost ~]# cd /data/softs
(venv) [root@localhost /data/softs]# wget https://www.sqlite.org/2022/sqlite-autoconf-3390000.tar.gz
(venv) [root@localhost /data/softs]# tar xf sqlite-autoconf-3390000.tar.gz
(venv) [root@localhost /data/softs]# cd sqlite-autoconf-3390000
(venv) [root@localhost /data/softs]# ./configure
(venv) [root@localhost /data/softs]# make && make install
(venv) [root@localhost /data/softs/sqlite-autoconf-3390000]# sqlite3 --version
3.39.0 2022-06-25 14:57:57 14e166f40dbfa6e055543f8301525f2ca2e96a02a57269818b9e69e162e98918

echo 'export LD_LIBRARY_PATH="/usr/local/lib"' >> ~/.bashrc
source ~/.bashrc
系统环境变量设置
(venv) [root@localhost ~]# echo 'export LD_LIBRARY_PATH="/usr/local/lib"' >> ~/.bashrc
(venv) [root@localhost ~]# source ~/.bashrc

django环境

进入python虚拟环境部署django环境
[root@localhost ~]# source /data/virtual/venv/bin/activate
(venv) [root@localhost ~]# pip install Django==3.2.0

查看软件安装效果
(venv) [root@localhost ~]# pip list
Package           Version
----------------- -------
asgiref           3.4.1
Django            3.2.0
pip               21.3.1
pytz              2022.1
setuptools        28.8.0
sqlparse          0.4.2
typing_extensions 4.1.1
wheel             0.29.0
创建项目
(venv) [root@localhost ~]# mkdir /data/server -p
(venv) [root@localhost ~]# cd /data/server/
(venv) [root@localhost /data/server]# django-admin startproject web_site
(venv) [root@localhost /data/server]# ls
web_site
(venv) [root@localhost /data/server]# tree web_site/
web_site/
├── manage.py
└── web_site
    ├── asgi.py
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

1 directory, 6 files
创建应用
(venv) [root@localhost /data/server]# cd web_site/
(venv) [root@localhost /data/server/web_site]# python manage.py startapp app1
(venv) [root@localhost /data/server/web_site]# tree
.
├── app1
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── web_site
    ├── asgi.py
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-36.pyc
    │   └── settings.cpython-36.pyc
    ├── settings.py
    ├── urls.py
    └── wsgi.py

4 directories, 15 files

注册应用
(venv) [root@localhost /data/server/web_site]# sed -i "/staticfiles/a\    'app1'," web_site/settings.py

定制访问逻辑

定制访问页面的逻辑
(venv) [root@localhost /data/server/web_site]# cat app1/views.py
from django.shortcuts import render
from django.http import HttpResponse

def hello(resquest):
   return HttpResponse("web_site V1.0\n")
   
定制访问页面的路由
(venv) [root@localhost /data/server/web_site]# sed -i '/t path/a\from app1.views import *' web_site/urls.py
(venv) [root@localhost /data/server/web_site]# sed -i "/admin.site/a\    path('hello/', hello)," web_site/urls.py
启动djang应用
(venv) [root@localhost /data/server/web_site]# python  manage.py runserver>> /dev/null 2>&1 &
[1] 4585

检查效果
(venv) [root@localhost /data/server/web_site]# curl localhost:8000/hello/
web_site V1.0

web环境

ngix环境

安装nginx
[root@localhost ~]# yum install nginx -y

启动nginx
[root@localhost ~]# systemctl start nginx

查看状态
[root@localhost ~]# netstat -tnulp | grep nginx
tcp        0      0 0.0.0.0:80    0.0.0.0:*      LISTEN  4725/nginx: master
tcp6       0      0 :::80         :::*           LISTEN 4725/nginx: master

nginx配置

创建配置文件
[root@localhost ~]# cat /etc/nginx/default.d/django.conf
  location /hello/ {
    proxy_pass http://localhost:8000;
  }

测试配置文件
[root@localhost ~]# /usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

重载nginx服务
[root@localhost ~]# systemctl reload nginx
测试访问效果
[root@localhost ~]# echo 'hello nginx' > /usr/share/nginx/html/index.html
[root@localhost ~]# curl localhost
hello nginx
[root@localhost ~]# curl localhost/hello/
web_site V1.0

定制访问域名
[root@localhost ~]# echo '10.0.0.12 django.superopsmsb.com' >> /etc/hosts
[root@localhost ~]# curl django.superopsmsb.com
hello nginx
[root@localhost ~]# curl django.superopsmsb.com/hello/
web_site V1.0

21.2.3 手工发布

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	为了合理的演示生产环境的项目代码发布,同时又兼顾实际实验环境的资源,我们这里将 B主机和C主机 用一台VM主机来实现,A主机单独实现。这两台主机的ip地址相关内容如下:
	A主机:10.0.0.12   B主机: 10.0.0.13
	为了体现整个实践操作的标准化,在这里进行所有目录的统一规划:
		代码存储目录: /data/codes/django
		打包文件目录: /data/codes
		脚本相关目录: /data/scripts
		备份文件目录: /data/backup/django
		项目代码目录: /data/server/web_site

简单实践

代码准备-10.0.0.13

准备待发布代码
[root@localhost ~]# mkdir /data/codes
[root@localhost ~]# cd /data/codes/
[root@localhost /data/codes]# mkdir django
[root@localhost /data/codes]# scp root@10.0.0.12:/data/server/web_site/app1/views.py django/
[root@localhost /data/codes]# cat django/views.py
from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
# 定制后端业务逻辑处理函数
def hello(request):
    return HttpResponse("web_site v0.1\n")

代码获取-10.0.0.13

我们借助于sed的方式模拟代码获取修改后的代码
[root@localhost /data/codes]# sed -i 's#0.1#0.2#' django/views.py
[root@localhost /data/codes]# grep web_site django/views.py
    return HttpResponse("web_site v0.2\n")

打包代码-10.0.0.13

打包文件
[root@localhost /data/codes]# tar zcf django.tar.gz django/
[root@localhost /data/codes]# ls
django  django.tar.gz

确认文件信息
[root@localhost /data/codes]# zcat  django.tar.gz
django/0000...026 5ustar  rootrootdjango/views.py000...12531 0ustar  rootrootfrom django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
# 定制后端业务逻辑处理函数
def hello(request):
    return HttpResponse("web_site v0.2\n")

传输代码-10.0.0.12

在制定的目录下获取远程代码目录
[root@localhost ~]# mkdir /data/codes
[root@localhost ~]# cd /data/codes/
[root@localhost ~]# scp root@10.0.0.13:/data/codes/django.tar.gz ./

关闭应用-10.0.0.12

前端准备数据迁移配置
[root@localhost ~]# mkdir /etc/nginx/conf.d
[root@localhost ~]# cat /etc/nginx/conf.d/update.conf
server {
  listen 6666;
  location / {
    index index.html;
    root /usr/share/nginx/update/;
  }
}

准备数据迁移文件
[root@localhost ~]# mkdir /usr/share/nginx/update -p
[root@localhost ~]# echo '数据迁移中,请耐心等待,抱歉!!!' >> /usr/share/nginx/update/index.html
使用数据迁移配置
[root@localhost ~]# cat /etc/nginx/default.d/django.conf
location /hello/ {
  # proxy_pass http://localhost:8000;
  proxy_pass http://10.0.0.12:6666/;
}

重启nginx服务
[root@localhost ~]# /usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost ~]# systemctl restart nginx
检查效果
[root@localhost ~]# netstat -tnulp |grep nginx
tcp        0      0 0.0.0.0:6666   0.0.0.0:*   LISTEN      92562/nginx: master
tcp        0      0 0.0.0.0:80     0.0.0.0:*   LISTEN      92562/nginx: master
tcp6       0      0 :::80          :::*        LISTEN      92562/nginx: master
[root@localhost ~]# curl www.superopsmsb.com/hello/
数据迁移中,请耐心等待,抱歉!!!
关闭后端的django服务
[root@localhost ~]# kill $(lsof -Pti :8000)
[root@localhost ~]# netstat -tnulp | grep 8000
[root@localhost ~]#

解压代码-10.0.0.12

[root@localhost ~]# cd /data/codes
[root@localhost /data/codes]# tar xf django.tar.gz

备份文件-10.0.0.12

备份老文件
[root@localhost /data/codes]# mkdir /data/backup/django -p
[root@localhost /data/codes]# mv /data/server/web_site/app1/views.py /data/backup/django/views.py-$(date +%Y%m%d%H%M%S)
[root@localhost /data/codes]# ls /data/server/web_site/app1/views.*
ls: 无法访问/data/server/web_site/app1/views.*: 没有那个文件或目录

放置新文件
[root@localhost /data/codes]# mv /data/codes/django/views.py /data/server/web_site/app1/
[root@localhost /data/codes]# ls /data/server/web_site/app1/views.*
/data/server/web_site/app1/views.py
[root@localhost /data/codes]# grep web_site /data/server/web_site/app1/views.py
    return HttpResponse("web_site v0.2\n")

开启应用-10.0.0.12

开启后端django服务
[root@localhost /data/codes]# source /data/virtual/venv/bin/activate
(venv) [root@localhost /data/codes]# cd /data/server/web_site/
(venv) [root@localhost /data/server/web_site]# python manage.py runserver >> /dev/null 2>&1 &
[1] 92774
(venv) [root@localhost /data/server/web_site]# deactivate
[root@localhost /data/server/web_site]#

检查效果
[root@localhost ~]# netstat -tnulp | grep 8000
tcp        0      0 127.0.0.1:8000  0.0.0.0:*    LISTEN      92776/python
修改前端nginx服务入口
[root@localhost ~]# cat /etc/nginx/default.d/django.conf
location /hello/ {
  proxy_pass http://localhost:8000;
  # proxy_pass http://10.0.0.12:6666/;
}

重启nginx服务
[root@localhost /data/server/web_site]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost /data/server/web_site]# systemctl restart nginx

内部检查-10.0.0.12

检查效果
[root@localhost /data/server/web_site]# curl www.superopsmsb.com
hello nginx
[root@localhost /data/server/web_site]# curl www.superopsmsb.com/hello/
web_site v0.2

对外开放

接收外部用户流量即可

22 脚本发布

22.1 简单脚本

22.1.1 命令罗列

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

目的:
	实现代码仓库主机上的操作命令功能即可

简单实践

实践

查看脚本内容
#!/bin/bash
# 功能:打包代码
# 版本: v0.1
# 作者: 书记
# 联系: superopsmsb.com

cd /data/codes
[ -f django.tar.gz ] && rm -f django.tar.gz
tar zcf django.tar.gz django	

脚本实践

脚本编写完成后,进行测试:
sed -i 's#1.1#1.2#' /data/server/web_site/views.py
bash /data/scripts/tar_code.sh
查看压缩文件内容
zcat django.tar.gz 

22.1.2 变量转化

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

问题:
	脚本里面的手写的固定的内容太多了,更改时候费劲
	所以通过变量的方式实现信息的固化

简单实践

实践

查看脚本内容
#!/bin/bash
# 功能:打包代码
# 版本: v0.2
# 作者: 书记
# 联系: superopsmsb.com

FILE='django.tar.gz'
CODE_DIR='/data/codes'
CODE_PRO='django'

cd "${CODE_DIR}"
[ -f "${FILE}" ] && rm -f "${FILE}"
tar zcf "${FILE}" "${CODE_PRO}"	

脚本实践

脚本编写完成后,进行测试:
sed -i 's#1.2#1.3#' /data/server/web_site/views.py
bash /data/scripts/tar_code.sh
查看压缩文件内容
zcat django.tar.gz

22.1.3 功能函数

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	三条命令其实是一个组合,实现的是一个功能

简单实践

实践

查看脚本内容
#!/bin/bash
# 功能:打包代码
# 版本: v0.3
# 作者: 书记
# 联系: superopsmsb.com

FILE='django.tar.gz'
CODE_DIR='/data/codes'
CODE_PRO='django'

code_tar(){
	cd "${CODE_DIR}"
	[ -f "${FILE}" ] && rm -f "${FILE}"
	tar zcf "${FILE}" "${CODE_PRO}"	
}
code_tar

脚本实践

脚本编写完成后,进行测试:
sed -i 's#1.3#1.4#' /data/server/web_site/views.py
bash /data/scripts/tar_code.sh
查看压缩文件内容
zcat django.tar.gz

22.1.3 远程执行

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

有时候,我们需要通过远程方式到另外一台主机进行脚本的执行
格式:
	ssh 远程主机登录用户名@远程主机ip地址 "执行命令"

效果

[root@localhost ~]# ssh root@10.0.0.13 "ifconfig eth0"
root@10.0.0.13's password:
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.13  netmask 255.255.255.0  broadcast 10.0.0.255
        ...

简单实践

实践

远程更新文件内容
ssh root@10.0.0.13 "sed -i /'s#1.4#1.5#' /data/server/web_site/views.py"
远程查看脚本
ssh root@10.0.0.13 "ls /data/scripts"
远程执行脚本
ssh root@10.0.0.13 "/bin/bash /data/scripts/tar_code.sh"
远程检查更新效果
ssh root@10.0.0.13 "zcat /data/server/web_site.tar.gz"

22.2 大型脚本

22.2.1 功能框架

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

问题:为什么不按照简单脚本的思路进行编写

为什么?
    1、命令多
    2、功能多
    3、不好组合
解决方案:
    一句话:化整为零,各个击破

脚本框架

编写大型脚本有一个流程:
    一、脚本框架
    二、命令填充
    三、完善功能
        增加日志功能
        增加锁文件功能
        增加主函数逻辑
        增加参数安全措施

需求

完成代码发布流程框架,一个流程(步骤)即一个功能
	- 用函数来实现

简单实践

实践

脚本内容
#!/bin/bash
# 功能:打包代码	
# 版本: v0.1
# 作者: 书记
# 联系: superopsmsb.com

# 获取代码
get_code(){
  echo "获取代码"
}

# 打包代码
tar_code(){
  echo "打包代码"
}

# 传输代码
scp_code(){
  echo "传输代码"
}

# 关闭应用
stop_serv(){
  echo "关闭应用"
  echo "关闭nginx应用"
  echo "关闭django应用"
}

# 解压代码
untar_code(){
  echo "解压代码"
}

# 放置代码
fangzhi_code(){
  echo "放置代码"
  echo "备份老文件"
  echo "放置新文件"
}

# 开启应用
start_serv(){
  echo "开启应用"
  echo "开启django应用"
  echo "开启nginx应用"
}

# 检查
check(){
  echo "检查项目"
}

# 部署函数
deploy_pro(){
  get_code
  tar_code
  scp_code
  stop_serv
  untar_code
  fangzhi_code
  start_serv
  check
}

# 主函数
main(){
  deploy_pro
}

# 执行主函数
main

22.2.2 命令填充

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	在流程跑通的情况下,执行完整的代码部署过程
方案:
	在脚本框架中,填写执行成功的命令

简单实践

实践

2#!/bin/bash
# 功能:打包代码	
# 版本: v0.2
# 作者: 书记
# 联系: superopsmsb.com

# 获取代码
get_code(){
  echo "获取代码"
}

# 打包代码
tar_code(){
  echo "打包代码"
  ssh root@10.0.0.13 "/bin/bash /data/scripts/tar_code.sh"
}

# 传输代码
scp_code(){
  echo "传输代码"
  cd /data/codes
  [ -f django.tar.gz ] && rm -f django.tar.gz
  scp root@10.0.0.13:/data/server/web_site.tar.gz ./
}

# 关闭应用
stop_serv(){
  echo "关闭应用"
  echo "关闭nginx应用"
  /data/server/nginx/sbin/nginx -s stop
  echo "关闭django应用"
  kill $(lsof -Pti :8000)
}

# 解压代码
untar_code(){
  echo "解压代码"
  cd /data/codes
  tar xf django.tar.gz
}

# 放置代码
fangzhi_code(){
  echo "放置代码"
  echo "备份老文件"
  mv /data/server/web_site/app1/views.py /data/backup/views.py-$(date +%Y%m%d%H%M%S)
  echo "放置新文件"
  mv /data/server/web_site/views.py /data/server/web_site/app1/
}

# 开启应用
start_serv(){
  echo "开启应用"
  echo "开启django应用"
  source /data/virtual/venv/bin/activate
  cd /data/server/web_site/
  python manage.py runserver >> /dev/null 2>&1 &
  deactivate
  echo "开启nginx应用"
  /data/server/nginx/sbin/nginx
}
# 检查
check(){
  echo "检查项目"
  netstat -tnulp | grep ':80'
}

...

22.2.3 日志功能

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	1、追踪记录
	2、数据说话
方案:
	增加日志功能
	1、日志文件
		/data/logs/deploy.log
	2、日志格式
		日期	时间	脚本名称	步骤
知识点:
	文件内容追加: >>
	日期:date +%F
	时间:date +%T
	脚本:$0

简单实践

实践

#!/bin/bash
...
LOG_FILE='/data/logs/deploy.log'
# 增加日志功能
write_log(){
  DATE=$(date +%F)
  TIME=$(date +%T)
  buzhou="$1"
  echo "${DATE} ${TIME} $0 : ${buzhou}" >> "${LOG_FILE}"
}

# 获取代码
get_code(){
  ...
  write_log "获取代码"
}

# 打包代码
tar_code(){
  ...
  write_log "打包代码"
}

# 传输代码
scp_code(){
  ...
  write_log "传输代码"
}

# 关闭应用
stop_serv(){
  ...
  write_log "关闭应用"
  ...
  write_log "关闭nginx应用"
  ...
  write_log "关闭django应用"
}

# 解压代码
untar_code(){
  ...
  write_log "解压代码"
}

# 放置代码
fangzhi_code(){
  ...
  write_log "放置代码"
  ...
  write_log "备份老文件"
  ...
  write_log "放置新文件"
}

# 开启应用
start_serv(){
  ...
  write_log "开启应用"
  ...
  write_log "开启django应用"
  ...
  write_log "开启nginx应用"
}
# 检查
check(){
  ...
  write_log "检查项目"
}

...

22.2.4 锁文件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	同一时间段内,只允许有一个用户来执行这个脚本
	如果脚本执行的时候,有人在执行,那么输出报错:
	脚本 deploy.sh 正在运行,请稍候...
设计:
	1、锁文件	/tmp/deploy.pid
	2、存在锁文件时候,输出报错信息
	3、脚本执行的时候,需要创建锁文件
	4、脚本执行结束的时候,需要删除锁文件
知识点:
    条件和结果: 双分支if语句
    文件表达式: -f  file_name
    验证表达式: [ 表达式 ]
    创建和删除命令:touch、rm -f

简单实践

实践

#!/bin/bash
...
PID_FILE='/tmp/deploy.pid'
...
# 增加锁文件功能
add_lock(){
  echo "增加锁文件"
  touch "${PID_FILE}"
  write_log "增加锁文件"
}

# 删除锁文件功能
del_lock(){
  echo "删除锁文件"
  rm -f "${PID_FILE}"
  write_log "删除锁文件"
}

# 部署函数
deploy_pro(){
  add_lock
  ...
  del_lock
}

# 脚本报错信息
err_msg(){
  echo "脚本 $0 正在运行,请稍候..."
}

# 主函数
main(){
if [ -f "${PID_FILE}" ]
then
      err_msg
else
      deploy_pro
fi
}

# 执行主函数
main

22.2.5 流程控制

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	如果我给脚本输入的参数是deploy,那么脚本才执行,否则的话,提示该脚本的使用帮助信息,然后退出
提示信息:脚本 deploy.sh 的使用方式: deploy.sh [ deploy ]
分析:
    1、脚本传参,就需要在脚本内部进行调用参数
    2、脚本的帮助信息
    3、脚本内容就需要对传参的内容进行判断
知识点:
    1、shell内置变量:$n
    2、帮助信息: 简单函数定义和调用
    3、内容判断: 多if语句或者case语句
方案:
	1、脚本的传参
        脚本执行:bash deploy.sh deploy
        位置参数的调用: $1
	2、脚本的帮助信息
        定义一个usage函数,然后调用。
        提示信息格式:
        脚本 deploy.sh 的使用方式: deploy.sh [ deploy ]
	3、内容判断
        main函数体调用函数传参: $1
        在main函数中,结合case语句,对传入的参数进行匹配
            如果传入参数内容是"deploy",那么就执行代码部署流程
            如果传入参数内容不是"deploy",那么输出脚本的帮助信息
        if语句和case语句的结合
        	case语句在外,if语句在内

简单实践

实践

#!/bin/bash
...

# 脚本帮助信息
usage(){
  echo "脚本 $0 的使用方式: $0 [deploy]"
  exit
}

# 主函数
main(){
  case "$1" in 
    "deploy")
      if [ -f "${PID_FILE}" ]
      then
         err_msg
      else
        deploy_pro
      fi
    ;;
    *)
      usage
    ;;
  esac
}

# 执行主函数
main $1

22.2.6 参数安全

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	对脚本传入的参数的数量进行判断,如果参数数量不对,提示脚本的使用方式,然后退出
分析:
    1、脚本参数数量判断
    2、条件判断
        数量对,那么执行主函数
        数量不对,那么调用脚本帮助信息
知识点:
    1、脚本参数数量判断
        shell内置变量: 	$#
        条件表达式:	   [ $# -eq 1 ]
    2、条件判断:
    	双分支if语句    
方案:
	1、双分支if语句 + main函数调用

简单实践

实践

#!/bin/bash
...

# 执行主函数
if [ $# -eq 1 ] 
then
  main $1
else
  usage
fi

22.2.7 脚本调试

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

我们介绍脚本调试的时候呢,主要分三种方式来介绍:

    -n	检查脚本中的语法错误
    -v	先显示脚本所有内容,然后执行脚本,结果输出,如果执行遇到错误,将错误输出。
    -x	将执行的每一条命令和执行结果都打印出来

简单实践

实践


22.3 脚本技巧

22.3.1 技巧解读

这一节,我们从 简单脚本、复杂脚本、注意事项、小结 四个方面来学习

简单脚本

简介

	1、手工执行的命令一定要可执行
	2、命令简单罗列
	3、固定的内容变量化
	4、功能函数化

复杂脚本

实践

	1、手工执行的命令一定要可执行
	2、根据发布流程编写脚本的框架
	3、将手工执行的命令填充到对应的框架函数内部
	4、增加日志功能,方便跟踪脚本历史执行记录
	5、主函数中逻辑流程控制好
	6、设计安全的方面:
		增加锁文件,保证代码发布的过程中不受干扰,
		输入参数数量
		输入参数匹配
		脚本帮助信息
	7、调试脚本

注意事项

	1、命令一定要保证能正常执行
	2、成对的符号,要成对写,避免丢失
	3、函数调用,
		写好函数后,一定要在主函数中进行调用
	4、避免符号出现中文
	5、命令变量的写法一定要规范
	6、固定的内容一定要变量实现,方便以后更改
	7、日志的输出
	8、脚本的传参和函数的传参要区别对待
posted on 2024-05-06 23:46  苦行僧DH  阅读(83)  评论(0编辑  收藏  举报