云计算脱产班: Shell任务自动化编程实战


 

一、Shell含义诠释

1.1 什么是shell

  shell本意指“壳”, 但在IT领域中多指 “指挥操作系统完成任务执行或信息展示的指令集”, 就比如 Windows中的PowerShell、macOS中的zsh、Linux操作系统中的shell等都是用来控制操作系统的指令集; 通过 类shell指令可完成在无操作界面的系统上完成任务发布和任务部署, 熟练编写指令以及指令集文件(脚本)也是运维工程师必备的技能之一;

  对于shell而言几乎是占领了所有服务端操作系统的交互式命令行; 比如在CentOS上使用的ls, cd等都是属于shell指令集的, 使用它们可以完成特定的任务以及展示特定的信息, 而这些指令又可以配合选项、参数来完成更加复杂的操作, 这就形成了丰富的shell指令集, 让运维工程师能够使用命令行完成更加复杂高级的操作;

 

1.2 什么是shell脚本?

  shell脚本指的是: “一个存储有逻辑、有顺序的多个命令的文本文件”; 对于一些比较复杂的任务, 通常需要使用很多条指令来完成, 并在执行时要保障执行的顺序, 以及执行的逻辑; 而且对于命令的选型也是有严格要求的, 为了更加顺滑的执行要避免使用具备交互属性的指令;

For Example PR: 本公司前端开发框架采用 Vue , 前端开发人员在研发过程中经常需要对项目进行打包构建并运行测试, CTO为简化研发过程中的测试难度, 需要你编写一个脚本来帮助研发一键执行测试.

 

二、Shell基本语法

2.1 变量 variable

  对于在shell脚本中, 经常使用的就是变量了, 变量是由变量名和值组成的, 变量名字可以被赋予不同的值, 想要在脚本中调用该值, 可使用变量名进行调用; 变量声明的方式为 NAME=VALUE, 注意等号两边是没有空格的, 此处严格要求; 调用值时使用 ${NAME} 进行调用, 大括号可有效避免其他字符“混入”变量名中;

 

2.2 流程控制之 if

  在shell脚本中, 由于要顾及到文件的执行逻辑性, 所以需要使用到条件判断来增加脚本的逻辑性; 在生产中单分支条件判断使用的频次是较为突出的, 多分支在流程控制上使用的较为突出, 但从整体代码易读性而言单分支条件判断配合取反思想可以达到意想不到的效果;

上述代码框架中的segment可以继续编写if条件判断, 但值得注意的是需要将框架写全, 也就是注意整体性; CONDITION所编写的表达式必须返回True/False这两种结果; CONDITION经常使用 [] 来获取命令以及表达式的布尔值结果从而重定向到对应的分支中, 来形成流程上的控制;

在上述的代码片段中, 涉及到变量的定义和if条件判断的框架; 但对于双引号中调用变量会有的一个疑问是: 双引号和单引号有没有区别呢?答案是肯定的; 单引号中不能调用变量, 因为单引号是强引, 所以并不会解释调用变量的字符$; 对于条件判断中的 [] 符号, 是用来将其内部表达式的布尔值取出来的;

表达式中的$? 是用来获取上条命令执行的状态返回码用的, 当返回0时即为正确, 返回非0时即为错误。特殊符号还有很多比如:

当然还包含 -eq 这样的比较运算符, 用来比较数字的大小, 不能被用来比较字符串等其他字符, 类似的符号还有:

如果想判断文件或者目录存在与否, 可以按照下方的写法进行逻辑判断:

作业:

 

2.3 流程控制之 case

  使用if流程控制也可以实现多条件判断, 但会使代码显得臃肿不易阅读, 所以大多数时候编写shell脚本都配合单分支及取反思想来实现判断需求; 而case是天生的多条件判断分支模型, 当分支较多并且判断条件比较简单时使用 case in 语句就比较方便了;

case、in 和 esac 都是 Shell 关键字, expression 表示表达式, pattern 表示匹配模式:

case 会将 expression 的值与 pattern1、pattern2、pattern3 逐个进行匹配:

 

