ShellScript

ShellScript概念

目录

程序

程序:算法+数据结构,数据是程序的核心

  • 算法:处理数据的方式
  • 数据结构:数据在计算机中的类型和组织方式

分类

按程序编程风格:

  • 过程式:以指令为中心,数据服务于指令
  • 对象式:以数据为中心,指令服务于数据

按程序运行方式:

  • 编译运行:高级语言-->编译器-->机器代码-->执行

    源代码需要编译器转换为程序文件,运行程序文件时不需要编译器的参与,因此程序执行效率高

    比如:C,C++

  • 解释运行:高级语言-->执行-->解释器-->机器代码

    源代码不需要事先编译,运行时启动解释器而后由解释器边解释边运行,因此效率比较低

    比如:shell,python,php,JavaScript,perl


按编程实现是调用库还是调用外部的程序文件:

  • 非完整编程语言:利用系统上的命令及编程组件进行编程,shell脚本
  • 完整的编程语言:利用库和编程组件进行编程,非shell脚本

按编程模型:

  • 面向过程编程语言

    以指令为中心来组织代码,以过程或函数为基础,数据服务于代码,围绕指令来组织数据;这种语言对底层硬件,内存等操作比较方便,但是写代码和调试维护等会很麻烦。
    他们按照顺序执行,选择执行,循环执行
    比如:C bash C++ python

  • 面向对象的编程语言

    以数据为中心来组织代码,以对象作为基本程序结构单位的程序设计语言,指令服务于数据,围绕数据来组织指令;指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。语言中提供了类、继承等成分。
    对象:特定的数据类型
    类class:实例化成为对象
    比如:Java C++ python


综上所述可知:

shell脚本编程属于解释运行过程式编程语言且依赖于外部程序文件来运行


编程基本结构

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

bash脚本

一种为shell编写的脚本程序;

是Linux命令的堆砌;

但由于Linux中很多命令不具有幂等性,需要设定程序逻辑来判断运行条件是否满足,以避免其运行中发生错误

幂等性

即一个操作,不论执行多少次,产生的效果和返回的结果都是一样的!


ShellScript作用

减少重复性的工作

  • 自动化常用命令
  • 执行系统管理和故障排除
  • 创建简单的应用程序
  • 处理文本或文件
  1. 自动化安装操作系统
    1. kickstart 底层shell脚本
    2. cobbler 底层shell脚本
  2. 初始化操作系统
    SSH优化 关闭SElinux 防火墙放行需要的端口(80 443 22修改 10050) YUM源 时间同步 系统最大描述符 内核参数优化 字符集优化 禁止开机自动启动 修改主机名称 (修改公司网卡名称)...
    手动操作要注意 命令行安全 bash的命令历史
    写入shell脚本(常用)
  3. 安装服务 Nginx PHP MySQL Rsync等等... 针对不同的版本写入shell脚本自动安装
  4. 配置服务
  5. 启动服务 所有的服务底层的启动方式都是使用的shell脚本
    公司自己研发的程序
    nohup python3.5 test.py --redis --port --mysql --port -a xxxx &
    复制一下 写入脚本 sh start_test_py.sh
    如何停止py程序
    ps axu|grep test.py |grep -v grep|awk '{print $2}'|xargs kill -9
    复制一下 写入脚本 sh stop_test_py.sh
    把py的进程的端口和PID取出来 来判断是否运行
  6. 日志统计 查看程序运行的情况 统计我们需要的数据
    日志切割 定时任务+脚本
    统计数据 定时任务+脚本 ---> 通过邮件发送给管理员
    ELK 日志统计界面 py开发日志界面 py界面----> 数据库 <----数据 日志展示
  7. 监控 监控服务 服务端口是否存在 服务是否存在 服务器的硬件资源使用情况 状态 日志 网络
    Zabbix 通过脚本统计---> 测试---> 添加到zabbix服务 (cacti监控流量 Nagios宽带运营商 IT公司)

ShellScript规范

脚本文件创建约定

  1. 脚本存放在固定的目录/server/scripts统一管理
  2. 脚本使用.sh结尾,让我们能识别是shell脚本
  3. 脚本命名,见名知其意
  4. 脚本内的注释最好不用中文(可以用)
  5. 脚本内的成对的符号一次性写完再写内容

脚本代码开头约定

  1. 默认解析器: #!/usr/bin/env bash 会自己判断使用的shell是什么,并加载相应的环境变量
  2. 程序名,避免更改文件名为无法找到正确的文件
  3. 版本号
  4. 修改时间
  5. 作者相关信息
  6. 该程序的作用,及注意事项
  7. 最后是各版本的更新简要说明
#!/usr/bin/env bash
# ------------------------------------------
# Filename: hello.sh
# Revision: 1.0
# Date: 2020/10/21
# Author: wu
# Email: wu@gmail.com
# Description: This is the first script
# Copyright: 2020 wu
# License: GPL
# ------------------------------------------
echo “hello world”

注释

# 主函数 []<-()                   <-------函数注释这样写
function main(){
  local var="Hello World!!!"
  echo ${var}
}
# info级别的日志 []<-(msg:String)  <-------带入参的函数注释
log_info(){
  echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2
}
# error级别的日志 []<-(msg:String) <-------带入参的函数注释
log_error(){
  # todo [error]用红色显示         <------函数内注释
  local msg=$1 # 将要输出的日志内容 <------变量的注释紧跟在变量的后面
  if [[ x"${msg}" != x"" ]];then
    # 注释                        <-------函数内注释 `#` 与缩进格式对整齐
    echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]:[error] $*" >&2
  fi
}

缩进

  1. 使用两个空格进行缩进,不使用tab缩进
  2. 不在一行的时候使用 \ 进行换行,使用 \ 换行的原则是整齐美观
#!/usr/bin/env bash
# 脚本使用帮助文档 []<-()
manual(){
  cat "$0"|grep -v "less \"\$0\"" \
          |grep -B1 "function "   \
          |grep -v "\\--"         \
          |sed "s/function //g"   \
          |sed "s/(){//g"         \
          |sed "s/#//g"           \
          |sed 'N;s/\n/ /'        \
          |column -t              \
          |awk '{print $1,$3,$2}' \
          |column -t
}
function search_user_info(){
  local result=$(httpclient_get --cookie "${cookie}" \
                                         "${url}/userName=${user_name}")
}

执行方式

  1. 使用解释器(sh或者bash)运行脚本,开启一个子shell运行脚本内容
  2. 执行脚本绝对路径或相对路径,需要脚本有执行权限
  3. 使用. 或者source运行脚本,在当前父shell中运行里面的内容
  4. 传递给|bash执行,不常用

可以给脚本加上执行权限chmod +x /server/scripts/one.sh,并将脚本的绝对路径添加到path变量中echo PATH=/server/scripts:$PATH > /etc/profile.d/shell.sh,使变量立即生效. /etc/profile.d/shell.sh,就可以像运行普通命令一样直接执行脚本了!

父进程和子进程

[root@oldboyedu-lnb ~]# name=f;(echo $name;name=z;echo $name);echo $name  # 小括号会开启子进程,赋予的变量,只在小括号内有效,执行完命令后,就会退出子进程。
f
z
f
[root@oldboyedu-lnb ~]# name=f;{ echo $name;name=z;echo $name; };echo $name  # 大括号不会开启子进程,在当前进程有效,执行完命令后,留在当前进程。
f
z
z

调试

# 语法检测
bash -n /path/to/script
# 调试执行
bash -x /path/to/script

echo打印颜色字

echo -e "\033[31malong\033[0m"        显示红色along
echo -e "\033[1;31malong\033[0m"      高亮显示红色along
echo -e "\033[41malong\033[0m"        显示背景色为红色的along
echo -e "\033[31;5malong\033[0m"      显示闪烁的红色along

color=$[$[RANDOM%7]+31]
echo -ne "\033[1;${color};5m*\033[0m" 显示闪烁的随机色along

ShellScript变量

命名法则

  1. 不能使程序中的保留字:例如if,for
  2. 只能使用数字、字母及下划线,且不能以数字开头
  3. 见名知义
  4. 统一命名规则:驼峰命名法
  • 建议:
  1. 全局变量大写
  2. 局部变量小写
  3. 函数名小写

格式

  1. 变量赋值使用 = 等号,左右不能留有空格
  2. 使用变量时推荐使用 "${}" 双引号和大括号包裹
var1="Hello World"   # 正确,推荐使用双引号
var2=6.70            # 小数
var3="${var1}"       # 推荐 双引号和大括号 包裹

单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,单引号字串中不能出现单引号(对单引号使用转义符后也不行)。
双引号中的普通字符都会原样输出,可以使用$引用变量,双引号中可以出现单引号。

  1. 常量一定要定义成readonly

  2. 函数中的变量要用local修饰,定义成局部变量,这样在外部遇到重名的变量也不会影响

web="www.chen-shang.github.io"
function main(){
  local name="chenshang" # 这里使用local定义一个局部变量
  local web="${web}"     # 这里${}内的web是全局变量,之后在函数中在使用web变量都是使用的局部变量
  local web2="${web}"    # 对于全局变量,虽然在使用的时候直接使用即可,但还是推荐使用一个局部变量进行接收,然后使用局部变量,以防止在多线程操作的时候出现异常(相当于java中的静态变量在多线程中的时候需要注意线程安全一样,但常量除外)
}
  1. 变量一经定义,不允许删除(也就是禁用unset命令)

类型

强类型:

变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。一般定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误
如:java , c# ,python

弱类型:

语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
如:bash 不支持浮点数,php,javascript


shell中变量的基本类型就是String、数值(可以自己看做Int、Double之类的)、Boolean。

Boolean 其实是Int类型的变种, 在shell中0代表真、非0代表假,所以往往在shell脚本中用 readonly TURN=0 && readonly FALSE=1

根据变量的生效范围等标准划分下面变量类型:

局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效

环境变量:生效范围为当前shell进程及其子进程

本地变量:生效范围为当前shell进程中某代码片断,通常指函数

位置变量:$1, $2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数

特殊变量:$?, $0, $*, $@, $#,$$

局部变量

  • 变量赋值:name=‘value’

  • 变量引用:${name} 或者 $name

(1) 可以是直接字串:name=“root"
(2) 变量引用:name="$USER"
(3) 命令引用:name=`COMMAND`
             name=$(COMMAND)
  • " " 弱引用,其中的变量引用会被替换为变量值
  • ' ' 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
  • 显示已定义的所有变量:set
  • 删除变量:unset name

环境变量

  • 变量声明、赋值:
    export name=VALUE
    declare -x name=VALUE

  • 变量引用:
    $name, ${name}

  • 显示所有环境变量:
    env
    printenv
    export
    declare -x

  • 删除变量:unset name

  • bash内建的环境变量
    PATH
    SHELL
    USER
    UID
    HOME
    PWD
    SHLVL
    LANG
    MAIL
    HOSTNAME
    HISTSIZE
    _ 下划线

只读变量

只能声明,但不能修改和删除

  • 声明只读变量:
    readonly name
    declare -r name
  • 查看只读变量:
    readonly -p

位置变量

在脚本代码中调用通过命令行传递给脚本的参数

$1, $2, ... 对应第1、第2等参数,shift [n]换位置,从$9以后需要加{}表示整体
set --     清空所有位置变量

特殊变量

$0         脚本文件名称,如果全路径执行则带全路径,可以使用basename只获取名字
$#         传递给脚本的参数的个数
$*         传递给脚本的所有参数
$@         传递给脚本的所有参数
"$*"       全部参数合为一个字符串,可在循环中验证
"$@"       每个参数为独立字符串,可在循环中验证

$$         运行脚本的PID
$!         上一个运行脚本的PID
$_         当前命令行的最后一个参数, 类似于ESC .

$$$BASHPID区别:两者都是当前进程的编号,但是$BASHPID更精确

basename

只输出路径的基名

[root@oldboyedu-lnb ~]#  basename /etc/passwd
passwd

进程使用退出状态来报告成功或失败

0       代表成功
1-255  代表失败
$?      保存上一条命令的退出状态

例如:

ping -c1 -W1 hostdown &> /dev/null
echo $?

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

注意:

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

子串

[root@shell ~]# test='I am oldboy'
[root@shell ~]# url='www.baidu.com'

${var:n:x}切片

