Shell

0、小技巧

0.1 shell中$(( )) 与 shell - 图2%20%E8%BF%98%E6%9C%89#card=math&code=%28%20%29%20%E8%BF%98%E6%9C%89&id=samVV){ }的区别

1. $()与

  1. # 在多层次的复合替换中,` ` 须要额外的跳脱( \` )处理,而 $( ) 则比较直观
  2. command1 `command2 \`command3\` `
  3. command1 $(command2 $(command3))

2. ${ } 用来作变量替换

详见1.6 变量的子串

3. $(( )) :它是用来作整数运算的

在 bash 中,$(( )) 的整数运算符号大致有这些:

        • / :分别为 “加、减、乘、除”。
  • % :余数运算
  • & | ^ !:分别为 “AND、OR、XOR、NOT” 运算

1、基础篇

Shell是一个命令解释器,它在操作系统的最外层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,输出屏幕返回给用户。

Shell脚本与php/perl/python语言的区别和优势?

shell脚本的优势在于处理操作系统底层的业务 (linux系统内部的应用都是shell脚本完成)因为有大量的linux系统命令为它做支撑。2000多个命令都是shell脚本编程的有力支撑,特别是grep、awk、sed等。例如:一键软件安装、优化、监控报警脚本,常规的业务应用,shell开发更简单快速,符合运维的简单、易用、高效原则.

1.1 系统中的shell

# 查看系统中的命解释器
[root@hchost ~]# cat /etc/shells 
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
# 操作系统的默认shell
[root@hchost ~]# echo $SHELL
/bin/bash

# bash版本
[root@hchost ~]# bash -version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

# 升级当前的bash版本
yum install update bash

# sh与bash 的关系
[root@hchost ~]# ll /bin/sh
lrwxrwxrwx. 1 root root 4 Jul 11  2019 /bin/sh -> bash
# /bin与 /user/bin 的关系
[root@hchost ~]# ll /bin
lrwxrwxrwx. 1 root root 7 Jul 11  2019 /bin -> usr/bin

1.2 脚本书写规范

1.2.1 脚本统一存放目录

[root@hchost ~]# mkdir -p /server/scripts

1.2.2 选择解释器

其中开头的”#!”字符又称为幻数

在执行bash脚本的时候,内核会根据”#!”后的解释器来确定该用那个程序解释这个脚本中的内容

[root@hchost init.d]# head -1 /etc/init.d/*
==> /etc/init.d/aegis <==
#!/bin/bash

==> /etc/init.d/functions <==
# -*-Shell-script-*-

==> /etc/init.d/netconsole <==
#!/bin/bash

==> /etc/init.d/network <==
#! /bin/bash

1.2.3 生成开头的注释信息

使用 .vimrc 文件,能够快速的生成开头的注释信息

[root@hchost scripts]# vi /etc/vimrc 
# 在文件末尾添加
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"

func SetTitle()
    if expand("%:e") == 'sh'
        call setline(1,"#!/bin/bash")
        call setline(2, "##############################################################")
        call setline(3, "# File Name: ".expand("%"))
        call setline(4, "# Version: V1.0")
        call setline(5, "# Author: HuangChao")
        call setline(6, "# Created Time : ".strftime("%F %T"))
        call setline(7, "# Description:")
        call setline(8, "##############################################################")
        call setline(9, "")
    endif
endfunc

# 效果
#!/bin/bash
##############################################################
# File Name: hc.sh
# Version: V1.0
# Author: HuangChao
# Created Time : 2020-09-10 11:20:08
# Description:
##############################################################

最好不用中文注释,因为在不同字符集的系统会出现乱码。(字符集为zh_CN.UTF-8,为中文)。

1.2.4 代码书写习惯


  • 成对的内容一次性写出来,防止遗漏,如[ ]、’ ‘、” “等
  • 两端要有空格,先输入[ ],退格,输入2个空格,再退格写。

  • 流程控制语句一次书写完,再添加内容。(if 条件 ; then 内容;fi)

  • 通过缩进让代码易读。

  • 脚本中的引号都是英文状态下的引号,其他字符也是英文状态。

1.3 shell脚本的执行

1.3.1 执行脚本的办法

sh/bash   scripts.sh 

chown +x   ./scripts.sh  && ./scripts.sh  

source scripts.sh

. (空格) scripts.sh

测试案例

[root@hchost scripts]# cat hc.sh 
#!/bin/bash
##############################################################
# File Name: hc.sh
# Version: V1.0
# Author: HuangChao
# Created Time : 2020-09-10 11:23:31
# Description:
##############################################################


pwd
echo "hello hc "
ll
echo "hello $1"


# 没有执行权限时
# 两个执行结果一样
[root@hchost scripts]# . hc.sh 
/server/scripts
hello hc 
total 4
-rw-r--r-- 1 root root 287 Sep 10 11:24 hc.sh
hello 
[root@hchost scripts]# source hc.sh 
/server/scripts
hello hc 
total 4
-rw-r--r-- 1 root root 287 Sep 10 11:24 hc.sh
hello 

sh/bash   hc.sh # 部分命令不能识别