*)的几点说明:

 

  除最后一个分支外(这个分支可以是普通分支, 也可以是*)分支), 其它的每个分支都必须以;;结尾, ;;代表一个分支的结束, 不写的话会有语法错误。最后一个分支可以写;;, 也可以不写, 因为无论如何, 执行到 esac 都会结束整个 case in 语句。

  case经常搭配getops指令实现自定义命令的编写, 可编写出和Linux中自建命令相同的使用效果; getopts命令是用来解析Shell脚本命令行参数的工具, getopts命令参数包含需要被识别的选项字符, 如果选项字符后面跟着一个冒号, 则表明该字符选项需要一个命令行参数(选项字符与对应的命令行参数之间以空格分隔)(注意:冒号&问号不能被用作为选项字符)。getopts命令每次被调用时, 它会将下一个选项字符放置到变量中, OPTARG则可以拿到参数值;如果option前面加冒号, 则代表忽略错误

作业:

 

2.4 循环控制 for

  在shell脚本中, 经常要处理很多重复的动作和任务, 并在每次处理的时候根据不同的结果要导向不同的处理方式, 也就是说for、while这类循环控制通常都会在循环体中使用if或case来进行流程上的控制; 循环可以使机器帮助程序员完成重复且复杂的任务, 提高生产效率的同时又能保证其精确性;

 

  在for循环中处理重复任务时, 通常会在得到结果时又有后续的循环未执行, 此时却需要停下来, 那么这种情况就需要到break 指令了; 在for循环中处理任务的过程中, 某些循环内容不符合本次处理的规范, 所以要跳过这次循环, 那么就需要用continue 指令了;

 

  在for循环中, 由于需要重复的处理循环体内的任务, 但有个缺点是下一个循环体开始要等待上一个循环体结束, 所以当循环次数少时, 时间还可以接受, 但当循环次数增加, 则执行完整个循环的时间就会呈指数增加; 为了避免掉时间维度的问题就需要使用到异步方式进行循环, 以此来缩短整体循环的执行时间; 这也是常说的宏观层面的并发执行;

 