[root@shell ~]# echo ${test:2:2}  # (2,2+2]从第二个字符开始向后两位为止
am
[root@shell ~]# echo $test|awk '{print $2}'
am
[root@shell ~]# echo $test|cut -c3-4
am

${#var}字符长度

[root@shell ~]# echo ${#test}
11
[root@shell ~]# echo $test|wc -L
11
[root@shell ~]# expr length "$test"
11
[root@shell ~]# echo $test|awk '{print length}'
11

统计出字符串小于3的单词 笔试题
I am lzhenya teacher I am 18

[root@shell ~]# cat for.sh
for i in I am lzhenya teacher I am 18
do
		[ ${#i} -lt 3 ] && echo $i
done
[root@shell ~]# sh for.sh
I
am
I
am
18

[root@shell ~]# echo I am lzhenya teacher I am 18|xargs -n1|awk '{if(length<3)print}'
I
am
I
am
18

[root@shell ~]# echo I am lzhenya teacher I am 18|awk '{for(i=1;i<=NF;i++)if(length($i)<3)print $i}'
I
am
I
am
18

删除匹配内容

支持通配符*

${var#}从前往后匹配,${var##}贪婪匹配

${var%}从后往前匹配,${var%%}贪婪匹配

如果要匹配#%,需使用\转义

[root@shell ~]# echo ${url#www.}
baidu.com
[root@shell ~]# echo ${url#*.}
baidu.com
[root@shell ~]# echo ${url#*.*.}
com
[root@shell ~]# echo ${url##*.}
com

[root@shell ~]# echo ${url%.com}
www.baidu
[root@shell ~]# echo ${url%.*}
www.baidu
[root@shell ~]# echo ${url%.*.*}
www
[root@shell ~]# echo ${url%%.*}
www

${var/a/b}替换匹配内容

${var//a/b}贪婪匹配

[root@shell ~]# echo ${url/w/W}
Www.baidu.com
[root@shell ~]# echo ${url//w/W}
WWW.baidu.com
[root@shell ~]# echo ${url/baidu/sina}
www.sina.com
[root@shell ~]# echo $url|sed 's#www#WWW#g'
WWW.baidu.com

ShellScript算术运算

+, -, *, /, %取模(取余), **(乘方),乘法符号有些场景中需要转义

  1. 整数计算使用 expr或者 $[]或者$(())(运算最快)或者 let
  2. 小数计算使用 bc 计算器
  • 实现算术运算:
(1) var=$(expr arg1 arg2 arg3 ...)
(2) var=$[算术表达式]
(3) var=$((算术表达式))
(4) let var=算术表达式
(5) declare –i var = 数值
(6) echo ‘算术表达式’ | bc
  • 随机数
bash有内建的随机数生成器变量:$RANDOM(0-32767)
生成随机数 echo $RANDOM
生成指定范围随机数

示例:
# 生成随机7个数(0-6)
echo $[RANDOM%7]
# 生成随机7个数(31-37)
echo $[$[RANDOM%7]+31]
生成随机字符:cat /dev/urandom
# 生成8个随机大小写字母或数字
cat /dev/urandom |tr -dc [:alnum:] |head -c 8
  • 增强型赋值:

+=, -=, *=, /=, %=

let var OPER value
例如:let count+=3   自加3后自赋值
自增,自减:
let var+=1
let var++
let var-=1
let var--
let var=i++   是赋值后加
let var=++i   是先加后赋值
# 取1-63的余,其中 RANDOM%63 的值是0-62,加1就是1-63
echo $[RANDOM%63+1]
# 生成随机颜色
echo -e  "\033[1;$[RANDOM%7+31]m 字符串\033[0m"
  • 逻辑运算
true, false
1, 0

与 &
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0

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

非 !
! 1 = 0 ! true
! 0 = 1 ! false

短路与 &&
第一个为0,结果必定为0
第一个为1,第二个必须要参与运算

短路或 ||
第一个为1,结果必定为1
第一个为0,第二个必须要参与运算

异或:^ 异或的两个值,相同为假,不同为真

短路与和短路或 
[ $RANDOM%6 –eq 0 ] && rm –rf /* || echo “click”
# 数字互换
A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B

A=01010=10
B=10100=20
A=$[A^B]=11110=30

A=11110=30
B=10100=20=10
B=$[A^B]=01010

A=11110=30
B=01010=10
A=$[A^B]=10100=20

[root@oldboyedu-lnb ~]# A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B
A=20 B=10

ShellScript条件测试

非特别说明,则所有文件类操作都会追踪到软链接的源文件

test EXPRESSION
[ EXPRESSION ]
(( EXPRESSION ))    算术表达式
[[ EXPRESSION ]]    不会发生文件名扩展或者单词分割,会发生参数扩展和命令替换
注意:EXPRESSION 前后必须有空白字符
test -d "$HOME" ;echo $?
[ "abc" != "def" ];echo $?
test EXPRESSION && echo "exist" || echo "not exist"  更人性化地显示结果
test EXPRESSION && echo true || echo false           更人性化地显示结果

test -e file && echo "exist" || echo "not exist"
test 3 -gt 4 && echo true || echo false
  • bash的数值测试
  -v VAR   变量VAR是否设置
数值测试:
  -gt 是否大于
  -ge 是否大于等于
  -eq 是否等于
  -ne 是否不等于
  -lt 是否小于
  -le 是否小于等于
  • bash的字符串测试
=     是否等于
>     ascii码是否大于ascii码
<     是否小于
!=    是否不等于
=~    左侧字符串是否能够被右侧的 正则表达式 所匹配
注意: 此表达式一般用于[[ ]]中,[[ ]]中匹配正则表达式或通配符,不需要引号

[[ hello == hell? ]] && echo true || echo false
[[ 2\<3 ]] && echo true || echo false
[[ 0 < 1 ]] && echo true || echo false
[[ 2 -lt 3 ]] && echo true || echo false
[ 2 \< 3 ] && echo true || echo false
[ 1 = 1 ] && echo true || echo false

-z "STRING“ 字符串是否为空,空为真,不空为假
-n "STRING“ 字符串是否不空,不空为真,空为假
注意:用于字符串比较时的用到的操作数都应该使用引号
  • bash的文件测试
存在性测试
    -a FILE:同 -e
    -e FILE: 文件存在性测试,存在为真,否则为假
存在性及类别测试
    -b FILE:是否存在且为块设备文件
    -c FILE:是否存在且为字符设备文件
    -d FILE:是否存在且为目录文件
    -f FILE:是否存在且为普通文件
    -h FILE 或 -L FILE:存在且为符号链接文件
    -p FILE:是否存在且为命名管道文件
    -S FILE:是否存在且为套接字文件
  • bash的文件权限测试
文件权限测试:
    -r FILE:是否存在且可读
    -w FILE: 是否存在且可写
    -x FILE: 是否存在且可执行
文件特殊权限测试:
    -u FILE:是否存在且拥有suid权限
    -g FILE:是否存在且拥有sgid权限
    -k FILE:是否存在且拥有sticky权限
  • bash的文件属性测试
文件大小测试:
    -s FILE: 是否存在且非空
文件是否打开:
    -t fd: fd 文件描述符是否在某终端已经打开
    -N FILE:文件自从上一次被读取之后是否被修改过
    -O FILE:当前有效用户是否为文件属主
    -G FILE:当前有效用户是否为文件属组
双目测试:
    FILE1 -ef FILE2: FILE1是否是FILE2的硬链接
    FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)
    FILE1 -ot FILE2: FILE1是否旧于FILE2
  • bash的组合测试条件
第一种方式:
EXPRESSION1 -a EXPRESSION2 并且,只能在test或[]中使用
EXPRESSION1 -o EXPRESSION2 或者,只能在test或[]中使用
! EXPRESSION 非

第二种方式:
COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN,只能在[[]]中使用
COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE,只能在[[]]中使用
! COMMAND 非
如:[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]]
  • 条件性的执行操作符

示例:

grep -q no_such_user /etc/passwd || echo 'No such user'
No such user

ping -c1 -W2 station1 &> /dev/null \
> && echo "station1 is up" \
> || (echo 'station1 is unreachable'; exit 1)
station1 is up

test "$A" = "$B" && echo "Strings are equal"
test “$A”-eq “$B” && echo "Integers are equal“
[ "$A" = "$B" ] && echo "Strings are equal"
[ "$A" -eq "$B" ] && echo "Integers are equal“
[ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
# 判断字符串为空或者localhost.localdomain,则临时修改主机名为 www.magedu.com
[ -z “$HOSTNAME” -o $HOSTNAME "=="localhost.localdomain" ] && hostname www.magedu.com  

ShellScript相关命令

read

read           把输入值分配给一个或多个shell变量
     -a        后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符
     -e 	   在输入的时候可以使用命令补全功能
     -r 	   屏蔽\的转义功能
     -u 	   后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的
     -p        指定输入前打印提示信息
     -s        静默输入,输入的字符不在屏幕上显示,一般用于密码
     -n N      指定输入的字符长度最大为N
     -d ‘字符’  以输入的指定‘字符’作为结束符
     -t N      TIMEOUT为N秒,超时退出

read 从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
read -p “Enter a filename: “ FILE

示例: 
修改主机名称为shell,并且修改eth0网卡IP地址为88
[root@shell ~]# cat hostname.sh 
#!/bin/bash
eth0_cfg='/etc/sysconfig/network-scripts/ifcfg-eth0'
old_ip=`ifconfig eth0|awk 'NR==2{print $2}'|awk -F. '{print $NF}'`
read -p "please input hostname: " name
read -p "please input New IP: " IP
hostnamectl set-hostname $name
sed -i "s#$old_ip#$IP#g" $eth0_cfg
grep $IP $eth0_cfg

[root@shell ~]# cat ping.sh 
read -p "Please Input URL: " url
ping -c2 -W1  $url &>/dev/null
[ $? -ne 0 ] && echo "ping不通" || echo "通了"

cat

cat <<EOFcat <<-EOF的区别

用于执行脚本的时候,需要往一个文件里自动输入N行内容。

  • cat用于显示文本文件内容,全部输出。

  • EOF是END Of File的缩写,表示自定义终止符,Ctrl-D就代表EOF。

man说明:

If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter.

翻译:

如果重定向的操作符是<<-,那么分界符(EOF)所在行的开头部分的制表符(Tab)都将被去除。

也就是说:

  • cat <<EOF中EOF必须顶行写,前面不能用制表符或者空格。如果结束分解符EOF前有制表符或者空格,则EOF不会被当做结束分界符,只会继续被当做stdin来输入。
  • cat <<-EOF中就算最后的EOF前面有多个制表符和空格,但仍然会被当做结束分界符,表示stdin的结束。

trap

trap命令用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作。当shell接收到sigspec指定的信号时,arg参数(命令)将会被读取,并被执行,而不会执行原操作。

trap [-lp] [[arg] sigspec ...]
     -l    让shell打印一个命令名称和其相对应的编号的列表
     -p    如果有-p选项而没有提供arg参数,则会打印所有与sigspec指定信号相关联的的trap命令;
           如果没有提供任何参数或者仅有-p选项,trap命令将会打印与每一个信号有关联的命令的列表;
     
[arg]参数缺省或者为“-”,每个接收到的sigspec信号都将会被重置为它们进入shell时的值
[arg]参数是空字符串每一个由sigspec指定的信号都会被shell和它所调用的命令忽略;
trap commands signals
# commands 可以是任何有效的Linux命令,或一个用户定义的函数,
# signals 可以是任意数量的信号,或你想来捕获的列表。
# 信号有3种表达方法:信号的数字2、全名SIGINT、缩写INT

参考实例:

trap "rm -f $WORKDIR/work1$ $WORKDIR/dataout$; exit" 1 2  收到指定信号后清理临时文件
trap '' 1 2 20                                            收到指定信号后忽略信号
trap '-' 1 2 20
trap 1 2 20                                               恢复信号的默认操作,重设陷阱

每个sigspec信号都是是以名字或者编号的形式定义在signal.h头文件中,信号的名字是不区分大小写的,其前缀SIG是可选的,有以下情况:

  1. 如果sigspec是EXIT(0),那么arg指定的命令将会在shell上执行退出命令时执行

  2. 如果sigspec是DEBUG,那么arg指定的命令将会在以下每个命令执行之前执行:

    • 简单命令,for语句,case语句,select命令,算法命令,在函数内的第一条命令。
  3. 如果sigspec是ERR,那么arg指定的命令将会在任何简单命名执行完后返回值为非零值时执行,但是也有以下例外情况,arg命令不会执行,这些规则同样适用于errexit选项:

    • 如果执行失败的命令是紧跟在while或者until关键字之后的一组命令中的一部分时
    • 如果执行失败的命令是if测试语句的一部分时,是 && 和 ||连接的列表中的一部分时
    • 如果执行失败的命令的返回值是被取反过的(通过!操作符)
  4. 如果sigspec是RETURN,那么arg指定的命令在每次shell函数或者脚本用"."或者内置的命令执行完成后执行

注意:

  1. 在shell入口处被忽略的命令是没法被trap和reset的。
  2. 被trap的信号,在创建的子进程中使用时会在子进程被创建时被重置为原始的值。
  3. 如果trap使用的sigspec信号是无效的信号,则trap命令返回false(失败),否则返回true(成功)。

① 打印0-9,ctrl+c不能终止

执行脚本后,打印0-9,每秒一个数字,ctrl+c转换为echo press ctrl+c

#!/bin/bash
trap 'echo press ctrl+c' 2
for ((i=0;i<10;i++));do
        sleep 1
        echo $i
done

② 打印0-3,ctrl+c不能终止,3之后恢复,能终止

执行脚本后,打印0-3,每秒一个数字,ctrl+c不能终止,打印3之后解除捕获2信号,能终止

#!/bin/bash
trap '' 2
trap -p
for ((i=0;i<3;i++));do
        sleep 1
        echo $i
done
trap '-' SIGINT
for ((i=3;i<10;i++));do
        sleep 1
        echo $i
done

信号

信号是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。

应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。

进程收到一个信号后,会检查对该信号的处理机制:

  1. 如果是SIG_IGN,就忽略该信号;
  2. 如果是SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;
  3. 如果给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。

在有些情况下,我们不希望自己的shell脚本在运行时刻被中断,比如说我们写得shell脚本设为某一用户的默认shell,使这一用户进入系统后只能作某一项工作,如数据库备份,我们不希望用户使用Ctrl+c之类能够进入到shell状态,做我们不希望做的事情。这便用到了信号处理。

常见信号:

  1. SIGHUP: 无须关闭进程而让其重读配置文件

  2. SIGINT: 中止正在运行的进程;相当于Ctrl+c

  3. SIGQUIT: 相当于ctrl+\

  4. SIGKILL: 强制杀死正在运行的进程;本信号不能被阻塞,处理和忽略。

  5. SIGTERM :终止正在运行的进程(默认为15)

  6. SIGCONT :继续运行

  7. SIGSTOP :后台休眠

信号名称 信号数 描述
SIGHUP 1 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
SIGINT 2 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出。
SIGQUIT 3 和SIGINT类似, 但由QUIT字符(通常是Ctrl+/)来控制。进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号。
SIGFPE 8 在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。
SIGKILL 9 用来立即结束程序的运行。本信号不能被阻塞,处理和忽略。
SIGALRM 14 时钟定时信号,计算的是实际的时间或时钟时间。 alarm函数使用该信号。
SIGTERM 15 程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理,通常用来要求程序自己正常退出。shell命令kill缺省产生这个信号。
SIGHUP    1   /* Hangup (POSIX).  */                     终止进程  终端线路挂断
SIGINT    2   /* Interrupt (ANSI).  */                   终止进程  中断进程 Ctrl+C
SIGQUIT   3   /* Quit (POSIX).  */                       建立CORE文件终止进程,并且生成core文件 Ctrl+
SIGILL    4   /* Illegal instruction (ANSI).  */         建立CORE文件,非法指令
SIGTRAP   5   /* Trace trap (POSIX).  */                 建立CORE文件,跟踪自陷
SIGABRT   6   /* Abort (ANSI).  */
SIGIOT    6   /* IOT trap (4.2 BSD).  */                 建立CORE文件,执行I/O自陷
SIGBUS    7   /* BUS error (4.2 BSD).  */                建立CORE文件,总线错误
SIGFPE    8   /* Floating-point exception (ANSI).  */    建立CORE文件,浮点异常
SIGKILL   9   /* Kill, unblockable (POSIX).  */          终止进程  杀死进程
SIGUSR1   10  /* User-defined signal 1 (POSIX).  */      终止进程  用户定义信号1
SIGSEGV   11  /* Segmentation violation (ANSI).  */      建立CORE文件,段非法错误
SIGUSR2   12  /* User-defined signal 2 (POSIX).  */      终止进程  用户定义信号2
SIGPIPE   13  /* Broken pipe (POSIX).  */                终止进程  向一个没有读进程的管道写数据
SIGALARM  14  /* Alarm clock (POSIX).  */                终止进程  计时器到时
SIGTERM   15  /* Termination (ANSI).  */                 终止进程  软件终止信号
SIGSTKFLT 16  /* Stack fault.  */
SIGCHLD   17  /* Child status has changed (POSIX).  */   忽略信号  当子进程停止或退出时通知父进程
SIGCONT   18  /* Continue (POSIX).  */                   忽略信号  继续执行一个停止的进程
SIGSTOP   19  /* Stop, unblockable (POSIX).  */          停止进程  非终端来的停止信号
SIGTSTP   20  /* Keyboard stop (POSIX).  */              停止进程  终端来的停止信号 Ctrl+Z
SIGTTIN   21  /* Background read from tty (POSIX).  */   停止进程  后台进程读终端
SIGTTOU   22  /* Background write to tty (POSIX).  */    停止进程  后台进程写终端
SIGURG    23  /* Urgent condition on socket (4.2 BSD).*/ 忽略信号  I/O紧急信号
SIGXCPU   24  /* CPU limit exceeded (4.2 BSD).  */       终止进程  CPU时限超时
SIGXFSZ   25  /* File size limit exceeded (4.2 BSD).  */ 终止进程  文件长度过长
SIGVTALRM 26  /* Virtual alarm clock (4.2 BSD).  */      终止进程  虚拟计时器到时
SIGPROF   27  /* Profiling alarm clock (4.2 BSD).  */    终止进程  统计分布图用计时器到时
SIGWINCH  28  /* Window size change (4.3 BSD, Sun).  */  忽略信号  窗口大小发生变化
SIGIO     29  /* I/O now possible (4.2 BSD).  */         忽略信号  描述符上可以进行I/O
SIGPWR    30  /* Power failure restart (System V).  */
SIGSYS    31  /* Bad system call.  */

expect

安装

yum install expect -y
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
       -c:从命令行执行expect脚本,默认expect是交互地执行的
       -d:可以输出输出调试信息  

示例:
expect -c 'expect "\n" {send "pressed enter\n"}
expect -d ssh.exp

expect中相关命令 :

spawn:启动新的进程
send:用于向进程发送字符串
expect:从进程接收字符串
interact:允许用户交互,并停留在远程连接的主机上
exp_continue:匹配多个字符串在执行动作后加此命令

expect最常用的语法(tcl语言:模式-动作)

单一分支模式语法:

匹配到hi后,会输出“you said hi”,并换行

expect “hi” {send “You said hi\n"}

多分支模式语法:

匹配hi,hello,bye任意字符串时,执行相应输出。

expect "hi" { send "You said hi\n" } \  
       "hehe" { send "Hehe yourself\n" } \  
       "bye" { send "Good bye\n" }

等同如下:

expect {  
           "hi" { send "You said hi\n"}  
           "hehe" { send "Hehe yourself\n"}  
           "bye" { send  " Good bye\n"}
}

示例

① 用户名密码自动登录系统

#!/usr/bin/expect
set ip 192.168.7.100
set user root
set password centos
set timeout 10
# 登录 调用user和ip两个变量的值
spawn ssh $user@$ip
expect {
# 有发现yes/no 输入 yes\n
  "yes/no" { send "yes\n";exp_continue }
# 有发现password输入$password的值
  "password" { send "$password\n" }               
}
# 允许用户交互
interact

② shell调用expect脚本

#!/bin/bash
ip=$1 
user=$2
password=$3
expect <<EOF   # 开启expect命令多行重定向
set timeout 20
spawn ssh $user@$ip
expect {        
        "yes/no" { send "yes\n";exp_continue }        
        "password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }
expect "]#" { send "echo centos |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof   # 结束语
EOF

③ 多主机批量操纵:根据相同用户名和密码,批量创建用户

1、创建IP地址清单

[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF
192.168.7.101
192.168.7.102
192.168.7.103
EOF

2、通过while实现批量读取文件内容

#!/bin/bash
while read ip;do
user=root
password=centos
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {        
        "yes/no" { send "yes\n";exp_continue }        
        "password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }   # 远程ssh登录后创建用户名
expect "]#" { send "echo centos |passwd --stdin hehe\n" }  # 设置密码
expect "]#" { send "exit\n" }
expect eof
EOF 
done < iplist.txt

④ 多主机批量操纵:根据不同用户名和密码传递公钥,实现免密钥登录

1、创建IP地址,密码清单

[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF
192.168.7.101  wangwang
192.168.7.102  centos
192.168.7.103  hahahaha
EOF

2、通过while实现批量读取文件内容

#!/bin/bash
ssh-keygen -t rsa -P "" -f /root/.ssh/id_rsa
while read ip password;do
user=root
set timeout 10
expect << EOF
spawn ssh-copy-id -i /root/.ssh/id_rsa.pub $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect eof
EOF
done <iplist.txt

ShellScript逻辑语句

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

条件语句

if

选择执行:可嵌套

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

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

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

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

Example:
根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then
    echo 'Station1 is UP'
elif grep "station1" ~/maintenance.txt &> /dev/null; then
    echo 'Station1 is undergoing maintenance'
else
    echo 'Station1 is unexpectedly DOWN!'
exit 1
fi

① 判断年纪

请输入年纪,先判断输入的是否含有除数字以外的字符,有,输出"please input a int";没有,继续判断是否小于150,是否大于18。

#!/bin/bash
read -p "Please input your age: " age
if [[ $age =~ [^0-9] ]] ;then
    echo "please input a int"
    exit 10
elif [ $age -ge 150 ];then
    echo "your age is wrong"
    exit 20
elif [ $age -gt 18 ];then
    echo "good good work,day day up"
else
    echo "good good study,day day up"
fi

② 判断分数

请输入成绩,先判断输入的是否含有除数字以外的字符,有,输出"please input a int";没有,继续判断是否大于100,是否大于85,是否大于60。

#!/bin/bash
read -p "Please input your score: " score
if [[ $score =~ [^0-9] ]] ;then
    echo "please input a int"
    exit 10
elif [ $score -gt 100 ];then
    echo "Your score is wrong"
    exit 20
elif [ $score -ge 85 ];then
    echo "Your score is very good"
elif [ $score -ge 60 ];then
    echo "Your score is soso"
else
    echo "You are loser"
fi

case

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

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

① 判断yes or no

请输入yes or no,回答Y/y、yes各种大小写组合为yes;回答N/n、No各种大小写组合为no。

#!/bin/bash
read -p "Please input yes or no: " anw
case $anw in
[yY][eE][sS]|[yY])
    echo yes
    ;;
[nN][oO]|[nN])
    echo no
    ;;
*)
    echo false
    ;;
esac

循环语句

for

for (( i = 0; i < 10; i++ )); do
  循环体
done

for item in 列表; do
  循环体
done

执行机制:依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束

列表:

  • 支持glob通配符,如:{1..10}*.sh
  • 也可以引用变量 ${array},如:seq 1 $1

① 求(1+2+...+n)的总和

sum初始值为0,请输入一个数,判断输入的值是否以1-9开头,后面跟任意个0-9的数字,不是,就报错;是,进入for循环,i的范围为1~输入的数,每次的循环为sum=sum+i,循环结束,最后输出sum的值。

#!/bin/bash
sum=0
read -p "Please input a positive integer: " num
if [[ ! $num =~ ^[1-9][0-9]* ]] ;then
    echo "input error"
else
    for i in `seq 1 $num` ;do
        sum=$[$sum+$i]
    done
    echo $sum
fi

while

while [[ 循环控制条件 ]]; do
  循环体
done

while read -r item ;do
  循环体
done < 'file_name'

cat 'file_name' | while read line; do
  循环体
done

循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true” ,则执行一次循环;直到条件测试状态为“false” 终止循环

遍历文件的每一行:依次读取file_name文件中的每一行,且将行赋值给变量line

① 100以内所有正奇数之和

sum初始值为0,i的初始值为1;当i<=100时,进入循环,判断 i÷2取余,不为0时为奇数,sum=sum+i,i+1;为0时,i+1;循环结束,最后输出sum的值。

#!/bin/bash
sum=0
i=1
while [ $i -le 100 ] ;do
if [ $[$i%2] -ne 0 ];then
    let sum+=i
    let i++
else
    let i++
fi
done
echo "sum is $sum"

② 菜单

#!/bin/bash
cat << EOF
             1)gongbaojiding
             2)kaoya
             3)fotiaoqiang
             4)haishen
             5)baoyu
             6)quit
EOF
while read -p "please choose the number: " num;do
case $num in
    1)
     echo "gongbaojiding price is 30"
     ;;
    2)
     echo "kaoya price price is 80"
     ;;
    3)  
     echo "fotiaoqiang price is 200"
     ;;
    4)
     echo "haishen price is \$20"
     ;;
    5)
     echo "baoyu price is \$10"
     ;;
    6)
     break
     ;;
    *)
    echo "please input again"