# 授权后 部分命令不能识别
[root@hchost scripts]# chmod +x hc.sh 
[root@hchost scripts]# ./hc.sh 
/server/scripts
hello hc 
./hc.sh: line 13: ll: command not found
hello 
[root@hchost scripts]# sh hc.sh 
/server/scripts
hello hc 
hc.sh: line 13: ll: command not found
hello 
[root@hcho

1.4 Shell的变量

1.4.1 什么是变量


  • 环境变量

也可称为全局变量,可以在创建他们的Shell及其派生出来的任意子进程shell中使用,环境变量又可分为自定义环境变量和Bash内置的环境变量


  • 普通变量

也可称为局部变量,只能在创建他们的Shell函数或Shell脚本中使用。普通变量一般是由开发者用户开发脚本程序时创建的。

1.4.2 环境变量

使用 env/declare/set/export -p 4个命令查看系统中的环境变量

1.4.3 环境变量相关配置文件

四文件读取顺序(CentOS6和7都一样)

① /etc/profile

② ~/.bash_profile

③ ~/.bashrc

④ /etc/bashrc

image.png

1.4.4 环境变量的知识小结

  • 变量名通常要大写。
  • 变量可以在自身的Shell及子Shell中使用。
  • 常用export来定义环境变量。
  • 执行env默认可以显示所有的环境变量名称及对应的值。
  • 输出时用“$变量名”,取消时用“unset 变量名”。
  • 书写crond定时任务时要注意,脚本要用到的环境变量最好先在所执行的Shell脚本中重新定义。
  • 如果希望环境变量永久生效,则可以将其放在用户环境变量文件或全局环境变量文件里。
  • 一般的变量定义、赋值常用双引号;简单连续的字符串可以不加引号;希望原样输出时使用单引号。
  • 希望变量的内容是命令的解析结果时,要用反引号’’,或者用$()把命令括起来再赋值。

1.4.5 变量中引号的使用

只有在变量的值中有空格的时候,会使用引号。

单引号与双引号的区别在于,是否能够解析特殊符号。

[root@hchost scripts]# hc='this is $PWD'
[root@hchost scripts]# echo $hc
this is $PWD
[root@hchost scripts]# hc="this is $PWD"
[root@hchost scripts]# echo $hc
this is /server/scripts

1.5 特殊变量

1.5.1 位置变量

位置变量 作用说明
$0 获取当前执行的shell脚本的文件名,如果执行脚本带路径那么就包括脚本路径。
$n 获取当前执行的shell脚本的第n个参数值,n=1..9,当n为0时表示脚本的文件名,如果n大于9用大括号括起来{10},参数以空格隔开。
$# 获取当前执行的shell脚本后面接的参数的总个数
$* 获取当前shell的所有传参的参数,不加引号同shell - 图4加上双引号,例如: “$”,则表示将所有的参数视为单个字符串,相当于“112$3”。
$@ 获取当前shell的所有传参的参数,不加引号同shell - 图5@加上双引号,例如: “$@”,则表示将所有参数视为不同的独立字符串,相当于“$1” “$2” “$3” “……”,这是将参数传递给其他程序的最佳方式,因为他会保留所有内嵌在每个参数里的任何空白。

当“shell - 图6@”都加双引号时,两者有区别,都不加双引号时,两者无区别

1.5.2 $* 与 $@ 对比实践

[root@hchost scripts]#  set -- "I am" handsome boy..
[root@hchost scripts]# echo $0
-bash
[root@hchost scripts]# echo $1
I am
[root@hchost scripts]# echo $2
handsome
[root@hchost scripts]# echo $3
boy..
[root@hchost scripts]# echo $*
I am handsome boy..
[root@hchost scripts]# echo $@
I am handsome boy..
[root@hchost scripts]# for i in $*;do echo $i ;done
I
am
handsome
boy..
[root@hchost scripts]# for i in $@;do echo $i ;done
I
am
handsome
boy..
[root@hchost scripts]# for i in "$*";do echo $i ;done
I am handsome boy..
[root@hchost scripts]# for i in "$@";do echo $i ;done
I am
handsome
boy..

1.5.3 进程状态变量

位置变量 作用说明
$? 获取执行上一个指令的执行状态返回值(0为成功,非零为失败),这个变量最常用
$$ 获取当前执行的Shell脚本的进程号(PID),这个变量不常用,了解即可
$! 获取上一个在后台工作的进程的进程号(PID),这个变量不常用,了解即可
$_ 获取在此之前执行的命令或脚本的最后一个参数,这个变量不常用,了解即可

1.5.4 echo参数说明

参数 参数说明
-n 不要追加换行
-e 启用下列反斜杠转义的解释
-E 显式地抑制对于反斜杠转义的解释
`echo’ 对下列反斜杠字符进行转义:
\n 换行
\r 回车
\t 横向制表符
\b 退格
\v 纵向制表符
\c 抑制更多的输出

1.6 变量的子串

1.6.1 变量子串说明

表达式 说明
${parameter} 返回变量$parameter的内容
${#parameter} 返回变内容的长度(按字符),也适用于特殊变量
${parameter:offset} 在变量${parameter}中,从位置offset之后开始提取子串到结尾
${parameter:offset:length} 在变量${parameter}中,从位置offset之后开始提取长度为length的子串
${parameter#word} 从变量${parameter}开头开始删除最短匹配的word子串
${parameter##word} 从变量${parameter}开头开始删除最长匹配的word子串
${parameter%word} 从变量${parameter}结尾开始删除最短匹配的word子串
${parameter%%word} 从变量${parameter}结尾开始删除最长匹配的word子串
${parameter/pattem/string} 使用string代替第一个匹配的pattern
${parameter//pattem/string} 使用string代替所有匹配的pattern
# 截取字符串
[root@hchost scripts]# pwd=/server/scripts
[root@hchost scripts]# a=${pwd:7}
[root@hchost scripts]# echo $a
/scripts
[root@hchost scripts]# b=${pwd:0:7}
[root@hchost scripts]# echo $b
/server

[root@hchost scripts]# word=abcABC123ABCabc
[root@hchost scripts]# echo ${word#abc}
ABC123ABCabc
[root@hchost scripts]# echo ${word##abc}
ABC123ABCabc
[root@hchost scripts]# echo ${word%abc}
abcABC123ABC
[root@hchost scripts]# echo ${word%%abc}
abcABC123ABC
[root@hchost scripts]# echo ${word#a*c}
ABC123ABCabc
[root@hchost scripts]# echo ${word##a*c}

[root@hchost scripts]# echo ${word%a*c}
abcABC123ABC
[root@hchost scripts]# echo ${word%%a*c}

[root@hchost scripts]# echo ${word#a*C}
123ABCabc
[root@hchost scripts]# echo ${word#a*C}
123ABCabc
[root@hchost scripts]# echo ${word##a*C}
abc
[root@hchost scripts]# echo ${word%a*c}
abcABC123ABC
[root@hchost scripts]# echo ${word%A*c}
abcABC123
[root@hchost scripts]# echo ${word%%A*c}
abc

# 替换变量内容
[root@clsn scripts]# echo $clsn
abcABC123ABCabc
[root@clsn scripts]# echo ${clsn/abc/clsn}
clsnABC123ABCabc
[root@clsn scripts]# echo ${clsn//abc/clsn}
clsnABC123ABCclsn

1.6.2 匹配删除的小结

表示从幵头删除匹配最短。

表示从开头删除匹配最长。

%表示从结尾删除匹配最短。

%%表示从结尾删除匹配最长。

ac表示匹配的突符串,表示匹配所有,a*c匹配开头为a、中间为任意多个字符、结尾为c的字符串。

aC表示匹配的字符串,表示匹配所有,a*C匹配开头为a、中间为任意多个字符、结尾为C的字符串。

一个“/”表示替换匹配的第-个字符串。

两个“/”表示替换匹配的所有字符串。

1.6.3 Shell的特殊扩展变量说明

表达式 说明
${parameter:-word} 如果parameter的变量值为空或未赋值,则会返回word字符串并替代变量的值用途.如果变量未定义,则返回备用的值,防止变量为空值或因未定义而导致异常
${parameter:=word} 如果parameter的变量值为空或未赋值,则设置这个变量值为word,并返回其值。位置变量和特殊变量不适用用途:基本同上一个${parameter>word},但该变量又额外给parameter变量赋值了
${parameter:?word} 如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。用途:用于捕捉由于变量未定义而导致的错误,并退出程序
${parameter:+word} 如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值
[root@hchost ~]# echo ${HC:-hc}
hc
[root@hchost ~]# echo $HC


[root@hchost ~]# echo ${HC:=hc}
hc
[root@hchost ~]# echo $HC
hc

1.7 变量的数值计算

1.7.1 仅支持整数的运算

echo $((数学运算表达式))

# 形式一
[root@clsn scripts]# echo $((1 + 1))
2
[root@clsn scripts]# echo $((2*7-3/6+5))
19
# 形式二
[root@clsn scripts]# ((clsn=2*8))
[root@clsn scripts]# echo $clsn
16
# 形式三
[root@clsn scripts]# znix=$((2*7-3/6+5))
[root@clsn scripts]# echo $znix
19

i++ 自增1 i— 自减1 ++i —i

记忆方法:++,—

变量a在前,表达式的值为a,然后a自增或自减 (先输出a,再计算)

变量a在符号后,表达式值自增或自减,然后a值自增或自减 (先计算,再输出a)

let

[root@clsn scripts]# i=1
[root@clsn scripts]# i=i+1
[root@clsn scripts]# echo $i
i+1

[root@clsn scripts]# i=1
[root@clsn scripts]# let i=i+1
[root@clsn scripts]# echo $i
2

expr

1.整数计算

2.判断扩展名

3.判断输入是否为整数,非整数返回值为2

4.计算变量的长度

[root@clsn scripts]# expr 1+1
1+1
[root@clsn scripts]# expr 1 + 1
2
[root@clsn scripts]# expr 1 * 1
expr: 语法错误
[root@clsn scripts]# expr 1 \* 1
1

# 非整数返回值为2
[root@clsn scripts]# expr 1 + 1 
2
[root@clsn scripts]# echo $?
0
[root@clsn scripts]# expr -1 + 1 
0
[root@clsn scripts]# echo $?
1
[root@clsn scripts]# expr a + 1 
expr: 非整数参数
[root@clsn scripts]# echo $?
2

# $[]运算符
[root@clsn scripts]# echo $[1+2]
3
[root@clsn scripts]# echo $[1-2]
-1
[root@clsn scripts]# echo $[1*2]
2
[root@clsn scripts]# echo $[1/2]
0

# typeset命令进行运算
[root@clsn scripts]# typeset -i A=2017 B=2018
[root@clsn scripts]# A=A+B
[root@clsn scripts]# echo $A
4035

1.7.2 可以进行小数运算的命令

bc 命令 yum -y install bc

# 交互模式测试bc命令
[root@hchost scripts]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
1+1.1
2.1
^C
(interrupt) Exiting bc.
[root@clsn scripts]# echo 1+1.1|bc
2.1

# 免交互模式测试bc命令
[root@hchost scripts]# echo 'scale=2;1/3'|bc
.33

awk 命令

[root@clsn ~]# echo "7.7 3.8"|awk '{print ($1-$2)}'
3.9
[root@clsn ~]# echo "358 113"|awk '{print ($1-3)/$2}'
3.14159
[root@clsn ~]# echo "3 9"|awk '{print ($1+3)*$2}'
54
[root@backup scripts]# awk BEGIN'{print 1.2+3.3}'
4.5

1.7.3 运算相关练习题

实现一个加减乘除等功能的计算器

# vim  sh脚本
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b

echo $a + $b =$(($a+$b))
echo $a - $b =$(($a-$b))
echo $a \* $b =$(($a*$b))
echo $a / $b =$(($a/$b))

# 精简方法
echo $(($1))

[root@clsn scripts]# sh jishuanqi2.sh  1+1
2
[root@clsn scripts]# sh jishuanqi2.sh  1*9
9

打印结果1+2+3+4+5+6+7+8+9+10=55

# vim  sh脚本
Num=`seq -s + 1 10`
echo  $Num=$(($Num))

2、进阶篇

2.1 条件表达式

2.1.1 文件判断

常用文件测试操作符 说明
-d 文件,d的全拼为directory 文件存在且为目录则为真,即测试表达式成立
-f 文件,f的全拼为file 文件存在且为普通文件则为真,即测试表达式成立
-e 文件,e的全拼为exist 文件存在则为真,即测试表达式成立。注意区别于“-f”,-e不辨别是目录还是文件
-r 文件,r的全拼为read 文件存在且可读则为真,即测试表达式成立
-s 文件,s的全拼为size 文件存在且文件大小不为0则为真,即测试表达式成立
-w 文件,w的全拼为write 文件存在且可写则为真,即测试表达式成立
-x 文件,x的全拼为executable 文件存在且可执行则为真,即测试表达式成立
-L 文件,L的全拼为link 文件存在且为链接文件则为真,即测试表达式成立
fl -nt f2,nt 的全拼为 newer than 文件fl比文件f2新则为真,即测试表达式成立。根据文件的修改时间来计算
fl -ot f2,*ot 的全拼为 older than** 文件fl比文件f2旧则为真,即测试表达式成立。根据文件的修改时间来计算
# 判断文件是否存在
[root@clsn scripts]# [ -f /etc/hosts ]
[root@clsn scripts]# echo $?
0
[root@clsn scripts]# [ -f /etc/hosts1 ]
[root@clsn scripts]# echo $?
1

# 判断文件是否存在,返回方式
[root@clsn scripts]# [ -f /etc/hosts ] && echo "文件存在" || echo "文件不存在" 
文件存在
[root@clsn scripts]# [ -f /etc/hosts1 ] && echo "文件存在" || echo "文件不存在" 
文件不存在

# 使用变量的方法进行判断
dir=/etc1/;[ -d $dir ] && tar zcf etc.tar.gz $dir || echo "$dir目录不存在"

2.1.2 字符串判断

常用字符串测试操作符 说明
-n “字符串” 若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为no zero
-z “字符串” 若字符串的长度为0,则为真,即测试表达式成立,z可以理解为zero的缩写
1”== “ 2” 若字符串1等于字符串2,则为真,即测试表达式成立,可使用”==”代替”=”
1” = “ 2” 若字符串1不等于字符串2,则为真,即测试表达式成立,但不能用”!==”代替”!=”

1.对于字符串的测试,一定要将字符串加双引号之后再进行比较。

2.空格非空

# -z 判断字符串长度
[root@clsn scripts]# x=  ; [ -z "$x" ] && echo "输入为空" || echo '输入有内容'
输入为空
[root@clsn scripts]# x=12 ; [ -z "$x" ] && echo "输入为空" || echo '输入有内容'
输入有内容

# -n 判断字符串长度
[root@clsn scripts]# x=12 ; [ -n "$x" ] && echo "输入有内容" || echo '输入为空'
输入有内容
[root@clsn scripts]# x= ; [ -n "$x" ] && echo "输入有内容" || echo '输入为空'
输入为空

# "串 1" == " 串 2 "       使用定义变量的方式进行判断
cmd=$1
[ "$cmd" == "start" ] && echo start
# 测试
[root@clsn scripts]# cmd=start;[ "$cmd" == "start" ] && echo start
start

2.1.3 整数判断

[]以及test中使用的比较符号 (())和[[]]中使用的比较符号 说明
-eq ==或= 相等,全拼为equal
-ne != 不相等,全拼为not equal
-gt > 大于,全拼为greater than
-ge >= 大于等于,全拼为greater equal
-lt < 小于,全拼为less than
-le <= 小于等于,全拼为less equal
# 判断两数是否相等
[root@clsn scripts]# [ 1 -eq 1 ]
[root@clsn scripts]# echo $?
0
[root@clsn scripts]# [ 1 -eq 2 ]
[root@clsn scripts]# echo $?
1
# 大于等于
[root@clsn ~]# [ 11 -ge 1 ] && echo "成立" || echo "不成立"
成立

# 小于
[root@clsn ~]# [ 11 -lt 1 ] && echo "成立" || echo "不成立"
不成立

# 大于
[root@clsn ~]# [ 11 -gt 1 ] && echo "成立" || echo "不成立"
成立

# 不等于
[root@clsn ~]# [ 11 -ne 1 ] && echo "成立" || echo "不成立"
成立

2.1.4 逻辑符号

[]和test中使用的操作符 说明 [[]]和中使用的操作符 说明
-a [ 条件A -a 条件B ]A与B都要成立,整个表达式才成立 && and,与,两端都为真,则结果为真
-o [ 条件A -o 条件B]A与B都不成立,整个表达式才不成立 || or,或,两端有一个为真,则结果为真
! not,非,两端相反,则结果为真

2.1.5 比较2个整数大小

# 使用传参方法
NUM1=$1
NUM2=$2

expr 1 + $NUM1  &>/dev/null
[ $? -eq 2 ] && echo "$NUM1 不是整数 " &&  exit 2
expr 1 + $NUM2  &>/dev/null
[ $? -eq 2 ] && echo "$NUM2 不是整数 " &&  exit 2

[ "$NUM1" -eq "$NUM2" ] && echo $NUM1 = $NUM2  && exit
[ "$NUM1" -gt "$NUM2" ] && echo $NUM1 \> $NUM2 && exit
[ "$NUM1" -lt "$NUM2" ] && echo $NUM1 \< $NUM2

# 使用read读入
read -p "请输入第一个整数:" NUM1
read -p "请输入第二个整数:" NUM2

expr 1 + $NUM1  &>/dev/null
[ $? -eq 2 ] && echo "$NUM1 不是整数 " &&  exit 2
expr 1 + $NUM2  &>/dev/null 
[ $? -eq 2 ] && echo "$NUM2 不是整数 " &&  exit 2

[ "$NUM1" -eq "$NUM2" ] && echo "$NUM1 = $NUM2"  && exit
[ "$NUM1" -gt "$NUM2" ] && echo "$NUM1 > $NUM2" && exit
[ "$NUM1" -lt "$NUM2" ] && echo $NUM1 \< $NUM2

2.2 if条件语句

2.2.1 语法 多分支语句

if [ -f /etc/hosts ]  

then

   echo " hosts文件存在"

elif [ -f /etc/host ]

then

   echo " host文件存在"

fi

2.2.2 数字比较大小

输入2个数字,比较大小

read -p "请输入第一个整数:" NUM1
  expr 1 + $NUM1  &>/dev/null
   NUM1_FH=$?   
      if [ $NUM1_FH -eq 2 ] 
        then
          echo "$NUM1 不是整数 "
          exit 2
      else  
        read -p "请输入第二个整数:" NUM2
        expr 1 + $NUM2  &>/dev/null 
        NUM2_FH=$?
        if [ $NUM2_FH -eq 2 ] 
         then 
           echo "$NUM2 不是整数 " 
           exit 2
        fi
      fi

# 判断输入数值大小 
    if [ $NUM1 -eq $NUM2 ]
      then
        echo "$NUM1 = $NUM2"
        exit
    elif [ $NUM1 -gt $NUM2 ]
      then
        echo "$NUM1 > $NUM2" 
        exit
    elif [ $NUM1 -lt $NUM2 ]
      then
        echo "$NUM1 < $NUM2"
    fi

2.2.3 内存报警

系统内存低于100M邮件报警,加入计划任务,3分钟检查一次。

# 第一步 先配置邮件服务,保证能够发生邮件
echo 'set from=mail@znix.top smtp=smtp.znix.top smtp-auth-user=mail@znix.top  smtp-auth-password=****** smtp-auth=login' >> /etc/mail.rc

# 发送测试邮件发送
echo "`date +%F_%T`" |mail -s "这是测试邮件" admin@znix.top

image.png

# 第二步编写检查脚本
Mem=`free -m |awk 'NR==2{print $NF}'`
host=`hostname`
Ip=`hostname -I`
Date=`date +%F_%T`
mail_file=/tmp/mail.s
dest_user=admin@znix.top

if [ $Mem -lt 100 ]
then
   echo "发生时间: $Date" >$mail_file
   echo "发生主机: $host  主机IP地址: $Ip " >> $mail_file
   echo "内存余量: $Mem M" >> $mail_file
   mail -s "【警告】内存不足了呀!"  $dest_user < $mail_file
fi

image.png

# 脚本测试成功,写入定时任务
[root@clsn panduan1]# crontab -l 
# 检查内存剩余大小
*/3 * * * * /bin/sh /server/scripts/panduan/mem_info.sh  &>/dev/null

2.2.4 启动nginx脚本

. /etc/init.d/functions  

StartCheck=`netstat -lntup  |grep -c 80`
StartCMD='/application/nginx/sbin/nginx '
StopCMD='/application/nginx/sbin/nginx -s stop '
StatusCheck=`netstat -lntp|grep -c nginx`
ReloadCMD='/application/nginx/sbin/nginx -s reload'
CheckCMD='/application/nginx/sbin/nginx  -t'
UsaGe="Usage: $0 {start|stop|status|reload|check}"


COMMAND=$1

if [  $# -ne 1 ]
  then
    echo $UsaGe  && exit 2
fi 

if [ $COMMAND = start ]
 then
    if [ $StartCheck  == 1  ]
      then
      action  "启动Nginx失败,端口被占用"  /bin/false
    else
      $StartCMD
      action  "启动Nginx"  /bin/true
    fi
elif [ $COMMAND = stop ]
  then
      $StopCMD  &>/dev/null
      if [ $? -eq 0 ]
        then
          action   "停止Nginx"   /bin/true 
      else
          action   "停止Nginx"   /bin/false
      fi
elif [ $COMMAND = status ]
  then
    if [ $StatusCheck -eq 1 ]
      then
        echo "nginx 正在运行..."
    else
        echo "Nginx 未运行."
    fi
elif [ $COMMAND = reload ]
  then
    $ReloadCMD
    action  "reload" /bin/true
elif [ $COMMAND = check ]
  then 
    $CheckCMD
else 
 echo $UsaGe
 exit 2
fi

shell - 图9

2.3 case条件结构语句

2.3.1 case语法结构

case "字符串变量" in 
  值1)
     指令1
     ;;
  值2)
     指令2
     ;;
  值*)
     指令
esac

# case书写方式
case $name in
  值1) 
      指令1
      ;;
  值2) 
      指令2
      ;;
   *) 
      指令
esac

# if书写方式
if [ $name == "值1" ]
  then 
    指令1
elif [ $name == "值2" ]
  then 
    指令2
else
    指令    
fi

2.3.2 case语句小结

  • case语句就相当于多分支的if语句。case语句的优势是更规范、易读。
  • case语句适合变量的值少,且为固定的数字或字符串集合。(1,2,3)或(start,stop,restart)。
  • 系统服务启动脚本传参的判断多用case语句,多参考rpcbind/nfs/crond脚本;菜单脚本也可以使用case

2.3.3 编写菜单脚本

cat<<EOF
1. install rsync
2. install nfs
3. install mysql
4. install all
EOF

read -p '请输入你的选择:' chs

case $chs in
  1)
   echo install rsync success
   ;;
  2)
   echo install nfs success
   ;;
  3)
   echo install mysql success
   ;;
  4)
   echo install rsync/nfs/mysql success
   ;;
  *)
   echo "你输入有误"
esac

2.3.4 文字带颜色

cat <<EOF
=======================
1.apple
2.pear
3.banana
4.exit
=======================
EOF
read -t 10 -p  "你要看什么水果:" Menu_Num

case "$Menu_Num" in 
  1)
   echo -e "\033[31m apple \033[0m"
   ;;
  2)
   echo -e "\033[32m pear \033[0m"
   ;;
  3)
   echo -e "\033[33m banana \033[0m" 
   ;;
  4)
   exit
esac

2.3.5编写rsync管理脚本


. /etc/init.d/functions
. /etc/init.d/run 

Rsync_Port=`netstat -lntup |grep -c  0.0.0.0:873`
Rsync_COM1='rsync --daemon'
Rsync_COM2='pkill rsync'

Rsync_Start() {
   Rsync_Port1=`netstat -lntup |grep -c  0.0.0.0:873`
   if [ $Rsync_Port1 -ne 0 ]
     then
      action  "服务已启动" /bin/false
      exit 3
   else
      $Rsync_COM1
      #action  "Rsync 启动" /bin/true
      QiDong
   fi
}

Rsync_Stop() {
   Rsync_Port2=`netstat -lntup |grep -c  0.0.0.0:873`
   if [ $Rsync_Port2 -eq 0 ]
     then
       action  "服务未启动" /bin/false
       exit 3
   else
       $Rsync_COM2 
       #action  "Rsync 停止" /bin/true
       TingZhi
   fi
}

Rsync_Status() {
   if [ $Rsync_Port -eq 1 ]
     then
       echo  "Rsync 服务运行中..." 
   else
       echo  "Rsync 服务未运行" 
   fi
}

Rsync_Restart() {
   Rsync_Stop 
   Rsync_Start
}
COMMAND=$1

case "$COMMAND" in
  start)
    Rsync_Start
    ;;
  stop)
    Rsync_Stop
    ;;
  status)
    Rsync_Status
    ;;
  restart|reload|force-reload)
    Rsync_Restart
    ;;
  *)
    echo $"Usage: $0 {start|stop|status|restart|reload|force-reload}"
    exit 2
esac

2.3.6 菜单自动化软件部署

打印选择菜单,按照选择一键安装不同的Web服务

示例菜单:

[root@oldboy scripts]# sh menu.sh

1.[install lamp]

2.[install lnmp]

3.[exit]

pls input the num you want:

echo 'echo lampis installed' >> /server/scripts/lamp.sh
chmod +x /server/scripts/lamp.sh

echo 'echo lnmpis installed' >> /server/scripts/lnmp.sh
chmod +x /server/scripts/lnmp.sh

cat <<EOF
1.[install lamp]
2.[install lnmp]
3.[exit]
EOF

read -p "please input the num you want:" Num

case "$Num" in 
  1)
    if [ -f /server/scripts/lamp.sh ]
      then
         echo -e "\033[36mstart installing lamp\033[0m"
         /server/scripts/lamp.sh
    else
      echo -e "\033[5;41;37m /server/scripts/lamp.sh 文件不存在 \033[0m"
    fi 
    ;;
  2) 
    if [ -f /server/scripts/lnmp.sh ]
      then
         echo -e "\033[36mstart installing lnmp\033[0m"
        /server/scripts/lnmp.sh
    else
      echo -e "\033[5;41;37m /server/scripts/lnmp.sh 文件不存在 \033[0m"
    fi
    ;;
  3)
    exit
    ;;
  *)
    echo -e "\033[31mInput error \033[0m"
    exit 2 
esac

2.3.7 if 与 case 对比

  • if 语句类似黑名单,需要把这种错误场景封堵
  • case 语句类似白名单,只要把正确结果列完整即可

2.4 其他补充说明

2.4.1 linux中产生随机数的方法

[root@clsn scripts]# echo $RANDOM 
29291
[root@clsn scripts]# echo $RANDOM 
5560
[root@clsn scripts]# echo $RANDOM 
2904

2.4.2 echo 命令输出带颜色字符

彩色字体

echo -e "\033[30m 黑色字 clsn \033[0m"
echo -e "\033[31m 红色字 clsn \033[0m"
echo -e "\033[32m 绿色字 clsn \033[0m"
echo -e "\033[33m 黄色字 clsn \033[0m"
echo -e "\033[34m 蓝色字 clsn \033[0m"
echo -e "\033[35m 紫色字 clsn \033[0m"
echo -e "\033[36m 天蓝字 clsn \033[0m"
echo -e "\033[37m 白色字 clsn \033[0m"

彩色底纹

echo -e "\033[40;37m 黑底白字 clsn \033[0m"
echo -e "\033[41;37m 红底白字 clsn \033[0m"
echo -e "\033[42;37m 绿底白字 clsn \033[0m"
echo -e "\033[43;37m 黄底白字 clsn \033[0m"
echo -e "\033[44;37m 蓝底白字 clsn \033[0m"
echo -e "\033[45;37m 紫底白字 clsn \033[0m"
echo -e "\033[46;37m 天蓝白字 clsn \033[0m

特效字体

\033[0m 关闭所有属性
\033[1m 设置高亮度
\033[4m 下划线
\033[5m 闪烁
\033[7m 反显
\033[8m 消隐
\033[30m — \033[37m 设置前景色
\033[40m — \033[47m 设置背景色
\033[nA 光标上移 n 行
\033[nB 光标下移 n 行
\033[nC 光标右移 n 行
\033[nD 光标左移 n 行
\033[y;xH 设置光标位置
\033[2J 清屏
\033[K 清除从光标到行尾的内容
\033[s 保存光标位置
\033[u 恢复光标位置
\033[?25l 隐藏光标
\033[?25h 显示光标

2.5 for循环语句

2.5.1 shell中的for循环

# 列表for循环
#!/bin/bash
for i in 取值列表
do
    循环主体/命令
done

# 样式一:
for i in 1 2 3 
  do 
    echo $i
done
# 样式二:
for i in 1 2 3;do  echo $i;done

2.5.2 练习题

1、# 批量生成随机字符文件名
使用for循环在/root/hc目录下批量创建10个html文件,其中每个文件需要包含10个随机小写字母加固定字符串hc

[ -d /root/hc ]||mkdir -p /root/hc
rpm -qa|grep pwgen &>/dev/null
if [ $? -eq 1 ]
    then
        yum -y install pwgen &>/dev/null
fi

cd /root/hc
for i in {1..10}
    do
        # -1 不要在列中打印生成的密码,即一行一个密码
        # A 密码中不包含大写字母
        # 0 密码中不包含数字
        File_Name=`pwgen -1A0 10`
        touch ${File_Name}_hc.html
done


2、# 1中结果文件名中的hc字符串全部改成hello(最好用for循环实现),并且将扩展名html全部改成大写
cd /root/hc
File_name=`ls |sed -r 's#(.*)_hc.html#\1#g'`

for i in $File_name
  do
   if [ -f ${i}_hc.html ] 
     then
     mv ${i}_hc.html ${i}_hello.HTML
   else
     echo "文件修改完成."
     exit
   fi
done

3、# 批量创建10个系统帐号clsn01-clsn10并设置密码(密码为随机数,要求字符和数字等混合)
Passwd_File=/tmp/`uuidgen`.txt
>$Passwd_File
chmod 400 $Passwd_File

for i in clsn{01..10}
  do
   userdel -r "$i" &>/dev/null
   id $i &>/dev/null
   if [ $? -ne 0 ]
     then
       useradd $i
       PassWd=`uuidgen`
       echo $PassWd |passwd --stdin $i &>/dev/null
       echo "用户名:$i  密码:$PassWd" >>$Passwd_File
       echo -e "\033[32m $i 用户创建成功!\033[0m"
   else 
     echo "$i 用户已存在"
   fi
   if [ "$i" == "clsn10" ] 
     then
       echo "用户密码请查看文件 $Passwd_File"
   fi
done

4、# 写一个Shell脚本,判断10.0.0.0/24网络里,当前在线的IP有哪些
Ip_File=/tmp/scan_ip.txt
>$Ip_File

for i in 10.0.0.{1..254}
 do
  ping -c 1 -W 1 $i &>/dev/null && \
  if [ $? -eq 0 ] ;then 
    echo "存活主机: $i" &>>$Ip_File
  fi &   
done
echo "使用 cat $Ip_File 查看扫描结果"

5、# 破解RANDOM随机数案例
md5File=/tmp/Randow_Md5.txt
Md5_Word="21029299 00205d1c a3da1677 1f6d12dd 890684b"

if [ ! -f $md5File ]
  then
    >$md5File
    for i in {0..32767}
       do
        echo `echo $i |md5sum` $i  >> $md5File
    done
else
    for num in $Md5_Word
      do
       grep $num $md5File
    done
fi

6、#

2.6 while循环语句

while 条件
  do
    命令
done
1、# 计算1-100的和
echo `seq -s + 1 100`|bc

i=1
while [ "$i" -le 100 ]
  do
  ((b=b+i))
  ((i++))
done
echo $b

2、# 实现类似手机通讯计费功能
sum=1000
i=15


while [ $sum -ge 15 ]
  do
cat<<EOF
=================
1.发短信
2.查余额
3.账户充值
4.退出
=================
EOF
    read -p "你要做什么呢?" Some
    case "$Some" in
      1)
        sum=$((sum-i))
        read -p  "请输入发送短信的内容:"
        read -p  "请输入收信人:"
        sleep 0.3
        echo "发送成功."
        echo "您当前余额为$sum"
        ;;
      2)
        echo "您当前余额为$sum"
        ;;
      3)
        read -p "请输入你要充值的金额:" ChongZhi
        sum=$((sum+ChongZhi))
        echo "充值成功,当前余额为$sum"
        ;;
      4)
        exit
        ;;
      *)
        echo "输入有误!"
        exit 2
    esac
done

echo "余额不足,请及时充值!"

2.7 select

select  I  in (表达式) 
  do
    语句
done

# 案例:
[root@hchost script]# vim select.sh
select i in nginx mysql php
  do
    echo "$i install"
done

[root@hchost script]# . select.sh 
1) nginx
2) mysql
3) php
#? 1
nginx install
#? 2
mysql install
#? 3
php install

3、完结篇

3.1 break continue exit return

命令 说明
break n 如果省略n,则表示跳出整个循环,n表示跳出循环的层数
continue n 如果省略n,则表示跳过本次循环,忽略本次循环的剩余代码,进人循环的下一次循环。 n表示退到第n层继续循环
exit n 退出当前Shell程序,n为上一次程序执行的状态返回值。n也可以省略,在下一个Shell里可通过”$?”接收exit n的n值
return n 用于在函数里作为函数的返回值,以判断函数执行是否正确。在下一个Shell里可通过”$?”接收exit n的n值
# 简单来说即:
break    跳出循环
continue 跳出本次循环
exit     退出脚本
return   与 exit 相同,在函数中使用

3.2 shell中的数组

3.2.1 eval

[root@clsn ~]# help eval
eval: eval [参数 ...]
    将参数作为 shell 命令执行。

    将 ARGs 合成一个字符串,用结果作为 shell 的输入,
    并且执行得到的命令。

    退出状态:
    以命令的状态退出,或者在命令为空的情况下返回成功。

[root@hchost script]# hc=6
[root@hchost script]# echo {1..$hc}
{1..6}
[root@hchost script]# eval echo {1..$hc}
1 2 3 4 5 6

3.2.2 什么是Shell数组

Shell的数组就是一个元素集合,它把有限个元素(变量或字符内容)用一个名字来 命名,然后用编号对它们进行区分。 组成数组的各个元素(变量)称为数组的元素

${array[@]}     所有元素
${#array[@]}      数组长度
${array[i]}      单个元素,i是下标

3.2.3 数组中的增删改查

# 定义数组
[root@clsn scripts]# stu=(001 002 003)
# 打印数组
[root@clsn scripts]# echo ${stu[@]}
001 002 003
# 显示数组长度
[root@clsn scripts]# echo ${#stu}
3

查: 遍历数组的内容

# 方法一
[root@clsn scripts]# for i in ${stu[@]};do echo $i ;done 
001
002
003
# 方法二
[root@clsn scripts]# array=(1 2 3 4 5)
[root@clsn scripts]# for i in `eval echo {1..${#array[@]}}`;do echo ${array[i-1]};done
1
2
3
4
5

:数组添加

[root@clsn scripts]# stu[3]=004
[root@clsn scripts]# echo ${stu[@]}
001 002 003 004

数组修改

[root@clsn scripts]# stu[2]=000
[root@clsn scripts]# echo ${stu[@]}
001 002 000 004

数组删除

[root@clsn scripts]# unset stu[2]
[root@clsn scripts]# echo ${#stu[@]}
3
[root@clsn scripts]# echo ${stu[@]}
001 002 004

3.2.4 将命令的结果赋值给数组

[root@hchost script]# ls
make_file.sh  select.sh  sshd.status.sh
[root@hchost script]# a=(`ls`)
[root@hchost script]# b=($(ls))

[root@hchost script]# echo ${b[@]}
make_file.sh select.sh sshd.status.sh
[root@hchost script]# echo ${a[@]}
make_file.sh select.sh sshd.status.sh

3.2.5 检查多个网站地址是否正常

url=(
http://www.cnblogs.com/clsn/
http://blog.znix.top
http://blog.nmtui.com
http://10.0.0.7
)
while true 
  do
    for i in ${url[@]} 
      do 
      #curl $i &>/dev/null
      # -I, --head  仅显示 响应文档头
      # -w/--write-out [format]        什么输出完成后 http_code http状态码
      a=$(curl -I -w "%{http_code}\n" -o /dev/null -s $i)
      if [ $a -ne 200 ]
        then
          echo "$url 异常"
      fi
    done
    sleep 10
done

3.3 Shell 函数

函数的作用就是把程序里多次调用相同代码的部分定义成一份,然后起个名字,所有的调用都 只用这名字就可以了,修改代码时,只需要改变函数体内的代码即可

🐟 把相同的程序段定义成函数,可以减少代码量。

🐟 增加程序的可读、易读性

🐟 实现程序功能的模块化

3.3.1 函数格式

function name (){
    command1
    command2
    ........
}
name args1 args2

# 函数的引用
[root@hchost script]# vim fun1.sh 
function clsn(){
    echo "http://blog.znix.top"
}

znix(){
    echo "http://www.znix.top " 
}

[root@hchost script]# . fun1.sh 
[root@hchost script]# clsn
http://blog.znix.top
[root@hchost script]# znix
http://www.znix.top