2.5 数据结构 array

  数组是能够存储任意数据的容器, 使用数组可以很好的将不同类型的数据组织在一起, 且易于使用; 数组的声明 array=(A B C) 数组中每个元素使用空格分割, 获取数组中的单个元素使用 $array[n] n的取值范围为 0 <= n <= ${#array[@]}-1;

 

  在shell中上述的一维数组有时并不满足关联需求, 比如统计某个词在文章中出现的次数, 这就需要用到关联数组了; 关联数组的定义形式: declare -A array ; 此形式可声明一个关联数组, 在关联数组中有 键值对 键在关联数组中唯一, 值不唯一; 每个键可对应一个值;

 

2.6 函数 function

  在编写shell脚本时, 经常会在不同的位置使用相同的代码来实现数据的处理, 这时如果将相同的代码编写两次, 就会造成代码的冗余性增强, 不易于阅读, 且在不同文件中也会存在这样的问题; 如果使用函数就能很方便的解决这个问题, 让代码更加易于阅读, 且结构更加清晰;

在同一个文件中, 在不同位置调用函数:

构建函数库, 在不同文件中调用函数:

函数传参:

函数作用域:

 

2.7 Expect自动应答

  对于在shell脚本中经常不可避免的需要对系统的提示进行手动键入, 这时在遇到系统键入提示时, 就需要使用自动应答函数来实现对系统键入提示的自动输入, 既可以提高效率又可以避免重复劳动;

 

三、Shell文本处理

3.1 正则表达式

  正则表达式是编程语言中最重要的一个学习环节, 也是晋升高级开发的必经之路, 正则表达式可以帮助开发人员尽可能多的匹配到符合条件的数据, 在shell中经常与 grep 搭配使用来过滤出符合条件的数据; 对于正则表达式而言在任何语言中都是通用的, 包含元字符和拓展元字符两部分;

  推荐两款用于学习 正则表达式的工具 devtoys客户端 以及 iHateRegex网站;

文本定位符含义例子说明
^行首^abc匹配以abc开头的行
$行尾abc$匹配以abc结尾的行
<单词开头\<is仅在单词开头时匹配is
>单词末尾on\>仅在单词结尾匹配on

搜索文本时,仅根据字符分类来选择字符会很有用。字符的基本类别是数字和字母和其他字符(例如空格和标点符号)

字符含义例子说明 
.任意单个字符a.b匹配 “a任意一个字符b” 
\d任意单个数字(在使用grep时需加P参数) [0-9]a\db匹配 “a任意一个数字b” 
\D任意单个非数字(在使用grep时需加P参数)a\Db匹配 “a任意一个标点或特殊字符或下划线b” 
\w任意单个字母、数字、下划线a\wb...... 
\s任意单个空白字符(空格、制表符)a\sb...... 

匹配字符的数量,例如你可以精确匹配六个空格,或定位长度在四到八位之间的数字、字符串

字符含义例子说明
*前导符零个到多个hlio*ns匹配hlions、hlins、hliooooons
{m}前导符出现m次hlio{3}ns匹配hliooons
{m,}前导符至少出现m次hlio{3, }ns匹配hliooons、hliooooons、hlioooooons
{m, n}前导符至少出现m次, 至多出现n次hlio{3,5}ns匹配hliooons、hlioooons、hliooooons
?前导符出现0次或1次hlio?ns匹配hlions、hlins
+前导符出现1次或多次hlio+ns匹配hlions、hliooooooooooons

字符集是符合搜索条件的字符的列表,通过将一组字符括在方括号中来指示字符集。

字符范围是字符集的一种它在字符之间使用 - 来暗示它们之间的整个字符范围,以及开始和结束字符本身

字符含义例子说明
[abc]方括号中任意一个字符[abcefg]匹配abcefg中任意一个字符
[^abc]除了方括号中任意一个字符[^abcefg]匹配除了abcefg中的人一个字符(取反)
[0-9]0到9共十个阿拉伯数字[0-9]匹配0~9范围内的数 0<=x <=9
[a-zA-Z]匹配a~z或A~Z范围内的一个字符[a-zA-Z]......
(acd|b)匹配a或者b (a&b)(a|b)小括号的意义: 分组, 被小括号扩起来的两个逻辑条件为一个整体

 

最后说一下如果想匹配正则表达式中的特殊字符,比如 \, ^, *, {, [ 等,因为它们有特殊含义,所以需要另外加 \ 来表示,比如 * 表示 \*,\ 表示 \\

 

  1. 匹配所有的IP地址

     

  2. 匹配11位电话号

     

  3. 匹配年月日日期 格式2018-08-06

  4. 匹配长度为8-10位的用户密码 : 包含数字字母下划线

 

3.1.1 正则表达式速查手册之 校验数字表达式
  1. 数字:^[0-9]*$

  2. n位的数字:^\d{n}$

  3. 至少n位的数字:^\d{n,}$

  4. m-n位的数字:^\d{m,n}$

  5. 零和非零开头的数字:^(0|[1-9][0-9]*)$

  6. 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$

  7. 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$

  8. 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$

  9. 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$

  10. 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$

  11. 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$

  12. 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$

  13. 非负整数:^\d+$ 或 ^[1-9]\d*|0$

  14. 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$

  15. 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$

  16. 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$

  17. 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$

  18. 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$

  19. 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

     

3.1.2 正则表达式速查手册之 校验字符的表达式
  1. 汉字:^[\u4e00-\u9fa5]{0,}$

  2. 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$

  3. 长度为3-20的所有字符:^.{3,20}$

  4. 由26个英文字母组成的字符串:^[A-Za-z]+$

  5. 由26个大写英文字母组成的字符串:^[A-Z]+$

  6. 由26个小写英文字母组成的字符串:^[a-z]+$

  7. 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$

  8. 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}

  9. 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$

  10. 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$

  11. 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+

  12. 禁止输入含有~的字符[^~\x22]+

 

3.1.3 正则表达式速查手册之 特殊需求表达式
  1. Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$

  2. 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?

  3. InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$

  4. 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$

  5. 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$

  6. 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}

  7. 身份证号(15位、18位数字):^\d{15}|\d{18}$

  8. 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$

  9. 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

  10. 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$

  11. 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$

  12. 日期格式:^\d{4}-\d{1,2}-\d{1,2}

  13. 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$

  14. 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$

 

3.2 sed文本修改

sed是个流编辑器, 可以修改文件中的内容, 按照增删改查顺序学习

如果要将文件中的内容进行修改, 请使用 -i 选项, 要想 在sed中增加多个匹配模式或动作, 使用 -e

 

3.3 awk文本切割

awk 'BEGIN{} 条件{} END{}' 文件名

 

作业:

  1. 获取当前系统cpu使用率

  2. 获取当前系统memory使用率

  3. 获取当前系统磁盘使用率

  4. 请统计出ip_list.txt中所有IP地址出现次数, 并取其前十名

  5. 输出/etc/passwd文件中, gid小于100的组; 要求输出 组名 gid

 

3.3.1 日志处理速查手册

1、查看有多少个IP访问:

2、查看某一个页面被访问的次数:

3、查看每一个IP访问了多少个页面:

4、将每个IP访问的页面数进行从小到大排序:

5、查看某一个IP访问了哪些页面:

6、去掉搜索引擎统计的页面:

7、查看2015年8月16日14时这一个小时内有多少IP访问:

8、查看访问前十个ip地址

uniq -c 相当于分组统计并把统计数放在最前面

9、访问次数最多的10个文件或页面

访问量最大的前20个ip

10、通过子域名访问次数,依据referer来计算,稍有不准

11、列出传输大小最大的几个文件

12、列出输出大于200000byte(约200kb)的页面以及对应页面发生次数

13、如果日志最后一列记录的是页面文件传输时间,则有列出到客户端最耗时的页面

14、列出最最耗时的页面(超过60秒的)的以及对应页面发生次数

15、列出传输时间超过 30 秒的文件

16、列出当前服务器每一进程运行的数量,倒序排列

17、查看apache当前并发访问数

对比httpd.conf中MaxClients的数字差距多少

18、可以使用如下参数查看数据

统计httpd进程数,连个请求会启动一个进程,使用于Apache服务器。

表示Apache能够处理1388个并发请求,这个值Apache可根据负载情况自动调整

netstat -an会打印系统当前网络链接状态,而grep -i “80”是用来提取与80端口有关的连接的,wc -l进行连接数统计。 最终返回的数字就是当前所有80端口的请求总数

netstat -an会打印系统当前网络链接状态,而grep ESTABLISHED 提取出已建立连接的信息。然后wc -l统计 最终返回的数字就是当前所有80端口的已建立连接的总数。

可查看所有建立连接的详细记录

19、输出每个ip的连接数,以及总的各个状态的连接数

20、其他的收集

分析日志文件下 2012-05-04 访问页面最高 的前20个 URL 并排序

查询受访问页面的URL地址中 含有 www.abc.com 网址的 IP 地址

获取访问最高的10个IP地址 同时也可以按时间来查询

时间段查询日志时间段的情况

分析 2015/8/15 到 2015/8/16 访问”/index.php?g=Member&m=Public&a=sendValidCode”的IP倒序排列

($7~/.php/) $7里面包含.php的就输出,本句的意思是最耗时的一百个PHP页面

列出最最耗时的页面(超过60秒的)的以及对应页面发生次数

统计网站流量(G)

统计404的连接

统计http status

每秒并发

带宽统计

找出某天访问次数最多的10个IP

当天ip连接数最高的ip都在干些什么

小时单位里ip连接数最多的10个时段

找出访问次数最多的几个分钟

取5分钟日志

if [ $DATE_MINUTE != $DATE_END_MINUTE ] ;then # 则判断开始时间戳与结束时间戳是否相等

START_LINE=sed -n "/$DATE_MINUTE/=" $APACHE_LOG|head -n1 #如果不相等,则取出开始时间戳的行号,与结束时间戳的行号

查看tcp的链接状态

查找请求数前20个IP(常用于查找攻来源):

用tcpdump嗅探80端口的访问看看谁最高

查找较多time_wait连接

找查较多的SYN连接

根据端口列进程

查看了连接数和当前的连接数

查看IP访问次数

Linux命令分析当前的链接状况

watch "netstat -n | awk '/^tcp/ {++S[\$NF]} END {for(a in S) print a, S[a]}'" # 通过watch可以一直监控

LAST_ACK 5 #关闭一个TCP连接需要从两个方向上分别进行关闭,双方都是通过发送FIN来表示单方向数据的关闭,当通信双方发送了最后一个FIN的时候,发送方此时处于LAST_ACK状态,当发送方收到对方的确认(Fin的Ack确认)后才真正关闭整个TCP连接;

SYN_RECV 30 # 表示正在等待处理的请求数;

ESTABLISHED 1597 # 表示正常数据传输状态;

FIN_WAIT1 51 # 表示server端主动要求关闭tcp连接;

FIN_WAIT2 504 # 表示客户端中断连接;

TIME_WAIT 1057 # 表示处理完毕,等待超时结束的请求数;