esac
done

③ 统计日志访问IP情况

#!/bin/bash
# 其中access_log为访问日志,统计访问IP和次数,导出到文件iplist.txt中
sed -rn 's/^([^[:space:]]+).*/\1/p' access_log |sort |uniq -c > iplist.txt
# while read 逐行处理
while read count ip;do
    if [ $count -gt 100 ];then
        # 将统计后的日志导出到文件crack.log中
        echo from $ip access $count  >> crack.log
    fi
# while read 支持重定向,可以将要统计的文件导入到循环中
done < iplist.txt

④ 统计磁盘使用率大于指定值的信息

#!/bin/bash
# 定义报警的磁盘使用率
WARNING=10
df | awk '{if($5>$WARNING)print $0}'
#!/bin/bash
# 定义报警的磁盘使用率
WARNING=10
df |sed -rn  '/^\/dev\/sd/s#^([^[:space:]]+).* ([[:digit:]]+)%.*$#\1 \2#p' | while read part use; do
    if [ $use -gt $WARNING ]; then
        echo $part will be full,use:$use
    fi
done
#!/bin/bash
# 定义报警的磁盘使用率
WARNING=10
df |awk -F"[[:space:]]+|%" '/dev\/sd/{print $1,$(NF-2)}' > disk.txt
while read part use; do
        if  [ $use -gt $WARNING ]; then
            echo $part will be full,use:$use
        fi
done < disk.txt

until

until [[ 循环控制条件 ]]; do
  循环体
done

进入条件:循环条件为false ;

退出条件:循环条件为true;

刚好和while相反,所以不常用,用while就行。

① 监控test用户,登录就杀死

#!/bin/bash
# 发现test用户登录,条件为true,退出循环
until pgrep -u test &> /dev/null ;do
    # 每隔0.5秒扫描
    sleep 0.5
done
# 杀死test用户相关进程
pkill -9 -u test

select

select variable in list
do
  循环体
done

① select 循环主要用于创建菜单,按数字顺序排列的示菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入

② 用户输入菜单列表中的某个数字,执行相应的命令

③ 用户输入被保存在内置变量 REPLY 中

④ select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c退出循环

⑤ select 经常和 case 联合使用

⑥ 与for循环类似,可以省略 in list, 此时使用位置参量

示例: 生成菜单,并显示选中的价钱

#!/bin/bash
PS3="Please choose the menu: "
select menu in mifan huimian jiaozi babaozhou quit
do
        case $REPLY in
        1|4)
                echo "the price is 15"
                ;;
        2|3)
                echo "the price is 20"
                ;;
        5)
                break
                ;;
        *)
                echo "no the option"
        esac
done

注意:PS3是select的提示符,自动生成菜单,选择5退出循环。

循环控制语句

continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层

break [N]:提前结束第N层循环,最内侧为第1层

例:

while CONDTITON1; do
  CMD1
if CONDITION2; then
  continue / break
fi
  CMD2
done

(2)案例:

① 求(1+3+...+49+53+...+100)的和

#!/bin/bash
sum=0
for i in {1..100} ;do
        [ $i -eq 51 ] && continue
        [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; }
done
echo sum=$sum

分析:做1+3+...+99的循环,当i=51时,跳过这次循环,但是继续整个循环,结果为:sum=2449

② 求(1+3+...+49)的和

#!/bin/bash
sum=0
for i in {1..100} ;do
        [ $i -eq 51 ] && break
        [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; }
done
echo sum=$sum

分析:做1+3+...+99的循环,当i=51时,跳出整个循环,结果为:sum=625

shift

shift n    用于将参数列表list左移指定次数,最左端的那个参数就从列表中删除,其后边的参数继续进入循环,n是整数

1.依次读取输入的参数并打印参数个数:
$ cat run.sh
#!/bin/bash
while [ $# != 0 ];do
echo "第一个参数为:$1,参数个数为:$#"
shift
done 
$ sh run.sh a b c d e f 
第一个参数为:a,参数个数为:6
第一个参数为:b,参数个数为:5
第一个参数为:c,参数个数为:4
第一个参数为:d,参数个数为:3
第一个参数为:e,参数个数为:2
第一个参数为:f,参数个数为:1 

2.把参数进行左移3个:
$ cat t.sh
#!/bin/bash
echo -e "./t.sh arg1 arg2 arg3 arg4 arg5 arg6"
str1="${1},${2},${3}"
echo "str1=$str1"
shift 3
str2=$@
echo "str2=$str2"
$ sh t.sh 1 2 3 4 5 6 7
str1=1,2,3

3.将参数从左到右逐个移动:
$ cat shift.sh
#!/bin/bash
while [ $# -ne 0 ]
do
echo "第一个参数为: $1 参数个数为: $#"
shift
done
$ sh shift.sh Lily Lucy Jake Mike
第一个参数为: Lily 参数个数为: 4
第一个参数为: Lucy 参数个数为: 3
第一个参数为: Jake 参数个数为: 2
第一个参数为: Mike 参数个数为: 1

① 创建指定的多个用户

#!/bin/bash
if [ $# -eq 0 ] ;then
        echo "Please input a arg(eg:`basename $0` arg1)"
        exit 1
else
        while [ -n "$1" ];do
                useradd $1 &> /dev/null
                echo "User:$1 is created"
                shift
        done
fi

分析:如果没有输入参数(参数的总数为0),提示错误并退出;反之,进入循环;若第一个参数不为空字符,则创建以第一个参数为名的用户,并移除第一个参数,将紧跟的参数左移作为第一个参数,直到没有第一个参数,退出。

② 打印直角三角形的字符

#!/bin/bash
while (( $# > 0 ))
do
    echo "$*"
    shift
done
[root@oldboyedu-lnb ~]# sh trian.sh 1 2 3
1 2 3
2 3
3

Boolean

true

永远成功

false

永远错误


无限循环

while true ;do
  循环体
done
# 或者
until false ;do
  循环体
done

并行循环

每次循环将循环体放入后台执行. 继续下一次循环. 最后等待所有线程执行完毕再退出脚本

for name in 列表 ;do
  {
  循环体
  }&
done
wait

① 搜寻指定ip(子网掩码为24的)的网段中,UP的ip地址

read -p "Please input network (eg:192.168.0.0): " net
echo $net |egrep -o "\<(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>"
[ $? -eq 0 ] || ( echo "input error";exit 10 )
IP=`echo $net |egrep -o "^([0-9]{1,3}\.){3}"`
for i in {1..254};do
        {
        ping -c 1 -w 1 $IP$i &> /dev/null && \
        echo "$IP$i is up"
        }&
done
wait

分析:请输入一个IP地址,例如192.168.37.234,如果格式不是0.0.0.0 则报错退出;正确则进入循环,IP变量的值为192.168.37. i的范围为1-254,并行ping 192.168.37.1-154,ping通就输出此IP为UP。

展开命令行

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

防止扩展

  1. 反斜线\会使随后的一个字符按原意解释

  2. 单引号'防止所有扩展

  3. 双引号"除了以下情况,防止所有扩展:

  $   (美元符号)   变量扩展(注意:"$" 输出 $,仍有特殊含义)
  `   (反引号)      命令替换
  \   (反斜线)     禁止单个字符扩展
  !   (叹号)       历史命令替换

分支

HEAD_KEYWORD parameters; BODY_BEGIN
  BODY_COMMANDS
BODY_END
  • 将HEAD_KEYWORD和初始化命令或者参数放在第一行;
  • 将BODY_BEGIN同样放在第一行;
  • 复合命令中的BODY_COMMANDS部分以2个空格缩进;
  • BODY_END部分独立一行放在最后;
  1. if
if [[ condition ]]; then
  # statements
fi

if [[ condition ]]; then
  # statements
else
  # statements
fi

if [[ condition ]]; then
  # statements
elif [[ condition ]]; then
  # statements
else
  # statements
fi
  • if 后面的判断 使用 双中括号[[]]
  • if [[ condition ]]; then 写在一行
  1. while
while [[ condition ]]; do
  # statements
done

while read -r item ;do
  # statements
done < 'file_name'
  1. until
until [[ condition ]]; do
  # statements
done
  1. for
for (( i = 0; i < 10; i++ )); do
  # statements
done

for item in ${array}; do
  # statements
done
  1. case
case $var in
  pattern )
    #statements
    ;;
    *)
    #statements
    ;;
esac

ShellScript函数

function用法

  1. 函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程。

  2. 它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运 行,而是shell程序的一部分,定义函数只对当前的会话窗口有效,如果再打开一个窗口再定义另外一个函数,就对另一个窗口有效,两者互不影响。

  3. 函数和shell程序比较相似,区别在于以下两种:

(1)Shell程序在子Shell中运行。

(2)而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改。

定义函数

function main(){
  #函数执行的操作
  #函数的返回结果
}

main(){
  #函数执行的操作
  #函数的返回结果
}

function main {
  #函数执行的操作
  #函数的返回结果
}
  1. 使用关键字 function 显示定义的函数为 public 的函数,可以供外部脚本以 sh 脚本 函数 函数入参 的形式调用
  2. 未使用关键字 function 显示定义的函数为 privat 的函数, 仅供本脚本内部调用,注意这种privat是人为规定的,并不是shell的语法,不推荐以 sh 脚本 函数 函数入参 的形式调用,注意是不推荐而不是不能。

本shell规约这样做的目的就在于使脚本具有一定的封装性,看到 function 修饰的就知道这个函数能被外部调用, 没有被修饰的函数就仅供内部调用。你就知道如果你修改了改函数的影响范围. 如果是被function修饰的函数, 修改后可能影响到外部调用他的脚本, 而修改未被function修饰的函数的时候,仅仅影响本文件中其他函数。

如 core.sh 脚本内容如下是

# 重新设置DNS地址 []<-()
function set_name_server(){
  > /etc/resolv.conf
  echo nameserver 114.114.114.114 >> /etc/resolv.conf
  echo nameserver 8.8.8.8 >> /etc/resolv.conf
  cat /etc/resolv.conf
}
# info级别的日志 []<-(msg:String)
log_info(){
  echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[32m [info] \033[0m $*" >&2
}
# error级别的日志 []<-(msg:String)
log_error(){
  # todo [error]用红色显示
  echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[31m [error] \033[0m $*" >&2
}

则我可以使用 sh core.sh set_name_server 的形式调用 set_name_server 函数,但就不推荐使用 sh core.sh log_info "Hello World" 的形式使用 log_infolog_error 函数,注意是不推荐不是不能。


(1)可在交互式环境下定义函数

(2)可将函数放在脚本文件中作为它的一部分

#!/bin/bash
# 定义function func_os_version,在大括号里边定义命令,取出操作系统的版本号,类似于定义别名一样
func_os_version () {  
sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release   
}
# 直接写上函数名,或者用echo加反引号输出结果
echo OS version is `func_os_version`  

如果命令过多,不太方便

(3)可放在只包含函数的单独文件中

  • 然后将函数文件载入shell
  • 文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
  • 一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set查看所有定义的函数,其输出列表包括已经载入shell的所有函数
  • 若要改动函数,首先用unset function_name从shell中删除函数。改动完毕后,再重新载入此文件
# 将定义的函数放到functions文件中
[root@centos-7 ~]# cat functions
func_os_version () {
sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release
}

[root@centos-7 ~]# cat osversion.sh  
#!/bin/bash
# 在当前脚本加载 functions
source functions
# 调用函数
func_os_version

# 对脚本osversion.sh加上执行权限
[root@centos-7 ~]# chmod +x osversion.sh
# 运行脚本
[root@centos-7 ~]# ./osversion.sh
7

可以使用declare -F 查看所有定义的函数

定义环境函数

使子进程也可使用

(1)声明:export -f function_name

(2)查看:export -fdeclare -xf

调用函数

调用:给定函数名

函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止


使用脚本单独调用函数中的某个函数

#!/usr/bin/env bash
# shellcheck disable=SC1091,SC2155
readonly local TRUE=0 && readonly local FALSE=1
# 脚本使用帮助文档
manual(){
  cat "$0"|grep -v "less \"\$0\"" \
          |grep -B1 "function "   \
          |grep -v "\\--"         \
          |sed "s/function //g"   \
          |sed "s/(){//g"         \
          |sed "s/#//g"           \
          |sed 'N;s/\n/ /'        \
          |column -t              \
          |awk '{print $1,$3,$2}' \
          |column -t
}
######################################################################
# 主函数
main(){
  (manual)
}
######################################################################
# 执行函数 [Any]<-(function_name:String,function_parameters:List<Any>)
execute(){
  function_name=$1
  shift # 参数列表以空格为分割左移一位,相当于丢弃掉第一个参数
  function_parameters=$*
  (${function_name} "${function_parameters}")
}
case $1 in
  "-h" | "--help" | "?") (manual);;
  "") (main) ;;
  *) (execute "$@") ;;
esac

使用如上的框架,只需要在 两个 ###################################################################### 之间写函数,就可以使用 sh 脚本名称 脚本中的某个函数 脚本中的某个函数的入参 的形式调用函数了。
使用 sh 脚本名称 ? 或者 sh 脚本名称 -h/--help 就可以查看这个脚本中的函数说明了。

img

  1. 在函数内部首先使用有意义的变量名接受参数,然后在使用这些变量进行操作,禁止直接操作$1,$2 等,除非这些变量只用一次
  2. 函数的注释
    函数类型的概念是从函数编程语言中的概念偷过来的,shell函数的函数类型指的是函数的输入到函数的输入的映射关系
# 主函数 []<-()                  <-------函数注释这样写
function main(){
  local var="Hello World!!!"
  echo ${var}
}
# info级别的日志 []<-(msg:String)  <-------带入参的函数注释
log_info(){
  echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2
}

main函数的函数类型是 []<-() , <- 左侧表的是函数的返回值类型用[]包裹, 右侧是函数的参数类型用()包裹,多个参数用 ',' 分隔,
参数的描述是从 Scala 语言中偷过来, 先是参数名称, 然后是参数类型, 中间用:分隔

对于main函数的注释来说, # 顶格写,后面紧跟一个空格,其实这样写是遵循的markdown的语法, 后面再跟一个空格,然后是 []<-(),代表这个函数没有入参也没有返回值,这个函数的目的就是执行这个这个函数中的命令,但我不关心这个函数的返回值。也就是利用函数的副作用来完成我们想要的操作。

对于log_info也是一样不过最后的函数类型是 []<-(msg:String) 代表入参是一个string类型的信息,然后也没有返回值。
关于函数的返回值,我理解的函数的返回值有两种形式,一种是显示的return一种是隐式的echo

以下是几种常见的写法

[]<-()
[String]<-(var1:String,var2:String)
[Boolean]<-(var1:String,var2:Int)
[]<-(var1:String)

返回值

1、函数的执行结果返回值:

(1) 使用echo等命令进行输出

(2) 函数体中调用命令的输出结果

2、函数的退出状态码:

(1) 默认取决于函数中执行的最后一条命令的退出状态码

(2) 自定义退出状态码,其格式为:

return 从函数中返回,用最后状态命令决定返回值:

  (1)return 0 无错误返回。

  (2)return 1-255 有错误返回


执行一条命令的时候, 比如 pwd 正常情况下它输出的结果是 当前所处的目录

$ pwd
/Users/chenshang

shell中必然有一种状态来标识一条命令是否执行成功,也就是命令执行结果的状态。

0代表真、成功的含义。

非零代表假、失败的含义。

所以 pwd 这条命令如果执行成功的话,命令的执行结果状态一定是0,然后返回值才是当前目录。如果这条命令执行失败的话,命令的执行结果状态一定不是0,有可能是1 代表命令不存在,然后输出 not found,也有可能执行结果状态是2代表超时,然后什么也不输出。那怎么获取这个命令的执行结果和执行结果的状态呢?

function main(){
  pwd
}

执行main函数就会在控制台输出当前目录
如果想要将pwd的内容获取到变量中以供后续使用呢

function main(){
  local dir=$(pwd)
  echo "dir is ${dir}"
}

如果想要获取pwd的执行结果的状态呢

function main(){
  local dir=$(pwd)
  local status=$?
  echo "pwd run status is ${status}" #这个stauts一定有值,且是int类型,取值范围在0-255之间
  echo "dir is ${dir}"
}

显示return

return 用来显示的返回函数的返回结果,例如

# 检查当前系统版本 [Integer]<-()
function check_version(){
  (log_info "check_version ...") # log_info是我写的工具类中的一个函数
  local version # 这里是先定义变量,在对变量进行赋值,我们往往是直接初始化,而不是像这样先定义在赋值,这里只是告诉大家可以这么用
  version=$(sed -r 's/.* ([0-9]+)\..*/\1/' /etc/redhat-release)
  (log_info "centos version is ${version}")
  return "${version}"
}

这样这个函数的返回值是一个数值类型,我在脚本的任何地方调用check_version这个函数后,使用 $? 获取返回值

check_version
local version=$?
echo "${version}"

注意这里不用 local version=$(check_version) 这种形式获取结果,这样也是获取不到结果的,因为显示的return结果,返回值只能是[0-255]的数值,这对于我们一般的函数来说就足够了,因为我们使用显示return的时候往往是知道返回结果一定是数字且在[0-255]之间的,常常用在状态判断的时候。

本shell规约规定:

  1. 明确返回结果是在[0-255]之间的数值类型的时候使用显示 reuturn 返回结果
  2. 返回结果类型是Boolean类型,也就是说函数的功能是起判断作用,返回结果是真或者假的时候使用显示 return 返回结果
# 检查网络 [Boolean]<-()
function check_network(){
  (log_info "check_network ...")
  for((i=1;i<=3;i++));do
    http_code=$(curl -I -m 10 -o /dev/null -s -w %\{http_code\}  www.baidu.com)
    if [[ ${http_code} -eq 200 ]];then
      (log_info "network is ok")
      return ${TRUE}
    fi
  done
  (log_error "network is not ok")
  return ${FALSE}
}
# 获取数组中指定元素的下标 [int]<-(TABLE:Array,target:String)
function get_index_of(){
  readonly local array=($1)
  local target=$2
  local index=-1 # -1其实是255
  local size=${#array[@]}
  for ((i=0;i<${size};i++));do
    if [[ ${array[i]} == ${target} ]];then
      return ${i}
    fi
  done
  return ${index}
}

隐式echo

return 用来显示的返回函数的返回结果,例如

# 将json字符串格式化树形结构 [String]<-(json_string:String)
function json_format(){
  local json_string=$1
  echo "${json_string}"|jq . #jq是shell中处理json的一个工具
}

函数中所有的echo照理都应该输出到控制台上 例如

json_format "{\"1\":\"one\"}"

你会在控制台上看到如下输出

{
  "1": "one"
}

但是一旦你用变量接收函数的返回值,这些本该输出到控制台的结果就都会存储到你定义的变量中 例如

json=$(json_format "{\"1\":\"one\"}")
echo "${json}" # 如果没有这句,上面的语句执行完成后,不会在控制台有任何的输出

我们把 json_format 改造一下

# 将json字符串格式化树形结构 [String]<-(json_string:String)
function json_format(){
  local json_string=$1
  echo "为格式化之前:${json_string}" # 其实新添加的只一句只是用来记录一行日志的
  echo "${json_string}"|jq . # jq是shell中处理json的一个工具
}

echo "为格式化之前:${json_string}" 其实新添加的只一句只是用来记录一行日志的,但是json=$(json_format "{"1":"one"}")
json 也会将这句话作为返回结果进行接收,但这是我不想要看到的。

子shell可以捕获父shell的变量,但不能改变父shell的变量,使用()将代码块包裹,包裹的代码块将在子shell中运行,子shell相当于独立的一个环境,不会影响到父shell的结果

所以如果我不想让 echo "为格式化之前:${json_string}" 这句话也作为结果的话,我就只需要用()将代码块包裹即可

# 将json字符串格式化树形结构 [String]<-(json_string:String)
function json_format(){
  local json_string=$1
  (echo "为格式化之前:${json_string}") # 其实新添加的只一句只是用来记录一行日志的
  echo "${json_string}"|jq . # jq是shell中处理json的一个工具
}

示例

① 对不同的成绩分段进行判断

[root@oldboyedu-lnb ~]# cat functions
func_is_digit(){
    # 判断参数$1不是空,就为真,取反,空为真
    if [ ! "$1" ];then
        # 请输入数字
        echo "Usage:func_is_digit number"
        return  10
    # 如果输入是数字,返回0
    elif [[ $1 =~ ^[[:digit:]]+$ ]];then
        return 0
    else
    # 否则提醒不是数字
        echo "Not a  digit"  
        return 1
    fi
}
[root@oldboyedu-lnb ~]# cat score.sh
#!/bin/bash
source /data/functions
read -p "Input your score:" SCORE
func_is_digit $SCORE
#判断上面的命令执行结果不等于0就退出
if [ $? -ne 0 ];then
    exit
else
    # 如果成功了,对成绩的三种判断如下。
    if [ $SCORE -lt 60 ];then
        echo "You are loser"
    elif [ $SCORE -lt 80 ];then
        echo "soso"
    else
        echo "very good"
    fi
fi

② function配合case:代码发布与回滚与检验

#!/bin/bash
# Author:  liupengju
# date:    2020-06-22
# TEL:     xxxxxxxxxx
# 代码发布与回滚
set -e
set -u
 
# adx代码部署 定义变量
ADX_DIR=/gnome/adx
adx_new_version="gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar"
ADX_NEW_MD5=`md5sum $adx_new_version | awk '{ print $1 }'`
 
# adx代码部署 cf平台的md5码
ADX_CHK_MD5="43bcfe7594f083a8653126e0896b93ac"
 
# directAd代码部署 定义变量
direct_DIR=/gnome/directAd/
direct_version="direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar"
direct_MD5=`md5sum $direct_version | awk '{ print $1 }'`
#direct_old_version=$(ls -l  |tail -n1 | awk '{print $9}')
 
# directAd代码部署 cf平台的md5码
direct_CHK_MD5="03c3c2fc62b2edfc92e548351010ee9f"
 
##########部署directAd代码#############################
fun_copy_direct_code(){
    mv $direct_DIR/$direct_version  $direct_DIR/bak/${direct_version}_$(date +"%F-%T")
        echo "-----上一个版本已经移动到备份目录"
    cp /data/$direct_version  $direct_DIR  && echo "-----代码复制成功!!!"
}
 
fun_chk_direct_code(){
      if [[ "$direct_MD5" == "$direct_CHK_MD5" ]];then
           echo "-----代码校验成功" && echo "代码部署成功后MD5值为:$direct_MD5"
      else
           echo "-----代码校验失败" && exit
      fi
}
 
fun_deploy_direct_restart(){
    #$direct_DIR/restart.sh
    systemctl restart httpd
    systemctl restart nginx
    echo "后端服务重启成功!!!"
}

# 验证端口存活状态
fun_chk_direct_port1(){
    PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'`
    PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'`
     for port in $PORT1 $PORT2;do
        echo "The port is:$port------监听端口正常"
     done
}

#############回滚directAd代码###################################
fun_rollback_direct_code(){
    cd $direct_DIR/bak
# 提取上一个版本的jar包
    direct_old_version=$(ls -l  |tail -n1 | awk '{print $9}')  
    mv $direct_DIR/${direct_version} $direct_DIR/bak/${direct_version}_$(date +"%F-%T")
    mv $direct_DIR/bak/${direct_old_version} $direct_DIR/${direct_version}
    echo "------旧版本代码移动成功"
    direct_old_MD5=$(md5sum $direct_DIR/${direct_version} |  awk '{print $1}')
    echo "代码回滚后MD5值为:$direct_old_MD5"
}
 
fun_rollback_direct_restart(){
    #$direct_DIR/restart.sh
    systemctl restart httpd
    systemctl restart nginx
    echo "--------后端服务重启成功"
}

# 验证端口存活状态 
fun_chk_direct_port2(){
    PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'`
    PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'`
    for port in $PORT1 $PORT2;do
        echo "The port is:$port------端口监听正常"
    done
}

#####################adx代码部署########################################
fun_copy_adx__code(){
    mv $ADX_DIR/$adx_new_version  $ADX_DIR/bak/${adx_new_version}_$(date +"%F-%T")
        echo "-----上一个版本已经移动到备份目录"
    cp /data/$adx_new_version  $ADX_DIR  && echo "-----代码复制成功!!!"
}
 
fun_chk_adx_code(){
    if [[ "$ADX_NEW_MD5" == "$ADX_CHK_MD5" ]];then
        echo "-----代码校验成功" && echo "代码部署成功后MD5值为:$ADX_NEW_MD5"
    else
            echo "-----代码校验失败" && exit
    fi
}
 
fun_deploy_adx_restart(){
    #$ADX_DIR/restart.sh
    systemctl restart httpd
    systemctl restart nginx
    echo "后端服务已经启动!!!"
}
 
# 验证端口存活状态
fun_chk_adx_port1(){
    PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'`
    PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'`
      for port in $PORT1 $PORT2;do
          echo "The port is:$port------监听的端口正常启动"
      done
}
 
###################################adx代码回滚###########################
fun_rollback_adx_code(){
    cd $ADX_DIR/bak
    adx_old_version=$(ls -l  |tail -n1 | awk '{print $9}')
    mv $ADX_DIR/${adx_new_version} $ADX_DIR/bak/${adx_new_version}_$(date +"%F-%T")
    mv $ADX_DIR/bak/${adx_old_version} $ADX_DIR/${adx_new_version}
    echo "------旧版本代码移动成功"
    adx_old_MD5=$(md5sum $ADX_DIR/${adx_new_version} |  awk '{print $1}')
    echo "代码回滚后MD5值为:$adx_old_MD5"
}
 
fun_rollback_adx_restart(){
    #$ADX_DIR/restart.sh
    systemctl restart httpd
    systemctl restart nginx
    echo "--------后端服务已经启动"
}

# 验证端口存活状态
fun_chk_adx_port2(){
    PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'`
    PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'`
    for port in $PORT1 $PORT2;do
        echo "The port is:$port-------端口监听正常"
    done
}
 
case $1 in
  direct_deploy)
     fun_copy_direct_code
     fun_chk_direct_code
     fun_deploy_direct_restart
     fun_chk_direct_port1
     ;;
  direct_rollback)
     fun_rollback_direct_code
     fun_rollback_direct_restart
     fun_chk_direct_port2
     ;;
  adx_deploy)
     fun_copy_adx__code
     fun_chk_adx_code
     fun_deploy_adx_restart
     fun_chk_adx_port1
     ;;
  adx_rollback)
    fun_rollback_adx_code
    fun_rollback_adx_restart
    fun_chk_adx_port2
     ;;
esac
#!/bin/bash
# Auth:   liupengju
# date:   2020-06-22
# TEL:    xxxxx
# 部署完成校验

####验证adserver版本号#############
fun_chk_adx_version(){
    ansible adx -m shell -a 'md5sum   /gnome/adx/gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}'|sort |head -n62 |tee version_adx |cat -n
    adx_version=$(ansible adx -m shell -a 'md5sum /gnome/adx/gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}'|sort |tail -n62 |uniq -c|awk '{print $2}')
    echo -e "\e[1;32m新发布的版本号为:$adx_version\e[0m"
    version1=$(diff metadata  version_adx)
        if [ -z $version1 ];then
           echo -e "\e[1;32m代码部署成功 \e[0m"
    else
           echo -e "\e[1;31m请检查错误  \e[0m"
    fi
}
 
####验证directAd版本号############
fun_chk_direct_version(){  
        ansible adx -m shell -a ' md5sum   /gnome/directAd/direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar'|awk '{print $1}'|sort |head -n62 |tee version_direct |cat -n
        direct_version=$(ansible adx -m shell -a 'md5sum /gnome/directAd/direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar'|awk '{print $1}'|sort |tail -n62 |uniq -c|awk '{print $2}')
        echo -e "\e[1;32m新发布的版本号为:$direct_version\e[0m"
        version2=$(diff metadata  version_direct)
        if [ -z $version2 ];then
           echo -e "\e[1;32m代码部署成功 \e[0m"
        else
           echo -e "\e[1;31m请检查错误  \e[0m"
        fi
}
 
###验证8080端口状态###############
fun_chk_8080_port(){
    chk_ip_8080=$(ansible adx -m shell -a ' netstat -ntulp |grep 8080' |awk '{print $1}' |egrep "[0-9]+\.*" |sort | tee data_8080.bak |cat -n)
    DIR_8080=$(diff metadata  data_8080.bak)
    if [ -z $DIR_8080 ];then
           echo -e "\e[1;32m端口检查成功,端口号:8080 \e[0m"
    else
           echo -e "\e[1;31m请检查错误  \e[0m"
    fi
}
 
####验证8182端口状态#############
fun_chk_8182_port(){
    chk_ip_8182=$(ansible adx -m shell -a ' netstat -ntulp |grep 8182' |awk '{print $1}' |egrep "[0-9]+\.*" |sort |tee data_8182.bak |cat -n)
    DIR_8182=$(diff metadata  data_8182.bak)
    if [ -z $DIR_8182 ];then
        echo -e "\e[1;32m端口检查成功,端口号:8182 \e[0m"
    else
        echo -e "\e[1;31m请检查错误  \e[0m"
    fi
}
 
case $1 in
     adx)
       fun_chk_adx_version
       fun_chk_8080_port
       fun_chk_8182_port
       ;;
     direct)
       fun_chk_direct_version
       fun_chk_8080_port
       fun_chk_8182_port
       ;;
esac

环境变量

bash配置文件

  • 按生效范围划分,存在两类:
全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置:
~/.bash_profile
~/.bashrc
交互式登录:
(1)直接通过终端输入账号密码登录
(2)使用“su - UserName” 切换的用户
执行顺序:/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
非交互式登录:
(1)su UserName
(2)图形界面下打开的终端
(3)执行脚本
(4)任何其它的bash实例
执行顺序: /etc/profile.d/*.sh --> /etc/bashrc --> ~/.bashrc
  • 按功能划分,存在两类:
profile类:
为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:
(1) 用于定义环境变量
(2) 运行命令或脚本
bashrc类:
为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
(1) 定义命令别名和函数
(2) 定义本地变量

注意:

(1)命令中定义的特性,譬如变量和别名,仅对当前shell进程有效

(2)配置文件中定义的特性,只对随后新启动的shell进程有效,想让通过配置文件定义的特性立即生效,可以在命令行重复定义一次,或者让shell进程重读配置文件

  • source /PATH/FROM/CONF_FILE
  • . /PATH/FROM/CONF_FILE
  • 用户退出任务~/.bash_logout

    在退出登录shell时运行。

    用于

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

示例:当退出登录时,删除文件/data/test

[root@centos7 ~]# vim ~/.bash_logout
# ~/.bash_logout
rm -rf /data/test

环境变量配置文件

系统开机启动,环境变量配置文件的执行顺序为:
/etc/profile -> (~/.bash_profile | ~/.bash_login | ~/.profile) -> ~/.bashrc -> /etc/bashrc -> ~/.bash_logout

(1)/etc/profile:
此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. 
并从/etc/profile.d目录的配置文件中搜集shell的设置(*.sh文件)。
(2)~/.bash_profile: 
每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!
默认情况下,他设置一些环境变量,执行用户的.bashrc文件。
(3)~/.bashrc: 
该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,
该文件被读取,调用/etc/bashrc。
(4)/etc/bashrc: 
为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取。
(5)~/.bash_logout:
当每次退出系统(退出bash shell)时,执行该文件。可用于 创建自动备份 和 清除临时文件
注意:/etc/profile 中设定的变量(全局)的可以作用于任何用户,
      而~/.bashrc 等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.
	  ~/.bash_profile 是交互式 login 方式进入 bash 时运行的; 
      ~/.bashrc 是交互式 non-login 方式进入 bash 时运行的;
	  通常二者设置大致相同,通常前者会调用后者,以便统一配置用户环境。

其他配置文件

~/.bash_history 
是bash shell的历史记录文件,里面记录了你在bash shell中输入的所有命令。
环境变量:
HISSIZE  设置在历史记录文件里保存记录的条数。
/etc/enviroment 是系统的环境变量,与登录用户无关,例如登录提示语言,没必要修改
/etc/profile    是所有用户的环境变量,与登录用户有关,变量冲突以用户的环境变量为准
系统应用程序的执行与用户环境可以是无关的,但与系统环境是相关的.

系统开机时,读取的顺序是:?
/etc/enviroment -> /etc/profile
登陆系统时,用户shell建立环境,读取的顺序是:
/etc/profile -> /etc/enviroment 

永久修改环境变量(编辑环境变量配置文件)

示例:
在文件末尾添加环境变量:
export PATH=$PATH:/opt/lamp/mysql/bin
export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib

使环境变量生效

source /etc/profile   通知bash进程重新读取配置文件
. /etc/profile        执行配置文件
注销系统

(同名的环境变量,后写入的起作用)

查看环境变量

set                  显示当前shell的变量,包括当前用户的变量(内部命令)
export               显示当前导出成用户变量的shell变量(内部命令)
env                  显示当前用户的环境变量(外部命令)
declare -x
printenv

echo $PATH  显示当前导出成用户变量shell的变量,包括当前用户的变量中的PATH

临时设置或显示环境变量

注意:使用export或declare设置的变量都是临时变量,也就是说退出当前的shell,为该变量定义的值便不会生效

export [-fnp] [NAME]=[变量设置值]    当前导出成用户变量的shell变量
       -f                 代表[NAME]中为函数名称。
       -n                 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
       -p                 列出所有的shell赋予程序的环境变量。
Examples:
export MYENV=7             # 定义环境变量并赋值
export LANG=zh_CN.UTF-8    # 临时修改语言环境字符集中文
declare [-fFirx] [-p] [name[=value]]    显示所有shell变量(与 typeset 相同)
	    +/-       "-"可用来指定变量的属性,"+"则是取消变量所设的属性;
		  -p      将显示每个[name]的属性和值    
          -f      仅显示函数;
          -r      将[name]设置为只读;
          -x      指定的变量会成为用户环境变量,可供shell以外的程序来使用;env |grep [name]
          -i      [value]可以是数值,字符串或运算式。
		  -F      包含-f,禁止显示函数定义;只有函数名和属性会被显示
Examples:
declare -x MYENV=7                   # 定义环境变量并赋值
declare -i number=$RANDOM*100/32767  # 设置0~100之间的随机数变量number

显示和设置shell中的行为选项:shopt(向下兼容set)

shopt    列出当前shell中只能由shopt设置的选项
         cdspell       自动改正cd命令参数中的小错误
         hostcomplete  以@开头时,按tab键可进行主机名的自动完成
         dotgblob      以点开始的文件名被包含在路径名扩展中
         mailwarn      显示邮件警告信息
shopt -o 列出可由set命令设置的选项
         emacs         进入emacs编辑模式
         vi            进入vi编辑模式
         ignoreeof     不允许单独使用Ctrl+D退出的用法,要使用exit。与IGNOREEOF=10等价
         noclobber     不允许重定向覆盖已存在文件
         noglob        不允许扩展文件名通配符
         nounset       使用未定义的变量时给出错误
      -p 显示可设置选项及当前取值
      -s 设置每一选项为on
      -u 设置每一选项为off
      -q 不输出信息

临时设置shell变量:set

set               显示所有shell变量
    -a            标示已修改的变量,导出至环境变量。echo $[name]
    -b            使被中止的后台程序立刻回报执行状态。
    -C            重定向所产生的文件无法覆盖已存在的文件。
    -d            Shell预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用-d参数可取消。
    -e            若指令传回值不等于0,则立即退出shell。等同 set –o errexit
    -f            取消使用通配符。
    -h            自动记录函数命令的所在位置。hash下来
    -H,--history  可利用"!"加<指令编号>的方式来执行history中记录的指令。
    -k            指令所给的参数都会被视为此指令的环境变量。
    -l            记录for循环的变量名称。
    -m            使用监视模式。可以通过Job control来控制进程的停止、继续,后台或者前台执行等。
    -n            只读取指令,而不实际执行。
    -p            启动优先顺序模式。
    -P            启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接。
    -t            执行完随后的指令,即退出shell。
    -u            当执行时使用到未定义过的变量,则显示错误信息。等同 set –o nounset
    -v            显示shell所读取的输入值。
    -x            执行指令后,会先显示该指令及所下的参数
    -i,--interactive-comments  交互式 shell ,shell 脚本中默认关闭
    -B,braceexpand             大括号扩展命令,默认开启

删除shell变量:unset

unset [-f] [-v] [name ...]  删除shell变量 name
      -f                    仅删除函数
      -v                    仅删除变量

常用的环境变量

PATH

决定了shell将到哪些目录中寻找命令或程序,预设可执行文件或命令的搜索路径。
$PATH=路径1:路径2:...:路径n

RANDOM

0~32767之间的随机整数

HISTSIZE

设置在历史记录文件里保存记录的条数。

HISTFILE

指定历史文件,默认为~/.bash_history

HISTFILESIZE

命令历史文件记录的条数

HISTTIMEFORMAT

HISTTIMEFORMAT=“%F %T “ 显示时间

HISTIGNORE

HISTIGNORE=“str1:str2*:… “ 忽略str1str2开头的历史

HISTCONTROL

HISTCONTROL=            记录方式
            ignoredups  默认,忽略重复的命令,连续且相同为“重复”
            ignorespace 忽略所有以空白开头的命令
            ignoreboth  相当于 ignoredups,ignorespace 的组合
            erasedups   删除重复命令

CDPATH

cd 相对路径进入目录时,寻找匹配目录的目录

PWD

当前目录路径,自动更新

OLDPWD

上一次目录路径,自动更新

LANG

系统语言

LANG="en_US.UTF-8"
LANG="zh_CN.UTF-8"
Cent OS 6.x 配置文件
/etc/sysconfig/i18n
Cent OS 7.x 配置文件:
/etc/locale.conf
localectl set-locale LANG='en_US.UTF-8'

PS1

提示符变量,用于设置提示符格式,设置一级shell提示符环境变量,个性化终端命令行提示符的信息或格式。

PS1 基本提示符 例如:export PS1="[\u@\h \w]\$ "
    \d :代表日期,格式为weekday month date,例如:Wed Dec 12
    \H :完整的主机名称。例如:hostname是debian.linux
    \h :仅取主机的第一个名字,如上例,则为debian,.linux则被省略
    \t :显示时间为24小时格式,如:HH:MM:SS
    \T :显示时间为12小时格式
    \A :显示时间为24小时格式:HH:MM
    \u :当前用户的账号名称 如:root
    \v :BASH的版本信息  如:3.2
    \w :完整的工作目录名称。家目录会以~代替 如显示/etc/default/
    \W :利用basename取得工作目录名称,只会列出最后一个目录。如上例则只显示default
    \# :下达的第几个命令
    \$ :提示字符,如果是root时,提示符为:# ,普通用户则为:$ 

PS2

用于设置二级shell提示符环境变量。

终端命令行的相关设置:stty

stty     显示终端命令行的相关设置
     -a  打印终端所有当前设置
	    iuclc              禁止输出大写,前加-开启
		olcuc              禁止输出小写,前加-开启
		size               打印出终端的行数和列数
		stty eof "string"  修改Ctrl+D快捷键
		echo               打开回显,前加-关闭
		igncr              忽略回车符,前加-关闭

LOGNAME:当前用户的登录名
LANGUGE:语言相关的环境变量,多语言可以修改此环境变量
MAIL:当前用户的邮件存放目录
BASH:记录当前bash shell的路径。
BASH_SUBSHELL:记录当前子shell的层次。BASH_SUBSHELL是从0开始计数的整数。
BASH_VERSINFO:是一个数组包含六个元素,这六个元素显示bash的版本信息。
BASH_VERSION:显示shell版本的信息。
DIRSTACK:记录了栈顶的目录值,初值为空。
GLOBLGNORE:是由冒号分割的模式列表,表示通配时忽略的文件名集合。
GROUPS:记录当前用户所属的组。
HOME:记录当前用户的家目录,由/etc/passwd的倒数第二个域决定。
HOSTNAME:记录主机名。
HOSTTYPE和MACHTYPE:都是记录系统的硬件架构。
IFS:用于设置指定shell域分隔符,默认情况下为空格。
OSTYPE:记录操作系统类型。
PPID:是创建当前进程的进程号,即当前进程的父进程号
REPLY:REPLY变量与read和select有关。
SECONDS:记录脚本从开始到结束耗费的时间。
SHELL:显示当前所用的shell
SHELLOPTS:记录了处于“开”状态的shell选项列表,它只是一个只读变量。
SHLVL:记录了bash嵌套的层次,一般来说,我们启动第一个Shell时。$SHLVL=1。如果在这个Shell中执行脚本,脚本中的$SHLVL=2。
TMOUT:用来设置脚本过期的时间,比如TMOUT=3,表示该脚本3秒后过期。
UID: 已登用户的ID
USER:显示当前用户名字

用于查询与修改系统的本地化(locale)与键盘布局的设置:localectl

localectl list-locales      列出所有可用的 locale

设置系统的本地化环境变量(可以一次设置多个)
例如 "LANG=zh_CN.utf8", "LC_MESSAGES=en_US.utf8" 等等。

localectl set-locale LANG=zh_CN.UTF-8   --- centos7修改字符集信息

自定义环境变量

export PS1='\[\e[36;1m\][\u@\h \W]\$ \[\e[1;37m\]'

脚本示例

bc.sh

四则运算计算器

#!/bin/bash

# 1
if [[ $# == 2 ]];then
  echo $1+$2=$(($1+$2))
  echo $1-$2=$(($1-$2))
  echo $1*$2=$(($1*$2))
  echo $1/$2=$(($1/$2))
  exit
fi
# 2
if [[ $# == 0 ]];then
  read -p "请输入参数1: " var1
  read -p "请输入参数2: " var2
  if [[ $var1 == "" || $var2 == "" ]];then
# 3
    var1=10
    var2=10
  fi
  echo $var1+$var2=$(($var1+$var2))
  echo $var1-$var2=$(($var1-$var2))
  echo $var1*$var2=$(($var1*$var2))
  echo $var1/$var2=$(($var1/$var2))
  exit
fi

echo 请输入2个参数!
rabbit_chook.sh

鸡兔同笼脚本:

输入头数:35 输入脚个数:94

输出x只兔、y只鸡

算法:

x+y=35(头数) 2x+4y=94(脚数)

94/2=47-35=12(兔子个数)35-12=23(鸡个数)

let

#!/bin/bash
read -p "please input head num: " head
read -p "please input floot num: " floot
let x=$floot/2-$head
let y=$head-$x
echo "rabbit=$x;chook=$y"

expr

#!/bin/bash
read -p "please input head num: " head
read -p "please input floot num: " floot
x=`expr $floot / 2 - $head`
y=`expr $head - $x`
echo "rabbit=$x;chook=$y"

$[ ]

#!/bin/bash
read -p "please input head num: " head
read -p "please input floot num: " floot
rabbit=$[(floot-2*head)/2]
chook=$[head-rabbit]
echo "rabbit=$rabbit;chook=$chook"

$(())

#!/bin/bash
read -p "please input head num: " head
read -p "please input floot num: " floot
rabbit=$((floot/2-head))
chook=$((head-rabbit))
echo "rabbit=$rabbit;chook=$chook"
9x9.sh
#!/bin/bash
for a in {1..9};do
    for b in `seq 1 $a`;do
    let c=$a*$b ;echo -e "${a}x${b}=$c\t\c"
    done
    echo   
done
[root@oldboyedu-lnb ~]# sh 9x9.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
chess_board.sh
#!/bin/bash
red="\033[1;41m  \033[0m"
yellow="\033[1;43m  \033[0m"
 
for i in {1..8};do
        if [ $[i%2] -eq 0 ];then
                for i in {1..4};do
                        echo -e -n "$red$yellow";
                done
                echo
        else
                for i in {1..4};do
                        echo -e -n "$yellow$red";
                done
                echo
        fi
done
color_isosceles_triangle.sh
#!/bin/bash
read -p "Please input a num: " num
if [[ $num =~ [^0-9] ]];then
        echo "input error"
else
        for i in `seq 1 $num` ;do
                xing=$[2*$i-1]
                for j in `seq 1 $[$num-$i]`;do
                        echo -ne " "
                done
                for k in `seq 1 $xing`;do
                        color=$[$[RANDOM%7]+31]
                        echo -ne "\033[1;${color};5m*\033[0m"
                done
                echo
        done
fi
[root@oldboyedu-lnb ~]# sh color_isosceles_triangle.sh
Please input a num: 5
    *
   ***
  *****
 *******
*********
systeminfo.sh

显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小

#! /usr/bin/sh
hn=`uname -nr | awk '{print $1}'`
ipv4=`ip route | grep -o 'src.*$' | awk '{print $2}'`
os=`cat /etc/redhat-release`
ke=`uname -nr | awk '{print $2}'`
cpu=`lscpu | grep 'Ven.*' | awk '{print $3}'`
me=`free -h | tail -1 | awk '{print $2}'`
sda=`fdisk -l | head -2 | awk '{print $3}' | tail -1`
echo "hostname:$hn"
echo "IPv4:$ipv4"
echo "OS version:$os"
echo "Kernel version:$ke"
echo "CPU model:$cpu"
echo "SWAP size:$me"
echo "Disk size:$sda G"
sysinfo.sh

带颜色显示当前主机系统信息,包括系统版本,内核版本,硬盘使用率,主机名

#!/usr/bin/env bash
# ------------------------------------------
# Filename: hello.sh
# Revision: 1.0
# Date: 2020/10/21
# Author: wu
# Email: wu@gmail.com
# Description: This is the first script
# Copyright (C): 2020 All rights reserved
# License: GPL
# ------------------------------------------
# RED is content color
# REDD is content color
RED="\033[1;31m"                                                     
REDD="\033[0m"
echo -e OS Version is $RED`cat /etc/centos-release`$REDD
echo -e Disk used is $RED`df | grep /dev/sd | tr -s " " |cut -d" " -f5 | sort -nr | head -n1`$REDD
echo -e Kernel is $RED`uname -r`$REDD
echo -e "Host name  is  $RED`hostname`"$REDD
create_sh.sh

编写生成脚本基本格式的脚本,包括作者,联系方式,版本,时间,描述等

#!/usr/bin/env bash
# ------------------------------------------
# Filename: hello.sh
# Revision: 1.0
# Date: 2020/10/21
# Author: wu
# Email: wu@gmail.com
# Description: create script and start
# Copyright (C): 2020 All rights reserved
# License: GPL
# ------------------------------------------
if [ $# -lt 1 ]; then
     echo '至少应该给一个参数!'
     exit 1
fi
cat > $1 <<EOF
#!/usr/bin/env bash
# ------------------------------------------
# Filename: $1
# Revision: 1.0
# Date: `date +%F`
# Author: wu
# Email: wu@gmail.com
# Description: This is the $1 script
# Copyright (C): 2020 All rights reserved
# License: GPL
# ------------------------------------------

EOF
chmod +x $1
vim + $1
backup.sh

每日将/etc/目录备份到/backup/etcYYYYmm-dd中

#! /usr/bin/sh
if [[ ! -d '/backup' ]]; then
    mkdir /backup
fi
cp -af /etc/ /backup/etc$(date -d 'today' +'%Y%m-%d')
echo "/etc/已备份"
chmod u+x backup.sh
echo '0 9 * * * root /root/backup.sh #每天早上九点执行backup.sh该文件' >> /etc/crontab
user_no_nologin.sh

统计出/etc/passwd文件中,默认shell为非/sbin/nologin的用户个数,并将用户都显示出来

#! /usr/bin/sh
getent passwd | grep -v /sbin/nologin
user_max_uid.sh

查出用户UID最大值的用户名、UID及shell类型

#! /usr/bin/sh
getent passwd | sort -t: -k3 -nr | head -n1
memory

显示占用系统内存最多的进程

#! /usr/bin/sh
ps aux | sort -k4 -nr | head -n1
disk.sh

显示当前硬盘分区中空间利用率最大的值

#! /usr/bin/sh
echo `df|egrep -o '[0-9]{1,3}%'|sort -nr|head -1`
links.sh

显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序

#! /usr/bin/sh
echo `netstat -nt | awk '{print $4}' | egrep '[0-9.]' | cut -d: -f1 | sort | uniq -c | sort -nr`
sumid.sh

计算/etc/passwd文件中的第10个用户和第20用户的UID之和

#! /usr/bin/sh
echo `cut -d: -f3 /etc/passwd | sed -n '10p;20p' | awk '{sum += $1};END {print sum}'`
sumspace.sh

传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和

#! /usr/bin/sh
echo `cat $1 $2 | sed -n '/^$/p' | wc -l`
sumfile.sh

统计/etc, /var, /usr 目录中共有多少个一级子目录和文件

#! /usr/bin/sh
# c is Number of parameters
c=3
echo "`ll /etc /var /usr | wc -l` -($c*2+($c-1))" | bc
argsnum.sh

接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数

#! /usr/bin/sh
if [ $# -lt 1 ]; then
     echo '至少应该给一个参数!'
     exit 1
fi
cat $1 | sed -n '/^$/p' | wc -l
hostping.sh

接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”

#! /usr/bin/sh
if [ $# -lt 1 ]; then
     echo '至少应该给一个参数!'
     exit 1
fi
if ping -c1 -W2 $1; then
     echo '该IP地址可访问'
else
     echo '该IP地址不可访问'
fi
#! /usr/bin/sh
ping -c1 -W2 $1
[ $? -eq 0 ] && echo '该IP地址可访问' || echo '该IP地址不可访问'
hostsping.sh

使用for和while分别实现:

测试192.168.0.0/24网段内,地址是否能够ping通,若ping通则输出"success!",若ping不通则输出"fail!"

for循环实现

#!/bin/bash
NUM=`seq 1 254`

for IP in ${NUM};do
   HOST_IP="192.168.0.${IP}"
   ping -c 2 -w 3 ${HOST_IP} &> /dev/null
   if [ $? -eq 0 ];then
      echo "success!" && echo ${HOST_IP} >> /tmp/ip_success.txt
   else
      echo "fail!"  && echo ${HOST_IP} >> /tmp/ip_fail.txt
   fi
done

while循环实现

#!/bin/bash
IP=192.168.0
NUM=1

while [ $NUM -lt 255 ];do
  ping -c2 -W2 $IP.$NUM &> /dev/null
  if [ $? -eq 0 ];then
     echo "$IP.$NUM success!"
  else
     echo "$IP.$NUM fail!"
  fi
  let NUM++
done
checkdisk.sh

工作日,每10分钟执行一次,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满

#! /usr/bin/sh
sd=`df | egrep -o '[0-9]{1,3}%' | sort -nr | head -1 | egrep -o '[0-9]{1,3}'`
inode=`df -ih | egrep -o '[0-9]{1,3}%' | sort -nr | head -1 | egrep -o '[0-9]{1,3}'`
if [ $sd -gt 80 -o $inode -gt 80 ]; then
     wall 'Disk space will be full!'
else
     echo '磁盘分区空间和inode使用率正常'
fi
echo "*/10 * * * 1-5 /usr/bin/bash /server/script/checkdisk.sh" >> /var/spool/cron/root

每10分钟执行一次,检查磁盘分区空间、CPU和内存使用率,如果超过80%,就发广播警告空间将满

#! /usr/bin/sh
sd=`df | egrep -o '[0-9]{1,3}%' | sort -nr | head -1`
cpu=`cat /proc/stat|grep '^cpu[0-9]'|awk '{used+=$2+$3+$4;tolused+=$2+$3+$4+$5+$6+$7+$8} END{printf ("%.0f",used/tolused*100)}'`
mem=`free -t | tail -1 | awk '{printf ("%.0f",$3/$2*100)}'`

if [ ${sd%\%} -gt 80 ]; then
    wall 'Disk space will be full!'
elif [ $inode -gt 80 ]; then
    wall 'CPU space will be full!'
elif [ $mem -gt 80 ]; then
    wall 'Mem space will be full!'
fi
echo "*/10 * * * * /usr/bin/bash /server/script/checkdisk.sh" >> /var/spool/cron/root
checkip.sh

编写脚本/server/script/checkip.sh,每5分钟检查一次,如果发现通过ssh登录失败次数超过10次,自动将此远程IP放入Tcp Wrapper的黑名单中予以禁止防问

写法一:

#!/bin/bash

while true;do
  awk '/Failed/{ip[$(NF-3)]++}
  END {
        for(i in ip)
        {
          if(ip[i]>=2)
          {
            system("echo sshd:"i" >> /etc/hosts.deny")
          }
        }
      }' /var/log/ssh.log
  sleep 5m
done

写法二:

#!/bin/bash

while true;do
# 收集IP出现失败的次数
  ipnum=`awk '/Failed/{print $(NF-3)}'  /var/log/ssh.log  |uniq -c |awk '{print $1}'` 
# 统计IP地址
  ip=`awk '/Failed/{print $(NF-3)}'  /var/log/ssh.log  |uniq -c |awk '{print $2}'`
# 当IP地址出现次数大于等于3,将IP地址拉入黑名单。
  if [ $ipnum -ge 3 ] ;then
     echo "sshd:$ip" >> /etc/hosts.deny
  fi
  sleep 5m
done

执行方式:sleep 5分钟或者加入计划任务,每5分钟执行一次

$ crontab -l
*/5 * * * *  /server/script/checkip.sh
checkDOS.sh

解决DOS攻击生产案例: 根据web日志或者或者网络连接数,监控当某个IP并发连接数或者短时内PV达到100,调用防火墙命令封掉对应的IP,监控频率每隔5分钟。防火墙命令为: iptables -A INPUT -s IP -j REJECT

#!/bin/bash
# environment variable 
source /etc/profile 
iplist=$(ss -tan | awk -F "[[:space:]]+|:" '/ESTAB/{ip[$(NF-2)]++}END{
   for(i in ip)
   {print i,ip[i]}
  }' | awk '{if($2>100)print $1}')
for ip in $iplist
do
  iptables -I INPUT -s $ip -j DROP
  echo "$ip is drop!"
done

加入计划任务,每5分钟执行一次

$ crontab -l
*/5 * * * *  /server/script/checkDOS.sh
per.sh

判断当前用户对指定参数文件,是否不可读并且不可写

#! /usr/bin/sh
if [ -r $1 -a -w $1 ]; then
    echo "$1并非不可读并且不可写"
else
    echo "$1不可读并且不可写"
fi
excute.sh

判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件

#! /usr/bin/sh
if [[ -f $1 && `echo "$1" | cut -d. -f2` = sh ]]; then
    chmod a+x $1
else
    echo "$1非脚本文件"
fi
nologin.sh

禁止普通用户登录系统

#! /usr/bin/sh
echo "System maintenance, no login." > /etc/nologin
login.sh

允许普通用户登录系统

#! /usr/bin/sh
\mv /etc/nologin /root/nologin
createuser.sh

使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息

#! /usr/bin/sh
if id $1 &> /dev/null; then
    id $1
else
    useradd $1
    id $1
fi

create_user_home.sh

接受二个位置参数,magedu和/www,判断系统是否有magedu,如果没有则自动创建magedu用户,并自动设置家目录为/www

#!/bin/bash
id $1 &> /dev/null
if [ $? -eq 0 ]; then
    id $1
else
    useradd -d $2 $1
    id $1
fi
yesorno.sh

提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息

#! /usr/bin/sh
read -p "Please input:yes/no: " name
if [ $name = yes ]; then
    echo 'You input: yes'
elif [ $name = no ]; then
    echo 'You input: no'
else
    echo 'You input: other info'
fi
filetype.sh

判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)

#! /usr/bin/sh
if [ $# -lt 1 ]; then
    echo '至少应该给一个参数!'
    exit 1
elif [ -L $1 ]; then
    echo '$1 is a Symbolic link'
elif [ -d $1 ]; then
    echo '$1 is a directory'
elif [ -f $1 ]; then
    echo '$1 is a normal file'
else
    echo '$1 is a other file type'
fi
checkint.sh

判断用户输入的参数是否为正整数

#! /usr/bin/sh
if [ $# -lt 1 ]; then
    echo '至少应该给一个参数!'
    exit 1
fi
if expr $1 + 1 &> /dev/null; then
    if [ $1 -ge 0 ]; then
    	echo "$1是正整数"
    fi
else
    echo "$1不是正整数"
fi
#! /bin/bash
if [ $# -gt 1  ];then
  echo "只能判断一个参数!"
  exit
fi
if [[ -z $1 ]];then
  echo "输入为空"
  exit
fi
if [[ $1 =~ ^[0-9-]?[0-9]+$ ]];then
  echo "输入为整数"
  exit
fi
echo "输入不是整数"
pathadd.sh

让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin

#! /usr/bin/sh
echo "export PATH=$PATH:/usr/local/apache/bin" >> /etc/profile
source /etc/profile
rootlogin.sh

用户 root 登录时,将命令指示符变成红色,并自动启用如下别名:
rm='rm –i'
cdnet='cd /etc/sysconfig/network-scripts/'
editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0'
editnet='vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33' (如果系统是CentOS7)

#! /usr/bin/sh
cat <<EOF>> /etc/profile
export PS1="\e[31m[\u@\h \W]\$ \e[0m"
alias rm='rm –i'
alias cdnet='cd /etc/sysconfig/network-scripts/'
alias editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0'
EOF
loginissue.sh

任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”

#! /usr/bin/sh
echo 'echo -e "\e[1;31mHi,dangerous!\e[0m"' >> /etc/profile
reset.sh

用户的环境初始化:包括别名,登录提示符,vim的设置,环境变量等

#! /usr/bin/sh

selinux.sh

开启或禁用SELinux

#!/bin/bash

if [[ $1 =~ ^[Oo][Nn]$ ]]; then
  sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config &>/dev/null
  setenforce 1 &>  /dev/null 
elif [[ $1 =~ ^[Oo][Ff][Ff]$ ]]; then
  sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config &>/dev/null 
  setenforce 0 &>/dev/null
else
  echo "you are error,please input on or off !!!"
fi
checkganglia.sh

当客户机数量不为10的时候,进行报错;当客户机ganglia服务没有启动时,进行报错,并且筛选出所有没有启动ganglia的客户机

#!/bin/bash
DATE=`date +%Y%m%d`
filename="ganglia-${DATE}.log"
prefix="ganglia-${DATE}"
hosts=`grep test@ /tmp/log/ganglia/${filename} | wc -l`
pids=`grep gmond.pid /tmp/log/ganglia/${filename} | wc -l`
if [ ${hosts} != 10 ]
then
    echo "Some hosts are offline!" >> /tmp/log/ganglia/error-${DATE}.log
fi

if [ ${hosts} != ${pids} ]
then
    echo "Some ganglia services have stopped!" >> /tmp/log/ganglia/error-${DATE}.log
    cd /tmp/log/ganglia/
    csplit /tmp/log/ganglia/${filename} /test@/ -n2 -s {*} -f ${prefix} -b ".log.%02d"
    rm ${prefix}.log.00
    for file in /tmp/log/ganglia/${prefix}.log.*
    do 
    if [ -f "${file}" ]
    then
        #echo "${file} is file"
        if [ `grep gmond.pid ${file} | wc -l` == 0 ]
        then
            echo `grep test@ ${file}` >> /tmp/log/ganglia/error-${DATE}.log
        fi
    fi
    done
fi
[root@linuxcool ~]# crontab -l 
# 每天查看一下ganglia的状态,并保存到/tmp/log/ganglia目录
0 0 * * * /usr/bin/parallel-ssh -h /home/test/hosts.txt -t 30 -i 'ps aux | grep gmond' > /tmp/log/ganglia/ganglia-`date +\%Y\%m\%d`.log
# 自动筛选报错
10 0 * * * /bin/bash /home/test/checkganglia.sh

返回页首

posted @ 2021-01-08 07:49  原因与结果  阅读(301)  评论(1编辑  收藏  举报