首页 > 技术文章 > Shell脚本

strugger-0316 2021-02-19 02:13 原文

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/22
 # Author: liupenghui
 # Email: 15094034633@163.com
 # Description: This is the first script
 # Copyright: 2020 liu
 # 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 print env 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
tr -dc ‘a-zA-Z0-9’</dev/urandom|head -c8 
  • 增强型赋值:

+=, -=, *=, /=, %=
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 <<EOF、cat <<-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是可选的,有以下情况:

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

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

    简单命令,for语句,case语句,select命令,算法命令,在函数内的第一条命令。

    如果sigspec是ERR,那么arg指定的命令将会在任何简单命名执行完后返回值为非零值时执行,但是也有以下例外情况,arg命令不会执行,这些规则同样适用于errexit选项:

    如果执行失败的命令是紧跟在while或者until关键字之后的一组命令中的一部分时

    如果执行失败的命令是if测试语句的一部分时,是 && 和 ||连接的列表中的一部分时

    如果执行失败的命令的返回值是被取反过的(通过!操作符)

    如果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+\

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

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

18) SIGCONT :继续运行

19) 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。

函数递归示例

函数递归: 函数直接或间接调用自身 注意递归层数 递归实例: 阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!

n!=1×2×3×...×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!
函数递归示例 示例:
fact.sh 
#!/bin/bash 
fact() 
{ if [ $1 -eq 0 -o $1 -eq 1 ]; 
then echo 1 
else echo $[$1*$(fact $[$1-1])] fi } 
fact $1

展开命令行

  • 把命令行分成单个命令词

  • 展开别名

  • 展开大括号的声明({})

  • 展开波浪符声明(~)

  • 命令替换$()和``

  • 再次把命令行分成命令词

  • 展开文件通配(*、?、[abc]等等)

  • 准备I/0重导向(<、>)

  • 运行命令

防止扩展

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

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

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

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

分支

HEAD_KEYWORD parameters; BODY_BEGIN
  BODY_COMMANDS
BODY_END
  • 将HEAD_KEYWORD和初始化命令或者参数放在第一行;

  • 将BODY_BEGIN同样放在第一行;

  • 复合命令中的BODY_COMMANDS部分以2个空格缩进;

  • BODY_END部分独立一行放在最后;

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 -f 或 declare -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 就可以查看这个脚本中的函数说明了。

  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:   liupenghui
# date:   2020-10-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*:… “ 忽略str1,str2开头的历史

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:是创建当前进程的进程号,即当前进程的父进程号
RELY: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 '00 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

tr命令

tr [OPTION]... SET1 [SET2] 转换和删除字符
   -c –C --complement      取字符集的补集
   -d --delete             删除SET1的字符
   -s --squeeze-repeats    把连续重复的字符以单独一个字符表示
   -t --truncate-set1      将SET1转化为SET2
tr -d a < filename         删除文件filename标准输出中的a
tr -s '\n' < filename      删除文件filename标准输出中的空行
cat filename |tr a-z A-Z       将文件filename中标准输出的小写字母全部转换成大写字母
or
cat filename |tr [:lower:] [:upper:] 	
tr -cd '0-9a-zA-Z' < /dev/urandom | head -c8  # 生成8位随机数

收集文本统计数据wc

计数单词总数、行总数、字节总数和字符总数
可以对文件或STDIN中的数据运行
wc story.txt
39 237 1901 story.txt
行数 字数 字节数
常用选项
-l 只计数行数
-w 只计数单词总数
-c 只计数字节总数
-m 只计数字符总数
-L 显示文件中最长行的长度

uniq

uniq命令:从输入中删除前后相接的重复的行
uniq [OPTION]... [FILE]...
-c: 显示每行重复出现的次数
-d: 仅显示重复过的行
-u: 仅显示不曾重复的行
注:连续且完全相同方为重复
常和sort 命令一起配合使用:
sort userlist.txt | uniq -c

文本排序sort

把整理过的文本显示在STDOUT,不改变原始文件
sort [options] file(s)
常用选项
-r 执行反方向(由上至下)整理
-R 随机排序
-n 执行按数字大小整理
-f 选项忽略(fold)字符串中的字符大小写
-u 选项(独特,unique)删除输出中的重复行
-t c 选项使用c做为字段界定符
-k X 选项按照使用c字符分隔的X列来整理能够使用多次

bash如何展开命令行  

  • 把命令行分成单个命令词

  • 展开别名

  • 展开大括号的声明({})

  • 展开波浪符声明(~)

  • 命令替换$()和``)

  • 再次把命令行分成命令词

  • 展开文件通配(*、?、[abc]等等)

  • 准备I/0重导向(<、>)

  • 运行命令

防止扩展 

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

$echoYourcost:\$5.00
Yourcost:$5.00

加引号来防止扩展 •单引号'防止所有扩展 •双引号"也防止所有扩展,但是以下情况例外:

$(美元符号)-变量扩展
(反引号)-命令替换
\(反斜线)-禁止单个字符扩展,转义单个字符
!(叹号)-历史命令替换

1.简单的介绍shell 1) 为什么要使用shell shell的作用 a. 安装操作系统 手工方式安装

   自动化安装操作系统 
	     kickstart  底层shell脚本
		   cobbler    底层shell脚本

b. 初始化操作系统

	   SSH优化      关闭SElinux 防火墙放行需要的端口(80 443 22修改 10050) YUM源 时间同步 系统最大描述符 
	   内核参数优化 字符集优化 禁止开机自动启动 修改主机名称 (修改公司网卡名称)...
	   手动  命令行安全
	   写入shell脚本(常用)
  c. 安装服务 Nginx PHP MySQL Rsync等等...  针对不同的版本写入shell脚本自动安装
	d. 配置服务
	e. 启动服务 所有的服务底层的启动方式都是使用的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取出来 来判断是否运行

	f. 日志统计 查看程序运行的情况 统计我们需要的数据
	   日志切割 定时任务+脚本 
	   统计数据 定时任务+脚本  ---> 通过邮件发送给管理员
	   ELK 日志统计界面  py开发日志界面 py界面----> 数据库 <----数据   日志展示
g. 监控 监控服务 服务端口是否存在 服务是否存在 服务器的硬件资源使用情况 状态 日志 网络
   Zabbix	通过脚本统计---> 测试---> 添加到zabbix服务 (cacti监控流量 Nagios宽带运营商 IT公司)  
减少重复性的工作

2) 学习shell编程所用到的知识 必须熟练掌握

   a. vim编辑器 课下快捷键
   b. xshell crt
   c. shell基础命令60个左右  回顾基础命令
   d. 三剑客命令 grep sed awk

3) 如何学习shell编程

   a. 能读懂shell脚本---> 模仿---> 自己修改 ---> 自己写 ---> 熟练运用(基础命令) 重复性
   b. 有编程能力(写的越多 编程能力越高)
   c. 找一本适合自己的教材 或者 完善的文档 (脚本功能框架)
   e. 多练多操作
   f. 切记拿来直接使用,语句理解后在使用
  1. shell初步入门 1)什么是shell

      shell是命令解释器 负责翻译我们输入的命令 输入的ls pwd命令都是shell解释器给我们运行的
     交互式   我们输入命令,shell负责解释执行我们输入的命令,并且把结果输出到屏幕的过程称为交互式
    		  用户签退exit 关闭xshell shell终止
     非交互式 不和我们交互,读取存在到文本中的shell命令,读取到文件结尾,shell结束

    2)什么是shell脚本

      把可执行的命令统一放入到一个文本中,称为shell脚本 包含了 判断语句 循环语句 数组等

    3)脚本语言的种类

      编译型 c c++
     解释型 python ruby
     脚本型 python shell javascript
     其他语言: PHP html go
     问: 编译型语法是最快的吗 看优化
     面试题: Linux中默认的shell解释器是 bash
     shell和py的区别
     shell处理底层的能力较强 所有的服务都是shell编写 一键优化 一键安装 一键统计(awk)
     py主要作用可以写界面 自动化管理平台CMDB 功能需求

    4)书写脚本的规范

     a. 脚本存放固定的目录 统一管理 /server/scripts
     b. 脚本使用.sh结尾    让我们能识别是shell脚本
     c. 脚本命名 见名知其意 start_nginx.sh stop_nginx.sh
     d. 脚本内的开头使用解释器  #!/bin/bash 
     e. 脚本内的注释最好不用中文(可以用)
     f. 脚本内的成对的符号一次性书写完毕 语法书写完在写内容

    5)写第一个脚本 使用vim编辑test.sh 在屏幕上输出 Hellow World!

     [root@shell ~]# cat test.sh
     #!/bin/sh
     echo "Hello World!"
    

    执行脚本常用的三种方式: 父shell和子shell的区别 子shell可以继承父shell的变量 /etc/profile 变量 所有的子shell都可使用 使用bash或sh执行shell脚本 都是在子shell中运行的

    1. 使用sh或者bash方式运行 开启了一个子shell运行里面的内容

    [root@shell ~]# sh test.sh
      Hello World!
    
    1. 使用全路径方式执行脚本

    [root@shell ~]# /root/test.sh
    -bash: /root/test.sh: Permission denied
    [root@shell ~]# ./test.sh
    -bash: ./test.sh: Permission denied
     增加执行权限chmod
     [root@shell ~]# chmod +x test.sh 
     [root@shell ~]# ll
      total 4
      -rwxr-xr-x 1 root root 123 Oct 15 10:49 test.sh
      [root@shell ~]# /root/test.sh 
      Hello World!
      /bin/bash
      [root@shell ~]# ./test.sh
      Hello World!
      /bin/bash
    
    1. 使用. 或者source 在父进程中执行shell脚本

    [root@shell ~]# . test.sh 
    Hello World!
    /bin/bash
    [root@lb02 ~]# name=oldboy
    [root@shell ~]# echo $name
    oldboy
    

    不常用的执行方式

     [root@shell ~]# echo pwd|bash
     /root
     [root@shell ~]# sh < test.sh
     Hello World!
     /bin/bash
    
  2. shell常用的基础变量 1) 什么是变量

    系统中的变量: $PATH $LANG $BASH $PS1
      x=1 y=x+1 y=2
      x和y都是变量的名称 等号后面的是变量的值
    name=oldboy
    使用一个固定的值代表不固定的值(数字 字符串 命令)

    2) 环境变量分类

    环境变量 (全局变量)  国法  所有的shell都生效
    普通变量 (局部变量)  家规  只针对当前的shell生效  自定义的变量
    查看系统环境变量 env

    3) 按照生命周期划分

      临时生效 只是在当前shell中生效 关闭则失效 使用export直接定义即可
       永久生效 对当前系统所有shell生效 写入/etc/profile
       不加export 只对当前的shell生效
       加export   对当前的窗口的父shell和子shell生效

    4) 环境变量配置文件执行的顺序

    /etc/profile
    读取家目录下所有的环境变量文件
    .bash_profile 
    .bashrc
    /etc/bashrc
    

5) 定义环境变量 环境变量的名称定义: 字母数字下划线开头的组合 见名知其意 等号两端不能有空格 不允许数字开头

  oldboy_age=18    # 全小写
  OLDBOY_AGE=25	   # 全大写 系统环境变量都大写
  oldboy_Age=30    # 小驼峰语法
  Oldboy_Age=35    # 大驼峰语法
  变量值的定义:  数字 字符串 和命令

三种值: 字符串定义 变量值中间不允许有空格 如果有需要加引号

  [root@shell ~]# test='I am lizhenya'	
  [root@shell ~]# oldboy_age=45
  [root@shell ~]# test=`pwd`		# 定义命令
  [root@shell ~]# echo $test		
  /root
  [root@shell ~]# test=$(ls)		# 定义命令
  [root@shell ~]# echo $test
  [root@shell ~]# ls
  test.sh

PS: 注意赋值反引号还是双引号

  [root@shell ~]# date
  Thu Oct 15 11:46:55 CST 2020
  [root@shell ~]# time=`date`
  [root@shell ~]# echo $time
  Thu Oct 15 11:47:00 CST 2020
  [root@shell ~]# echo $time
  Thu Oct 15 11:47:00 CST 2020
  [root@shell ~]# time="date +%F+%M+%S"

6) shell脚本中特殊的位置变量

$0# 表示脚本的名称 如果全路径执行则带全路径 可以使用basename只获取名字
  $0
   [root@shell ~]# cat test.sh
     #!/bin/sh
     #print   hello world
     #Author  oldboy
     #date    20202020
     #version v1.0
       echo "Hello World!"
       echo $0
  [root@shell ~]# sh test.sh 
  Hello World!
test.sh
-------------------
$n # 代表了脚本的第n的参数 n为数字 如果是0呢 则为脚本名称 从1开始 从$9以后需要加{} 表示整体
-gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne 是否不等于
-lt 是否小于
-le 是否小于等于

$#   #表示脚本传参的个数
使用方法 判断传参的个数
[root@shell ~]# cat test.sh 
[ $# -ne 2 ] && echo "请输入两个参数" && exit
expr $1 + $2
[ $# -ne 2 ] && echo "请输入两个参数" && exit
echo $1
echo $2

$? # 判断上一条命令的结果是否正确 0为正确 非0失败

[root@shell ~]# cat ping.sh 
ping -c2 -W1  www.baiduaaaaa.com &>/dev/null
[ $? -ne 0 ] && echo "ping不通" || echo "通了"
$$ # 获取当前脚本的PID
[root@shell ~]# cat test.sh 
#!/bin/sh
#print   hello world
#Author  oldboy
#date    20202020
#version v1.0
echo $$ > /tmp/nging.pid
sleep 300

$$      # 获取当前脚本的PID
$#      # 表示脚本传参的个数
$n 		  # 代表了脚本的第n的参数 n为数字 如果是0呢 则为脚本名称 从1开始 从$9以后需要加{} 表示整体
$0      #脚本名称
$?      # 判断上一条命令的结果是否正确 0为正确 非0失败
$!      # 获取上一个在后台运行脚本的PID号 排错使用
$_		  # 获取当前命令行的最后一个参数 类似于esc .
$*		  # 获取脚本传参的所有参数 在循环体中 不加双引号和$@相同 加双引号 把所有的参数当做一个参数	
$@      # 获取脚本传参的素有参数 在循环体中 不加双引号和$*相同 加双引号 把所有的参数当做独立的
  1. 脚本传参的三种方式 1) 直接传参

    [root@shell ~]# cat test.sh 
    echo name=$1
    echo age=$2
    [root@shell ~]# sh test.sh  oldboy 18
    name=oldboy
    age=18
    

    2) 赋值传参

    [root@shell ~]# cat test.sh
    #!/bin/sh
    name=$1
    age=$2
    echo -e "$name\n$age"
    [root@shell ~]# sh test.sh oldboy 20
    oldboy
    20
    

    3) read 传参 read 变量名称 进行赋值 read -p "用户提示" 变量名称

[root@shell ~]# cat read.sh 
#!/bin/bash
read -p "请输入你的姓名: " name
echo "你输入的姓名是 $name"
[root@shell ~]# cat read.sh 
#!/bin/bash
read -p "请输入你的姓名和年龄: " name age
echo "你输入的姓名是: $name 年龄是: $age"
[root@shell ~]# sh read.sh
请输入你的姓名和年龄: oldboy 20
你输入的姓名是: oldboy 年龄是: 20
[root@shell ~]# cat read.sh
#!/bin/bash
read -p "请输入你的姓名: " name 
read -p "请输入你的年龄: " age
echo "你输入的姓名是: $name 年龄是: $age"
[root@shell ~]# sh read.sh
请输入你的姓名: oldboy
请输入你的年龄: 20
你输入的姓名是: oldboy 年龄是: 20
案例: 使用read传参的方式 修改主机名称为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 "通了"
重复赋值
[root@shell ~]# cat dir.sh
#!/bin/sh
dir1=/etc
dir2=/tmp
read -p "请输入需要备份的目录: " dir2
echo $dir1
echo $dir2
[root@shell ~]# sh dir.sh
请输入需要备份的目录: /opt
/etc
/opt
  1. 变量的子串及删除替换

    [root@shell ~]# test='I am oldboy'
    [root@shell ~]# echo $test
    I am oldboy
    [root@shell ~]# echo $test|awk '{print $2}'
    am
    [root@shell ~]# echo $test|cut -c3-4
    am
    

变量切片

nginx
[root@shell ~]# echo ${test:2:2}
am
[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
子串的删除替换
[root@shell ~]# echo ${url}
www.baidu.com	
[root@shell ~]# echo ${url}|awk -F "w." '{print $3 }'
baidu.com
[root@shell ~]# echo ${url}
www.baidu.com
[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
[root@shell ~]# test=%66
[root@shell ~]# echo $test
%66
[root@shell ~]# echo ${test#%}
66
[root@shell ~]# echo ${test##}
#66
[root@shell ~]# echo ${test#\#}
66
变量替换  
[root@shell ~]# echo $url www.baidu.com 
[root@shell ~]# echo $url|sed 's#www#WWW#g' WWW.baidu.com 
[root@shell ~]# echo $url www.baidu.com 
[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 ~]# expr 1 +1
expr: syntax error
[root@shell ~]# expr 10 - 5
5
[root@shell ~]# expr 10 * 5
expr: syntax error
[root@shell ~]# expr 10 \* 5
50
[root@shell ~]# expr 10 / 5
2
[root@shell ~]# expr length oldboy	# 统计字符串长度
6

2) $[] 只支持整数运算 不支持小数运算

[root@shell ~]# echo $[1+1]
2
[root@shell ~]# echo $[1+1.5]
-bash: 1+1.5: syntax error: invalid arithmetic operator (error token is ".5")
[root@shell ~]# echo $[1+10]
11
[root@shell ~]# echo $[1-10]
-9
[root@shell ~]# echo $[100*10]
1000
[root@shell ~]# echo $[100/10]
10
[root@shell ~]# #100*10=1000
[root@shell ~]# echo 100*10=1000
100*10=1000
[root@shell ~]# echo 100*10=$[100*10]
100*10=1000
[root@shell ~]# num1=100
[root@shell ~]# num2=10
[root@shell ~]# echo $num1*$num2
100*10
[root@shell ~]# echo $num1*$num2=$[$num1*$num2]
100*10=1000

3) $(()) 只支持整数运算 不支持小数运算 PS: $()执行命令 $(()) 数值运算 执行效率最高

  [root@shell ~]# echo $((1+1))
   2
  [root@shell ~]# echo $((1+1.5))
  -bash: 1+1.5: syntax error: invalid arithmetic operator (error token is ".5")
  [root@shell ~]# echo $((100-100))
  0
  [root@shell ~]# echo $((100*100))
  10000
  [root@shell ~]# echo $((100/100))
  1

4) let 只支持整数运算 不支持小数运算 语法格式:

let sum=1+1
输入结果: echo $sum
[root@shell ~]# let sum=1+1
[root@shell ~]# echo $sum
2
[root@shell ~]# let sum=100-100
[root@shell ~]# echo $sum
0
[root@shell ~]# let sum=100*100
[root@shell ~]# echo $sum
10000
[root@shell ~]# let sum=100/100
[root@shell ~]# echo $sum
1
[root@shell ~]# let sum=$num1+$num2
[root@shell ~]# echo $sum
110
[root@shell ~]# let i++
[root@shell ~]# echo $i
1
[root@shell ~]# let i++
[root@shell ~]# echo $i
2
[root@shell ~]# let i++
[root@shell ~]# echo $i
3
[root@shell ~]# echo i++ ===== i=i+1
i++ ===== i=i+1
[root@shell ~]# unset i
[root@shell ~]# let i=i+1
[root@shell ~]# echo $i
1
[root@shell ~]# let i=i+1
[root@shell ~]# echo $i
2
[root@shell ~]# let i++
[root@shell ~]# let i++
[root@shell ~]# let i++

5) bc 使用yum安装 yum -y install bc 支持整数和小数运算 语法格式: echo 10*10|bc

[root@shell ~]# echo 10*10|bc
100
[root@shell ~]# echo 10/10|bc
1
[root@shell ~]# echo 10-10|bc
0
[root@shell ~]# echo 10+10|bc
20
[root@shell ~]# echo 10+10.2|bc
20.2
[root@shell ~]# echo 10.5+10.2|bc
20.7
[root@shell ~]# echo 10.5*10.2|bc
107.1

其他运算 支持整数和小数运算 awk python awk语法: awk 'BEGIN{print 10*10}'

[root@shell ~]# awk 'BEGIN{print 10*10}'
100
[root@shell ~]# awk 'BEGIN{print 10-10}'
0
[root@shell ~]# awk 'BEGIN{print 10/10}'
1
[root@shell ~]# awk 'BEGIN{print 10+10}'
20
[root@shell ~]# awk 'BEGIN{print 10^10}'
10000000000
[root@shell ~]# echo 10 10|awk '{print $1*$2}' 
100
[root@shell ~]# echo 10 10|awk '{print $1^$2}' 
10000000000

案例: 做一个加减乘除的计算器 要求用户输入两个输入 显示加减乘除后的结果 使用三种传参方式 sh count.sh 10 10 输入的结果

  10+10=20
  10-10=
  10*10=
  10/10=
[root@shell ~]# cat count.sh 
#!/bin/sh
[ $# -ne 2 ] && echo "输入两个参数多了不行" && exit
#1.直接传参
echo $1+$2=$[$1+$2]
echo $1-$2=$[$1-$2]
echo $1*$2=$[$1*$2]
echo $1/$2=$[$1/$2]
#2 赋值传参
 num1=$1 num2=$2 
echo $num1+$num2=$[$num1+$num2] 
echo $num1-$num2=$[$num1-$num2] 
echo $num1*$num2=$[$num1*$num2]
 echo $num1/$num2=$[$num1/$num2] 

#3 read传参
read -p "输入两个参数:
" num1 num2 echo $num1+$num2=$[$num1+$num2] 
echo $num1-$num2=$[$num1-$num2] 
echo $num1*$num2=$[$num1*$num2] 
echo $num1/$num2=$[$num1/$num2]

 

条件表达式之文件判断 语法格式: 方法1 test 表达式 文件|目录 例如: test -e /etc/passwd 方法2 [ -e file ] 常用表达式:

[ -e file|dir ]   # 文件存在则为真 执行&& 否则执行||
[ -f file ]		    # 判断文件是否存在并且为普通文件
[ -d dir  ]       # 判断文件存在并且为目录
[ -r file ]       # 文件存在并且可读
[ -w file ]		    # 文件存在并且有写入权限
[ -x file ]		    # 文件存在并且可执行
[root@shell ~]# test -e /etc/passwd
[root@shell ~]# echo $?
0
[root@shell ~]# test -e /etc/passwd;echo $?
0
[root@shell ~]# test -e /etc/passwdddddddd;echo $?
1
[root@shell ~]# test -e /etc/passwd && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# test -f /etc/passwd && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# test -d /etc/passwd && echo "文件存在" || echo "文件不存在"
0 文件不存在
[root@shell ~]# test -x /etc/passwd && echo "文件存在" || echo "文件不存在"
文件不存在
[root@shell ~]# test -r /etc/passwd && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# test -w /etc/passwd && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -e /etc/hosts ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -f /etc/hosts ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -d /etc/hosts ] && echo "文件存在" || echo "文件不存在"
文件不存在
[root@shell ~]# [ -d /etc/ ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -r /etc/ ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -w /etc/hosts ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -x /etc/hosts ] && echo "文件存在" || echo "文件不存在"
文件不存在
[root@shell ~]# [ -x test.sh ] && echo "文件存在" || echo "文件不存在"
文件存在
判断中可以使用命令的方式进行取值判断
[root@shell ~]# [ -f `ls test.sh` ]
使用and和or判断多个文件
[root@shell ~]# [ -f /etc/hosts -a -f /etc/passwd ]
[root@shell ~]# [ -f /etc/hosts -a -f /etc/passwd ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -f /etc/hostsss -a -f /etc/passwd ] && echo "文件存在" || echo "文件不存在"
文件不存在
[root@shell ~]# [ -f /etc/hostsss -o -f /etc/passwd ] && echo "文件存在" || echo "文件不存在"
文件存在
[root@shell ~]# [ -f /etc/hostsss -o -f /etc/passwddd ] && echo "文件存在" || echo "文件不存在"
文件不存在

案例1:

 

使用变量赋值一个目录,判断目录是否存在,如果存在则打包压缩,如果不存在则提示目录不存在
[root@shell ~]# dir=/etc/;[ -d $dir ] && tar zcvf test.tar.gz $dir || echo "文件不存在"
案例2: 判断一个目录是否存在 如果不存在则创建 
[root@shell ~]# [ -d /hehe ] && mkdir hehe 
[root@shell ~]# ll hehe ls: cannot access hehe: No such file or directory 
[root@shell ~]# ll /hehe ls: cannot access /hehe: No such file or directory 
[root@shell ~]# [root@shell ~]# [ -d hehe ] && mkdir hehe 
[root@shell ~]# ll hehe ls: cannot access hehe: No such file or directory 
[root@shell ~]# [ -d hehe ] || mkdir hehe
[root@shell ~]# ll hehe total 0
案例3:
判断环境变量文件函数库是否存在 存在则执行.
[root@shell ~]# cat test.sh 
#!/bin/sh
[ -f /etc/init.d/functions ] && . /etc/init.d/functions
ping -c2 $1 &>/dev/null
[ $? -eq 0 ] && action "ping $1 is ok" true || action "ping $1 is error" false
  1. 数值比较 整数 格式: test 整数1 表达式 整数2 [ 整数1 表达式 整数2 ]

    表达式:

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

    [] 判断支持正则 必须使用[[]] 在正则中使用以下符号

    =
    !=
    >=
    ><
    ><=
    [root@shell ~]# [ 10 -eq 10 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ 10 -ne 10 ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [ 10 -ne 100 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ 10 -gt 100 ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [ 10 -lt 100 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ 100 -le 100 ] && echo "成立" || echo "不成立"
    成立
    

    使用变量的方式比较

    [root@shell ~]# echo $num1
    100
    [root@shell ~]# [ $num1 -le $num2 ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# echo $num2
    10
    [root@shell ~]# [ $num1 -gt $num2 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ $num1 -ne $num2 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ $num1 -ne $num2 ] && echo "成立" || echo "不成立"
    [root@shell ~]# name='I am lizhenya'
    [root@shell ~]# echo $name
    I am lizhenya
    [root@shell ~]# for i in $name;do echo $i;done
    I
    am
    lizhenya
    [root@shell ~]# for i in $name;do echo ${#i};done
    1
    2
    8
    [root@shell ~]# for i in $name;do [ ${#i} -lt 3 ];done
    [root@shell ~]# for i in $name;do [ ${#i} -lt 3 ]&& echo $i;done
    I
    am
    [root@shell ~]# for i in $name;do [ ${#i} -gt 3 ]&& echo $i;done
    lizhenya
    
    [root@shell ~]# [ 10 != 10 ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [ 100 > 10 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ 100 >= 10 ] && echo "成立" || echo "不成立"
    -bash: [: 100: unary operator expected
    不成立
    [root@shell ~]# [ 100 < 10 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [[ 100 < 10 ]] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [[ 100 > 10 ]] && echo "成立" || echo "不成立"
    成立
    

    案例1: 使用read或直接传参方式写一个数值比较脚本(使用数值表达式) 要求 输入两个数字 两个数字需要加判断 输入结果

    sh count.sh 100 10
    100>10
    [root@shell ~]# cat count.sh 
    #!/bin/sh
    [ $1 -gt $2 ] && echo "$1>$2"
    [ $1 -lt $2 ] && echo "$1<$2"
    [ $1 -eq $2 ] && echo "$1=$2"
    

    案例2: 统计磁盘使用率 如果超过3 则发送邮件通知管理员,正常则输出当前使用率 第一个步骤: 取出磁盘使用率的数值

    [root@shell ~]# df -h|awk 'NR==2{print $(NF-1)}'
    4%
    

    第二个步骤: 比较 优化取到的值

    [root@shell ~]#df -h|awk 'NR==2{print $(NF-1)}'|awk -F% '{print $1}'
    

    进行比较

    [root@shell ~]# [ `df -h|awk 'NR==2{print $(NF-1)}'|awk -F% '{print $1}'` -gt 3 ] && echo mail..... || echo ok
    mail.....
    

    第三个步骤: 写入脚本

    [root@shell ~]# cat disk.sh 
    #!/bin/bash
    use_disk=`df -h|awk 'NR==2{print $(NF-1)}'`
    [ ${use_disk%\%} -gt 80 ] && echo sendmail........ || echo "当前的磁盘使用率为 $use_disk"
    [root@shell ~]# sh disk.sh 
    
    当前的磁盘使用率为 4%

    案例3: 统计内存使用率 如果超过3 则发送通知管理员,否则输出当前的内存使用率 内存使用率的计算方式 使用的除以总共的乘以100 统计内存使用率:

    [root@shell ~]# free|awk 'NR==2{print $3/$2*100}'
    5.46261
    数值比较 
    [ `free|awk 'NR==2{print $3/$2*100}'|awk -F. '{print $1}'` -gt 3 ] && echo sendmail.... || echo ok 
    写入脚本
    [root@shell ~]# cat use_mem.sh
    #!/bin/sh
    use_mem=`free|awk 'NR==2{print $3/$2*100}'`
    [ ${use_mem%.*} -gt 80 ] && echo sendmail...... || echo "当前内存使用率 ${use_mem}%"
    [root@shell ~]# sh use_mem.sh
    当前内存使用率 5.4916%
    [root@shell ~]# cat use_mem.sh
    #!/bin/sh
    . /etc/init.d/functions
    use_mem=`free|awk 'NR==2{print $3/$2*100}'`
    [ ${use_mem%.*} -lt 3 ] && action "sendmail......" false || action "当前内存使用率 ${use_mem}%" true
    

    案例4: 统计当前的负载 负载大于1 则发送通知 否则输出当前的负载使用情况

    yum -y install httpd
    systemctl start httpd
    ab -n800000 -c400 http://10.0.0.61/index.html
    [root@shell ~]# cat use_load.sh 
    #!/bin/sh
    use_load=`uptime|awk '{print $(NF-2)}'`
    [ ${use_load%.*} -gt 1 ] && echo sendmail............. || echo "当前系统的负载为 ${use_load%,}"
    

    案例5: 统计系统的主机名称 虚拟平台 系统版本 内核版本 eth0 IP地址 外网网卡IP地址 当前内存和磁盘的使用率 输出的结果: 当前系统主机名称为: shell 当前系统内存使用率: 20%

    [root@lb02 ~]# cat static.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
    use_virtualization=`hostnamectl|awk 'NR==6{print $NF}'`
    [ ${use_virtualization} ] && echo "当前系统的虚拟平台为  ${use_virtualization}"
    use_oprete=`hostnamectl|awk 'NR==7{print $(NF-1)}'`
    [ ${use_oprete} ] && echo "当前系统版本为 ${use_oprete}"
    use_kernal=`hostnamectl|awk 'NR==9{print $NF}'`
    [ ${use_kernal} ] && echo "当前系统内核版本为 ${use_kernal}"
    use_mem=`free|awk 'NR==2{print $3/$2*100}'`
    [ ${use_mem%.*} -gt 80 ] && echo sendmail...... || echo "当前内存使用率 ${use_mem}%"
    use_disk=`df -h|awk 'NR==2{print $(NF-1)}'`
    [ ${use_disk%\%} -gt 80 ] && echo sendmail........ || echo "当前的磁盘使用率为 $use_disk"
    
  2. 多整数比较

    -a and
    -o or
     [ 10 -eq 10 -a 100 -gt 50 -o 100 -ne 100 ]
     [root@shell ~]# [ 10 -eq 10 ]
    [root@shell ~]# [ 10 -eq 10 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# 
    [root@shell ~]# [ 10 -eq 10 -a 100 -gt 50 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ 10 -ne 10 -a 100 -gt 50 ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# 
    [root@shell ~]# [ 10 -eq 10 -a 100 -gt 50 -o 100 -ne 100 ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ 10 -eq 10 -o 100 -ne 100 ] && echo "成立" || echo "不成立"
    成立
    在正则中使用 && ||
     [root@shell ~]# [[ 10 -eq 10 && 100 -gt 20 ]] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [[ 10 -eq 10 || 100 -gt 20 ]] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [[ 10 -eq 11 || 100 -gt 20 ]] && echo "成立" || echo "不成立"
    成立
    
  3. 字符串比对

    [root@shell ~]# [ user = user ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# [ user = userrr ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [ $USER = userrr ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [ $USER = user ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# echo $USER
    root
    [root@shell ~]# [ $USER = root ] && echo "成立" || echo "不成立"
    成立
    -n //nozero 字符串长度不为0时为真
    -z //zero 字符串长度为0时为真
    [root@shell ~]# test=""
    [root@shell ~]# [ -z $test ] && echo "成立" || echo "不成立"
    成立
    [root@shell ~]# test="aaa"
    [root@shell ~]# [ -z $test ] && echo "成立" || echo "不成立"
    不成立
    [root@shell ~]# [ -n $test ] && echo "成立" || echo "不成立"
    成立
    

    案例: 传参的时候不允许为空

    [root@shell ~]# cat test.sh
    #!/bin/sh
    [ -f /etc/init.d/functions ] && . /etc/init.d/functions
    [ -z $1 ] && echo "请输入url" && exit
    ping -c2 $1 &>/dev/null
    [ $? -eq 0 ] && action "ping $1 is ok" true || action "ping $1 is error" false
    [root@shell ~]# cat count.sh 
    #!/bin/sh
    [ $# -ne 2 ] && echo "请输入两个参数" && exit
    [ -z $1  ] && echo "请输入两个参数" && exit
    [ -z $2  ] && echo "请输入两个参数" && exit
    [ $1 -gt $2 ] && echo "$1>$2"
    [ $1 -lt $2 ] && echo "$1<$2"
    [ $1 -eq $2 ] && echo "$1=$2"
    read -p "请输入你的姓名: " name
    [ -z $name ] && echo "请输入姓名" && exit
    echo $name
    

    案例: 判断输入的是否为数字

    [root@shell ~]# expr 1 + 1.5
    expr: non-integer argument
    [root@shell ~]# echo $?
    2
    [root@shell ~]# expr 1 + qq
    expr: non-integer argument
    [root@shell ~]# echo $?
    2
    [root@shell ~]# expr 1 + 234
    235
    [root@shell ~]# echo $?
    0
    [root@shell ~]# cat count.sh 
    #!/bin/sh
    [ $# -ne 2 ] && echo "请输入两个参数" && exit
    expr $1 + $2 &>/dev/null
    [ $? -ne 0 ] && echo "请输入两个整数" && exit
    [ $1 -gt $2 ] && echo "$1>$2"
    [ $1 -lt $2 ] && echo "$1<$2"
    [ $1 -eq $2 ] && echo "$1=$2"
  4. 正则比对

  [root@shell ~]# [[ $user =~ ^r ]] && echo "成立" || echo "不成立"
  成立
  [root@shell ~]# [[ user =~ ^r ]] && echo "成立" || echo "不成立"
  不成立
  [root@shell ~]# [[ user =~ user ]] && echo "成立" || echo "不成立"
  成立
  [root@shell ~]# [[ user =~ r$ ]] && echo "成立" || echo "不成立"
  成立
  [root@shell ~]# [[ user =~ r$ && oldboy =~ ^o ]] && echo "成立" || echo "不成立"
  成立
  [root@shell ~]# [[ user =~ r$ && oldboy =~ ^a ]] && echo "成立" || echo "不成立"
  不成立
  [root@shell ~]# [[ user =~ r$ || oldboy =~ ^a ]] && echo "成立" || echo "不成立"
  成立
  [root@shell ~]# [[ $num1 =~ ^[0-9]+$ ]] && echo "成立" || echo "不成立"
  不成立
  [root@shell ~]# cat count.sh 
  #!/bin/sh
  [ $# -ne 2 ] && echo "请输入两个参数" && exit
  [[ ! $1 =~ ^[0-9]+$ ]] && echo "请输入整数" && exit 
  [ $1 -gt $2 ] && echo "$1>$2"
  [ $1 -lt $2 ] && echo "$1<$2"
  [ $1 -eq $2 ] && echo "$1=$2"

练习 判断输入的是否为整数 判断输入的是否为空 expr -z [[ 正则判断 ]]

  1. if判断 语法格式:

    [ 10 -eq 10 ] && echo ok || echo error
    if [ 10 -eq 10 ];then
       echo ok
    fi
    

    单分支语法: 一个条件 一个结果 [ 你有钱 ] && 我就嫁给你

    if [ 你有钱 ];then 		
      我就嫁给你
    fi
    if [ 你有钱 ]
    then
        我就嫁给你
    fi
    双分支语法: 一个条件 两个结果
    if [ 你有钱 ];then			 [ 你有钱 ] && 我就嫁给你 || 拜拜
      我就嫁给你
    else
       拜拜
    fi
    
    多分支语法: 多个条件 多个结果
  2. if [ 你有钱 ];
    then 我就嫁给你
     elif [ 你爸是李刚 ];
    then 我也嫁给你 
    elif [ 你活好 ];
    then 我倒贴
     elif [ 你在老男孩学习 ];
    then 先谈恋爱吧 
    else 拜拜 
    fi
    
  3. 案例: expr 判断输入的是否为整数

    [root@shell ~]# cat count.sh 
    #!/bin/sh
    expr $1 + $2 &>/dev/null
    if [ $? -ne 0 ];then
    echo "请输入两个整数"
    exit
    fi
    
    使用$# 判断传参的总个数 if [ $# -ne 2 ] 
    then echo "请输入两个参数" exit 
    fi 
    使用正则匹配判断输入的是否为整数 
    if [[ ! $1$2 =~ ^[0-9]+$ ]];
    then echo "请输入整数" 
    exit 
    fi 
    使用-z判断是否为空 
    read -p "please input hostname: " name if [ -z $name ];then echo "请输入姓名" exit fi
    

    案例: 不同的操作系统版本安装不用的YUM源 获取操作系统版本号:

    [root@shell ~]# awk '{print $(NF-1)}' /etc/redhat-release 
    7.6.1810
    [root@oldboyedu-c6 ~]# awk '{print $(NF-1)}' /etc/redhat-release
    6.9
    [root@shell ~]# cat yum.sh
    #!/bin/sh
    #判断网络是否正常
    #ping的返回结果使用if判断
    os_version=`awk '{print $(NF-1)}' /etc/redhat-release`
    if [[ $os_version =~ ^7 ]];then
    
    判断wget是否存在
    which wget &>/dev/null
    if [ $? -ne 0 ];then
    yum -y install wget &>/dev/null
    if [ $? -eq 0 ];then
    echo "install wget is ok.............."
    else
    echo "请检查你的网络或者源"
    fi
    fi
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
     wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
    elif
       [[ $os_version =~ ^6 ]];then
       echo yum6..................
    else
       echo "请检查你的系统版本"
    fi
    

if案例: 根据需求安装不同版本的PHP

菜单: 列出当前脚本的功能

[root@shell ~]# cat menu.sh 
#!/bin/sh
while true
do
                echo -e "\t\t\t##################"
		echo -e "\t\t\t#1. INSTLL PHP7.1#"
		echo -e "\t\t\t#2. INSTLL PHP7.2#"
		echo -e "\t\t\t#3. INSTLL PHP7.3#"
		echo -e "\t\t\t#4. INSTLL PHP7.4#"
		echo -e "\t\t\t#5. INSTLL MySQL #"
		echo -e "\t\t\t#6. exit         #"
		echo -e "\t\t\t##################"
		read -p "请输入需要安装的PHP的序号[1|2|3]: " num
		if [ $num -eq 1 ];then
		   echo yum7.1....................
		elif [ $num -eq 2 ];then
		   echo yum7.2...................
		elif [ $num -eq 3 ];then
		   echo yum7.3...................
		elif [ $num -eq 4 ];then
		   echo yum7.4...................
	elif [ $num -eq 5 ];then
	   echo MySQL...................
	elif [ $num -eq 6 ];then
	   exit
	fi
done

案例: 使用菜单的方式和if判断 显示当前系统的 内存 CPU负载 登录系统用户 当前的磁盘使用 退出 菜单: echo cat

 [root@shell ~]# cat os_menu.sh 
	#!/bin/sh
	menu(){
	cat<<EOF
							1 f.  查看系统内存	
							2 u.  查看CPU负载
							3 w.  查看当前用户
							4 d.  查看当前磁盘
							5 e.  exit
							6 h.  显示当前菜单
	EOF
	}
	menu
	while true
	do
		read -p "请输入要查看系统信息的编号或字母[1|f]: " num
		if [ $num = 1 -o $num = "f" ];then
		   free -h
		elif [ $num = 2 -o $num = u ];then 
		   uptime
		elif [ $num = 3 -o $num = w ];then
		   w
		elif [ $num = 4 -o $num = d ];then
		   df -h
		elif [ $num = 6 -o $num = h ];then
		   menu
		else
		   exit
		fi
	done

案例:猜数字游戏 系统随机生成一个数字, 让用户输入这个数字,如果大了 则提示大 小了则提示小了 相等则提示用户猜对了.

  1. 生成随机数

    [root@shell ~]# echo $((RANDOM%100+1))
    29
    [root@shell ~]# cat ran.sh 
    #!/bin/sh
    ran=`echo $((RANDOM%100+1))`
    while true
    do
    	let i++
    	read -p "请输入你要猜的数字1-100: " num
    	if [ $num -gt $ran ];then
    	   echo "你输入的大了"
    	elif [ $num -lt $ran ];then
    	   echo "你输入的小了"
    	else
    	   echo "恭喜你猜对了"
    	   echo "总共猜了 $i 次"
    		   exit
    	fi
    done  
    

case语句 语法格式:

case 变量 in
	  匹配变量1)
	     执行命令序列
		 ;;
      匹配变量2)
	     执行命令序列
		 ;;
	  匹配变量3)
	     执行命令序列
		 ;;
	  *)
	     执行命令序列
esac

案例: 查看系统信息

 [root@shell ~]# cat case.sh 
	#!/bin/bash
	menu(){
	cat<<EOF
					d. 查看磁盘
					f. 查看内存
					w. 查看用户
					u. 查看负载
					h. 显示菜单
					e. 退出脚本
	EOF
	}
	menu
	while true
	do
		read -p "请输入内容[d|f|w]: " test
		case $test in
			   d)
				 df -h
			   ;;
			   f)
				 free -h
			   ;;
			   w)
				 w
			   ;;
			   h)
				 clear
				 menu
			   ;;
			   *)
				echo "请输入菜单显示的系统项"
		esac
	done
 案例: shell跳板机
 [root@shell ~]# cat jumpserver.sh
 #!/bin/sh
 . /etc/init.d/functions
 WEB01=10.0.0.7
 WEB02=10.0.0.8
 LB01=172.16.1.5
 LB02=10.0.0.6
 MySQL=10.0.0.51
 NFS=10.0.0.31
 BACKUP=10.0.0.41
 menu1(){
 cat<<EOF
                         1. 运维
                         2. 开发
                         3. 测试   
                         4. 菜单
 EOF
 }
 menu1
 menu(){
 cat<<EOF
                         1. WEB01-10.0.0.7
                         2. WEB02-10.0.0.8
                         3. LB01-10.0.0.5
                         4. LB02-10.0.0.6
                         5. MySQL-10.0.0.51
                         6. NFS-10.0.0.31
                         7. 显示菜单
                         8. 返回上一级菜单
 EOF
 }
 menu2(){
 cat<<EOF
                         1.MySQL-10.0.0.51
                         2.LB01-10.0.0.5
                         3.显示菜单
                         4.返回上一级菜单
 EOF
 }
 trap "" INT TSTP HUP#禁止ctrl+c ctrl+d等
 while true
 do
 read -p "选择你的岗位:[1|2|3]: " jd
     if [[ ! $jd =~ ^[0-9]+$ ]];then
             echo "必须输入数字"
             continue
     fi
 if [ $jd -eq 1 ];then
      while true
      do
          let i++
          read -p "请输入运维的口令: " op
           [ -z $op ] && echo "请输入正确的口令,不允许为空" && continue
          if [ $op = "woxiangjinqu" ];then
              action "口令正确欢迎运维大神归来" /bin/true
              break
          else
             action "口令不对重新输入!!!" /bin/false
             if [ $i -ge 3 ];then
             /root/game.sh
             fi
          fi
      done
      menu
 while true
 do
 read -p "请输入你要连接的服务器的编号[1|2|3|4菜单]: " num
 ​
 case $num in
       1)
         ssh $WEB01
       ;;
       2)
         ssh $WEB02
       ;;
       3)
         ssh $LB01
       ;;
       4)
         ssh $LB02
       ;;
       5)
         ssh $MySQL
       ;;
       7)
         clear
         menu
       ;;
           houmen)
         exit
       ;;
       8)
       menu1     
          break
       ;;
       *) 
         echo "请输入正确的服务器编号"
 esac
 done
     elif [ $jd = 2 ];then
          while true
          do
                  read -p "请输入开发的口令: " dev
          [ -z $dev ] && echo "请输入正确的口令,不允许为空" && continue
                  if [ $dev = "wos" ];then
              action "口令正确欢迎代码搬运工" /bin/true
                      break
                  else
                     action "口令不对重新输入!!!" /bin/false
                  fi
          done   
     menu2 
     while true
     do
     read -p "请输入连接的服务器编号:[1|2]: " num2 
case $num2 in 
1) 
ssh $MySQL 
    ;; 
2) 
ssh $LB01 
    ;; 
3)  
      clear 
      menu2 
    ;; 
4) 
      men1 
      break 
    ;; 
houmen) 
      exit 
    ;; 
    *) 
echo "请输入正确的编号: [1|2]" 
esac 
done 
elif [ $jd = 4 ];then 
menu1 
fi 
done
------------------------------精简版----------------------------
[root@shell ~]# cat jump.sh
#!/bin/sh
. /etc/init.d/functions
WEB01=10.0.0.7
WEB02=10.0.0.8
LB01=172.16.1.5
LB02=10.0.0.6
MySQL=10.0.0.51
NFS=10.0.0.31
BACKUP=10.0.0.41
menu(){
cat<<EOF
						1. WEB01-10.0.0.7
						2. WEB02-10.0.0.8
						3. LB01-10.0.0.5
						4. LB02-10.0.0.6
						5. MySQL-10.0.0.51
						6. NFS-10.0.0.31
						7. 显示菜单
EOF
}
menu
trap "" INT TSTP HUP
while true
do
read -p "请输入你要连接的服务器的编号[1|2|3|4菜单]: " num
case $num in
	  1|web01)
		ssh $WEB01
	  ;;
	  2|web02)
		ssh $WEB02
	  ;;
	  3|lb01)
		ssh $LB01
	  ;;
	  4)
		ssh $LB02
	  ;;
	  5)
		ssh $MySQL
	  ;;
	  7)
		clear
		menu
	  ;;
		  houmen)
		exit
	  ;;
	  *) 
		echo "请输入正确的服务器编号"
esac
done
-------------------------------------------------------------

案例: nginx启动脚本

[root@shell ~]# /usr/sbin/nginx           # nginx启动方式
[root@shell ~]# /usr/sbin/nginx -s stop   # Nginx停止
[root@shell ~]# /usr/sbin/nginx -s reload # 重载
[root@shell ~]# /usr/sbin/nginx -s stop && /usr/sbin/nginx  # 先停止后启动
[root@shell ~]# cat start_nginx.sh
#!/bin/sh
. /etc/init.d/functions
case $1 in
	start)
		if [ `ps axu|grep nginx|grep master|wc -l` -eq 0 ];then
		 
			/usr/sbin/nginx
				if [ $? -eq 0 ];then
				action "Nginx $1 is"  /bin/true
				else
				action "Nginx $1 is"  /bin/false
				fi
	   else
			action "Nginx $1 is 已经在运行" /bin/true
	   fi
	;;
	stop)
	   /usr/sbin/nginx -s stop
	;;	
	reload)
	  /usr/sbin/nginx -s reload
	;;
	restart)
	 /usr/sbin/nginx -s stop
	sleep 1
	/usr/sbin/nginx
	;;
	status)  
		if [ `ps axu|grep nginx|grep master|wc -l` -eq 0 ];then
		echo "Nginx is not running......"
		else
		pid=`ps axu|grep nginx|grep master|awk '{print $2}'`
		port=`netstat -tnulp|grep nginx|grep master|grep 80|awk '{print $4}'`
		echo "当前的Nginx端口号为 $port"
		echo "当前的NgnxPID号为: $pid"
		fi
	;;
	*)
	 echo "USAGE: $0 [start|stop|restart|reload|status]"
esac

交互脚本expect

1.简单实现免交互登陆
expect是一款自动化的脚本解释型的工具
脚本开头
expect脚本一般以#!/usr/bin/expect 开头,类似bash脚本。
常用后缀
expect脚本常常以.exp或者.ex结束。
expect主要命令
spawn 新建一个进程,这个进程的交互由expect控制
expect 等待接受进程返回的字符串,直到超时时间,根据规则决定下一步操作
send 发送字符串给expect控制的进程
set 设定变量为某个值      set user oldboy
exp_continue 重新执行expect命令分支
[lindex $argv 0] 获取expect脚本的第1个参数
[lindex $argv 1] 获取expect脚本的第2个参数
set timeout -1 设置超时方式为永远等待
set timeout 30 设置超时时间为30秒
interact 将脚本的控制权交给用户,用户可继续输入命令
expect eof 等待spawn进程结束后退出信号eof

安装expect

yum -y install expect
#!/usr/bin/expect
spawn ssh root@10.0.0.5
expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "centos\r" };
} 
interact

2.expect定义变量实现交互方式

#!/usr/bin/expect
set ip 10.0.0.5
set user root
set password centos
set timeout 5

spawn ssh $user@$ip

expect {
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "$password\r" };
} 

#交互方式 interact 案例

批量获取在线主机, 进行秘钥批量分发

cat for_ip.sh 
#!/usr/bin/bash
#setup1 拿到IP地址
>ip.txt
for i in {1..10}
do
ip=10.0.0.$i
{
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo "$ip" >> ip.txt
 fi
 }&
done
#2.生成对应的密钥
if [ ! -f ~/.ssh/id_rsa ];then
ssh-keygen -P "" -f ~/.ssh/id_rsa
fi

#3.批量分发密钥

 while read line
        do
/usr/bin/expect << EOF
                        set pass 1
                        set timeout 2
                        spawn ssh-copy-id  $line -f 
                        expect {
                                "yes/no" { send "yes\r"; exp_continue}
                                "password:" { send "1\r"}
                        }
                        expect eof 
EOF
done<ip.txt

for 循环

语法结构: for 变量 in 变量值 变量值可以是数字 字母 变量 命令 do 执行的命令序列 具体的执行动作 可以和变量无关 done

for.sh

for i in 框子(苹果 梨子 香蕉 黄瓜)
do
	 echo $i 
	第一个循环:
	i=苹果
	echo 苹果
	第二次循环:
	i=梨子
	echo 梨子
	第四次循环
	i=黄瓜
	echo 黄瓜
done

[root@shell scripts]# cat for.sh 
for i in 1 10 20 30
do
	   echo $i
done

[root@shell scripts]# cat for.sh 
for i in `seq 10`
do
	   echo $i
done

[root@shell scripts]# cat for.sh
for i in `seq 5`
do
	   echo ok
done

 

案例: 数字从1加到100  笔试题
[root@shell scripts]# cat for.sh 
for i in `seq 100`
do
	   sum=$[$sum+$i]
done
echo $sum
[root@shell scripts]# cat for.sh
for i in $(seq 100)
do 
         sum=$(($sum+$i))  or   sum=$((sum+i))
done
echo $sum
[root@shell scripts]# seq -s + 100|bc
5050
扩展使用awk
[root@shell scripts]# seq 100|awk '{i=i+$1}END{print i}'
5050
[root@shell scripts]# echo {1..100}| awk -F " " '{for(i=1;i<NF+1;i++){j=j+i;}print j;}'
5050
案例: 批量pin 获取在线的IP地址 能ping通说明在线
10.0.0.0/24  10.0.0.1-10.0.0.254

[root@shell scripts]# cat ping.sh 
#!/bin/sh
for i in {1..254}
do
	{
	IP=10.0.0.$i
	ping -c2 -W1 $IP &>/dev/null
	if [ $? -eq 0 ];then
	   echo "当前 $IP 在线"
	fi
	} &
done
wait
echo "完成在线取IP.........."
案例: 批量创建10个用户 用户前缀名oldboy oldboy1 oldboy2 oldboy3 ... oldboy10
要求判断用户是否存在 不存在则创建 存在提示用户已经存在  为每个用户创建123456密码 并且判断密码是否创建成功
使用随机密码 并且把用户名和密码统一输出到文件中
[root@shell scripts]# vim user.sh 
#!/bin/sh
for i in {1..10}
do
		useradd oldboy$i
done
[root@shell scripts]# cat user.sh
#!/bin/bash
for i in {1..10}
do
      userdel -r oldboy$i
done

[root@shell scripts]# cat user1.sh
#!/bin/sh
for i in {1..10}
do
	id oldboy$i &>/dev/null
		if [ $? -eq 0 ];then
	   echo "useradd: user oldboy$i already exists"
	else
	useradd   oldboy$i
	   if [ $? -eq 0 ];then
	   echo "Create oldboy$i is ok"
	   else
	   echo "Create oldboy$i is error"
	   fi
	fi
done

[root@shell scripts]# cat user2.sh
#!/bin/bash
for i in {1..10}
do 
id oldboy$i &>/dev/null
 if [ $? -eq 0 ];then
    echo "用户已经存在"
    else 
     useradd  oldboy$i
     if [ $? -eq 0 ];then
     echo "Create oldboy$i is ok"
     else
      echo "Create oldboy$i is error"
      fi
fi 
done

--------------------------
[root@shell scripts]# cat user.sh 
#!/bin/sh
> passwd.txt
for i in {1..10}
do
	pass=`date +%N|md5sum|cut -c1-8`
	id oldboy$i &>/dev/null
		if [ $? -eq 0 ];then
	   echo "useradd: user oldboy$i already exists"
	else
	useradd   oldboy$i
	   if [ $? -eq 0 ];then
	   echo "Create oldboy$i is ok"
			echo $pass|passwd --stdin oldboy$i &>/dev/null
			if [ $? -eq 0 ];then
			echo "设置密码成功"
			else
			echo "设置密码失败"
			fi
		   echo  "oldboy$i $pass" >>passwd.txt
	   else
	   echo "Create oldboy$i is error"
	   fi
	fi
done
用户的前缀手动输入 创建的个数手动输入 for循环 结合case语句方式批量创建用户
[root@shell scripts]# cat user1.sh 
#!/bin/sh
read -p "请输入用户的前缀名称: " prefix
[ -z $prefix ] && echo "请输入用户前缀" && exit
read -p "请输入创建用户的个数: " num
if [[ ! $num =~ ^[0-9]+$ ]];then
	echo "请输入整数"
fi
for i in `seq $num`
do
	echo $prefix$i
done
read -p "你确定要创建以上的用户吗?[y|Y|yes|n|N|no]: " re
for a in `seq $num`
do
		  user=$prefix$a
case $re in
	  y|Y|yes)
			 id $user &>/dev/null
		 if [ $? -eq 0 ];then
			echo "用户存在"
		 else
			useradd $user &>/dev/null
			if [ $? -eq 0 ];then
			   echo "用户创建成功"
			else
			   echo "用户创建失败"
			fi
		 fi
		 ;;
	   n|N|no)
		  exit
		;;
		   *)
		  echo "USAGE: $0 [请输入前缀个数....]"

esac
done

while循环

语法格式: while 条件表达式 为真则执行 否则不执行 do 命令序列 done

[root@shell scripts]# cat while.sh
while true
do
	 echo ok
	 sleep 2
done

[root@shell scripts]# cat while.sh 
a=0
while [ $a -le 3 ]
do
	 let a++
	 echo ok
done
[root@shell scripts]# sh while1.sh 
ok
ok
ok
ok
[root@shell scripts]# cat while1.sh
a=1
while [ $a -le 3 ]
do 
  let a++
       echo ok
done
[root@shell scripts]# sh while1.sh 
ok
ok
ok
使用while循环从1加到100
[root@shell scripts]# cat while.sh 
a=1
while [ $a -le 100 ]
do
	 sum=$[$sum+$a]
	 let a++
done
echo $sum
while读取文件
for循环取值方式: 按照空格取值
[root@shell scripts]# cat test.sh
#!/bin/sh
for i in `cat /etc/hosts`
do
echo $i
done
while循环取值方式: 按照行取值
while read line
do
echo $line
done</etc/hosts
案例: 通过读取文件的方式创建用户名
    user.txt 
	zs1
	old2
	tes1
	test2
	lisi

[root@shell scripts]# cat while.sh 
#!/bin/sh
while read line
do
		user=`echo $line|awk '{print $1}'`
	pass=`echo $line|awk '{print $2}'`
	useradd $user
	echo $pass|passwd --stdin $user
done<user.txt

流程控制语句 exit 退出当前的脚本 break 跳出本层循环 continue 省略当前循环的后面的代码.从头开始执行

#!/bin/sh
while true
do
		echo ok
		continue
		echo hehe
done
echo done...............

exit 退出当前整个脚本
[root@shell scripts]# cat while2.sh
#!/bin/sh
for i in `seq 10`
do
	useradd oldboy$i &>/dev/null
		if [ $? -eq 0 ];then
		echo "Create oldboy$i success"
		else
		exit
	fi
done
break 跳出本层循环 
[root@shell scripts]# cat while2.sh
#!/bin/sh
for i in `seq 10`
do
	useradd oldboy$i &>/dev/null
		if [ $? -eq 0 ];then
		echo "Create oldboy$i success"
		else
		break
	fi
done
echo done................
[root@shell scripts]# sh while2.sh 
Create oldboy1 success
Create oldboy2 success
Create oldboy3 success
Create oldboy4 success
done................

 continue
   [root@shell scripts]# cat while2.sh 
	#!/bin/sh
	for i in `seq 10`
	do
		useradd oldboy$i &>/dev/null
			if [ $? -eq 0 ];then
			echo "Create oldboy$i success"
			else
			continue
		fi
	done
	echo done................
  [root@shell scripts]# sh while2.sh 
	Create oldboy1 success
	Create oldboy2 success
	Create oldboy3 success
	Create oldboy4 success
	Create oldboy6 success
	Create oldboy7 success
	Create oldboy8 success
	Create oldboy9 success
	Create oldboy10 success
	done................

 

fun函数

1.命令的集合,完成特定功能的代码块 ​ 2.便于代码复用 3.和变量类似 只能先定义 在调用,只定义不调用 不执行 函数的定义: [root@shell scripts]# cat fun.sh #!/bin/sh fun(){ echo "函数的第一种定义方式" }

function fun1(){
	echo "函数的第二种定义方式"
}

function fun2 {
	echo "函数的第三种定义方式"
}
fun
fun1
fun2
[root@shell scripts]# sh fun.sh 
函数的第一种定义方式
函数的第二种定义方式
函数的第三种定义方式

函数的传参 函数的传参第一种方式 在函数的后面调用

[root@shell scripts]# cat fun1.sh
	#!/bin/sh
	fun(){
		if [ -f $1 ];then
		  echo "文件存在"
		else
		  echo "文件不存在"
		fi
	}
	fun /etc/hosts
	[root@shell scripts]# sh -x fun1.sh /etc/passwd 
	+ fun /etc/hosts
	+ '[' -f /etc/hosts ']'
	+ echo 文件存在
	文件存在

[root@shell scripts]# cat fun1.sh
#!/bin/sh
fun(){
	if [ -f $1 ];then
	  echo "$1 文件存在"
	else
	  echo "$1 文件不存在"
	fi
}
fun $2
[root@shell scripts]# sh fun1.sh /etc/passwd /etc/hosts
/etc/hosts 文件存在

-------------------------
[root@shell scripts]# cat fun1.sh
#!/bin/sh
fun(){
	if [ -f $1 ];then
	  echo "$1 文件存在"
	else
	  echo "$1 文件不存在"
	fi
}
fun $2 $1
[root@shell scripts]# sh fun1.sh /etc/passwd /etc/hosts
/etc/hosts 文件存在

[root@shell scripts]# cat fun1.sh
#!/bin/sh
fun(){
	if [ -f $2 ];then
	  echo "$2 文件存在"
	else
	  echo "$2 文件不存在"
	fi
}
fun $2 $1
[root@shell scripts]# sh fun1.sh /etc/passwd /etc/hosts
/etc/passwd 文件存在

函数传参的第二种方式:
    调用当前shell中的变量

[root@shell scripts]# cat fun1.sh
#!/bin/sh
file=$1
fun(){
	if [ -f $file ];then
	  echo "$file 文件存在"
	else
	  echo "$file 文件不存在"
	fi
}
fun

函数的本地变量: 只能在函数体中生效 在其他的shell语句中不生效
local file=/etc/hosts
[root@shell scripts]# vim fun1.sh 
#!/bin/sh
fun(){
	local file=/etc/hosts
	if [ -f $file ];then
	  echo "$file 文件存在"
	else
	  echo "$file 文件不存在"
	fi
}
fun
echo $file

案例:
[root@shell scripts]# cat fun2.sh 
#!/bin/sh
num1=10
fun(){
	for i in `seq 10`
	do
	total=$[$i+$num1]
	done
}
fun
echo $total
[root@shell scripts]# sh fun2.sh
20	

注意变量位置
[root@shell scripts]# cat fun2.sh
#!/bin/sh
fun(){
	for i in `seq 10`
	do
	total=$[$i+$num1]
	done
}
num1=10
fun
echo $total
[root@shell scripts]# sh fun2.sh
20

[root@shell scripts]# cat fun2.sh
#!/bin/sh
fun(){
	for i in `seq 10`
	do
	total=$[$i+$1]
	done
	echo $total
}
fun $1
fun $2
fun $3
[root@shell scripts]# sh fun2.sh 10 20 30
20
30
40

 [root@shell scripts]# cat fun2.sh 
     #!/bin/sh
     fun(){
         for i in `seq 10`
         do
         total=$[$i+$1]
         done
         echo $total
     }
     fun $2 $1 $3
     [root@shell scripts]# sh fun2.sh 20 30 10
     40
 ​
 函数的返回值: return
     [root@shell scripts]# cat fun1.sh
     #!/bin/sh
         file=$1
     fun(){
         if [ -f $file ];then
           return 50
         else
           return 100
         fi
     }
     fun
     echo $?
     [root@shell scripts]# sh fun1.sh /etc/hostssss
     100
     [root@shell scripts]# sh fun1.sh /etc/hosts
     50
 ​
    [root@shell scripts]# cat fun1.sh 
     #!/bin/sh
         file=$1
     fun(){
         if [ -f $file ];then
           return 50
         else
           return 100
         fi
     }
     fun
     if [ $? -eq 50 ];then
         echo "文件存在"
     else
         echo "文件不存在"
     fi
   [root@shell scripts]# cat fun1.sh
     #!/bin/sh
         file=$1
     fun(){
           echo 50
           return 100
     }
     num=`fun`
     echo "当前函数的返回值为: $?"
     echo "当前函数的执行结果为: $num"
     [root@shell scripts]# sh fun1.sh /etc/hosts
     当前函数的返回值为: 100
     当前函数的执行结果为: 50
   [root@shell scripts]# cat fun1.sh
     #!/bin/sh
         file=$1
     fun(){
           echo 50
           return 100
     }
     num=`fun`
     name=oldboy
     echo "当前函数的返回值为: $?"
     echo "当前函数的执行结果为: $num"
     [root@shell scripts]# sh fun1.sh /etc/hosts
     当前函数的返回值为: 0
     当前函数的执行结果为: 50
 ​
 函数在其他shell中调用 类似变量
    [root@shell scripts]# cat fun.sh
       /bin/sh
     fun(){
         echo "函数的第一种定义方式"
     }

 function fun1(){
     echo "函数的第二种定义方式"
 }
 ​
 function fun2 {
     echo "函数的第三种定义方式"
 }
 [root@shell scripts]# fun
 -bash: fun: command not found
 [root@shell scripts]# . fun.sh
 [root@shell scripts]# fun
 函数的第一种定义方式
 [root@shell scripts]# fun1
 函数的第二种定义方式
 [root@shell scripts]# fun2
 函数的第三种定义方式

 

数组

1.数组的分类

普通数组

只能使用整数来定义

关联数组

可使用数字和字符来定义

变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
索引:编号从0开始,属于数值索引
注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
bash的数组支持稀疏格式(索引不连续)
声明数组:
declare -a ARRAY_NAME
declare -A ARRAY_NAME 关联数组
注意:两者不可相互转换 高级变量用法-有类型变量 Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的 declare [选项] 变量名 -r 声明或显示只读变量 -i 将变量定义为整型数 -a 将变量定义为数组 -A 将变量定义为关联数组 -f 显示已定义的所有函数名及其内容 -F 仅显示已定义的所有函数名 -x 声明或显示环境变量和函数 -l 声明变量为小写字母 declare –l var=UPPER -u 声明变量为大写字母 declare –u var=lower

间接变量引用

bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1
tempvar=${!variable1}
示例:
[root@server ~]# N=NAME
[root@server ~]# NAME=wangxiaochun
[root@server ~]# N1=${!N}
[root@server ~]# echo $N1
wangxiaochun
[root@server ~]# eval N2=\$$N
[root@server ~]# echo $N2
wangxiaochun

创建临时文件

mktemp命令:创建并显示临时文件,可避免冲突
mktemp [OPTION]... [TEMPLATE]
TEMPLATE: filenameXXX
X至少要出现三个
OPTION:
-d: 创建临时目录
-p DIR或--tmpdir=DIR:指明临时文件所存放目录位置
示例:
mktemp /tmp/testXXX
tmpdir=`mktemp –d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX

安装复制文件

install命令:
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录
选项:
-m MODE,默认755
-o OWNER
-g GROUP
示例:
install -m 700 -o wang -g admins srcfile desfile
install –m 770 –d /testdir/installdir

2.数组的格式

变量名称=变量的值 数组名称[索引]=(元素的值)

3.定义数组

第一种定义方式

索引定义

默认索引从0开始

[root@shell scripts]# array[1]=cm
[root@shell scripts]# array[5]=zs
[root@shell scripts]# array[6]=ls

第二种定义方式

一次性定义多个值

 [root@shell scripts]# array=(ll lz lw lz)

第三种定义方式

混合定义的方式

 [root@shell scripts]# array=(qq aa cc [20]=tt [22]=yy)

第四种定义方式

命令定义

[root@shell scripts]# ls /tmp/
nging.pid  vmware-root_6223-1681855427
[root@shell scripts]# array=(`ls /tmp`)

4.调用数组 declare -a 查看当前定义所有的普通数组

 [root@shell scripts]# echo $array          # 默认只能查看下标为0的值
 mysql
 [root@shell scripts]# 
 [root@shell scripts]# echo ${array[*]} # 查看数组中的所有内容
 mysql shell redis
 [root@shell scripts]# echo ${array[@]}
 mysql shell redis
 [root@shell scripts]# echo ${!array[@]} # 查看所有的下标
 0 1 2
 [root@shell scripts]# echo ${array[1]}  # 通过下标查看值
 shell
 [root@shell scripts]# echo ${array[2]}
 redis
 [root@shell scripts]# echo ${array[0]}
 mysql
 [root@shell scripts]# echo ${#array[*]} # 查看元素的个数
 3
 案例: 判断当前公司中几台服务器的IP地址是否能ping通
61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8

定义数组:

 [root@shell scripts]# IP=(61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8)

获取数组的值:

  [root@shell scripts]# echo ${IP[*]}
    61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8

for循环方式去ping每个IP

  [root@shell scripts]# for i in ${IP[*]};do echo $i;done
     61.135.169.121
     123.126.55.41
     10.0.0.254
     8.8.8.8
   [root@shell scripts]# for i in ${IP[*]};do ping -c2 -W1 $i;done
   [root@shell scripts]# cat array.sh 
     #!/bin/sh
     IP=(
     61.135.169.121
     123.126.55.41
     10.0.0.254
     8.8.8.8
     10.0.0.51
     )
     for i in ${IP[*]}
     do
         ping -c2 -W1 $i &>/dev/null
             if [ $? -eq 0 ];then
            echo "ping $i is ok............"
         else
            echo "ping $i is error........."
         fi
     done

 

关联数组: 下标可以使用数字和字符串  声明关联数组:  declare -A 数组名称   使用下标方式定义数组
[root@shell scripts]# array[index1]=redis
[root@shell scripts]# array[index2]=mysql
[root@shell scripts]# array[index3]=shell
[root@shell scripts]# echo ${array[*]}
redis mysql shell
第二种方式定义数组
[root@shell scripts]# array=([0]=zabbix [index4]=kvm [index5]=mongo)
[root@shell scripts]# echo $array[*]
zabbix[*]
[root@shell scripts]# echo ${array[*]}
kvm mongo zabbix
[root@shell scripts]# echo ${!array[*]}
index4 index5 0

关联数组案例: 统计文件中性别出现的次数

 cat sex.txt
 m
 f
 f
 m
 m
 m
 x
 [root@shell scripts]# sort sex.txt|uniq -c
 2 f
 4 m
 1 x
 [root@shell scripts]# sort sex.txt|uniq -c|sort -rn
 4 m
 2 f
 1 x
 [root@shell scripts]# cat sex.txt |uniq -c|sort -rn
 3 m
 2 f
 1 x
 1 m
 [root@shell scripts]# cat sex.txt |sort 
 f
 f
 m
 m
 m
 m
 x
 [root@shell scripts]# cat sex.txt |sort |uniq -c
 2 f
 4 m
 1 x
 [root@shell scripts]# cat sex.txt |sort |uniq -c|sort -rn
 4 m
 2 f
 1 x
 [root@shell scripts]# cat s.sh 
 for i in `cat sex.txt`
 do
 if [ $i = m ];then
 let m++
 elif [ $i = f ];then
 let f++
 else
 let x++
 fi 
 done
 echo "m出现了 $m 次"
 echo "f出现了 $f 次"
 echo "x出现了 $x 次"
 [root@shell scripts]# sh s.sh
 m出现了 4 次
 f出现了 2 次
 ​
 x出现了 1 次
 --------------------------
 ​
   [root@shell scripts]# let a++
     [root@shell scripts]# echo $a
     1
     [root@shell scripts]# let array[b]++
     [root@shell scripts]# echo ${array[b]}
     1
     [root@shell scripts]# let a++
     [root@shell scripts]# let b++
     [root@shell scripts]# let b++
     [root@shell scripts]# let a++
     [root@shell scripts]# let array[b]++
     [root@shell scripts]# let array[a]++
     [root@shell scripts]# let array[a]++
     [root@shell scripts]# 
     [root@shell scripts]# echo $a
     3
     [root@shell scripts]# echo ${array[b]}
     2
     [root@shell scripts]# echo ${array[a]}
 ​
     2
 -----------------------------
 ​
    cat s.sh         # 执行过程
    declare -A sex
     for i in `cat sex.txt`
     do
            let sex[$i]++
 ​
 --------------
 ​
 •          i=m
 •          let sex[m]++
 ​
 ---
 ​
 •          i=f
 •          let sex[f]++
 ​
 ---
 ​
 •          i=m
 •          let sex[m]++
 •   done
 •   echo "m出现了 $m 次"
 •   echo "f出现了 $f 次"
 •   echo "x出现了 $x 次"
  [root@shell scripts]# cat s.sh 
 •   declare -A sex
 •   for i in `cat sex.txt`
 •   do
 •          let sex[$i]++
 •   done
 •   echo "m出现了 ${sex[m]} 次"
 •   echo "f出现了 ${sex[f]} 次"
 •   echo "x出现了 ${sex[x]} 次"
 ​
 •    [root@shell scripts]# sh s.sh
 •    m出现了 4 次
 •    f出现了 2 次
 •    x出现了 1 次
 ​
    遍历数组:
    [root@shell scripts]# cat s.sh 
     declare -A sex
     for i in `cat sex.txt`
     do
            let sex[$i]++
     done
     #echo "m出现了 ${sex[m]} 次"
     #echo "f出现了 ${sex[f]} 次"
     #echo "x出现了 ${sex[x]} 次"
     for a in ${!sex[*]}
     do
          echo "$a 出现了 ${sex[$a]} 次"
     done
     [root@shell scripts]# sh s.sh
     f 出现了 2 次
     m 出现了 4 次
     x 出现了 1 次


案例: 统计/etc/passwd 中出现的解释器

   declare -A bash         # 执行过程
     while read line
     do
            let bash[`echo $line|awk -F: '{print $NF}'`]++
            root:x:0:0:root:/root:/bin/bash
            let bash[/bin/bash]++
     bin:x:1:1:bin:/bin:/sbin/nologin 
       let bash[/sbin/nologin]++ 
     
done</etc/passwd 
for a in ${!bash[*]} 
do 
echo "$a 出现了 ${bash[$a]} 次" 
done 
[root@shell scripts]# cat bash.sh  
declare -A bash 
while read line 
do 
       let bash[`echo $line|awk -F: '{print $NF}'`]++ 
done</etc/passwd 
for a in ${!bash[*]} 
do 
echo "$a 出现了 ${bash[$a]} 次" 
done 
[root@shell scripts]# sh bash.sh 
    /sbin/nologin 出现了 21 次 
    /bin/sync 出现了 1 次 
    /bin/bash 出现了 26 次 
    /sbin/shutdown 出现了 1 次 
    /sbin/halt 出现了 1 次

统计当前nginx日志文件中IP地址出现的次数

 [root@shell scripts]# cat ip.sh
     #!/bin/sh
     declare -A IP
     while read line
     do
     let IP[`echo $line|awk '{print $1}'`]++
     done</var/log/nginx/access.log
     for i in ${!IP[*]}
     do
         echo "$i 出现了 ${IP[$i]} 次"
     done 

数组的删除

 引用数组
 引用数组元素
 ${ARRAY_NAME[INDEX]}
 注意:省略[INDEX]表示引用下标为0的元素
 引用数组所有元素
 ${ARRAY_NAME[*]}
 ${ARRAY_NAME[@]}
 数组的长度(数组中元素的个数)
 ${#ARRAY_NAME[*]}
 ${#ARRAY_NAME[@]}
 除数组中的某元素:导致稀疏格式
 unset ARRAY[INDEX]
 删除整个数组
 unset ARRAY
 [root@localhost ~]# unset array[1]
 [root@localhost ~]# echo ${array[*]}
 one three four
 [root@localhost ~]# unset array
 [root@localhost ~]# echo ${array[*]}

 

数组内容截取和替换

数组数据处理 引用数组中的元素:

 数组切片:
 ${ARRAY[@]:offset:number}
 offset 要跳过的元素个数
 number 要取出的元素个数
 取偏移量之后的所有元素 ${ARRAY[@]:offset}
 向数组中追加元素:
 ARRAY[${#ARRAY[*]}]=value
 联数组:
 declare -A ARRAY_NAME
 ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
 注意:关联数组必须先声明再调用
[root@localhost ~]# array=(0 1  2 3 4)
[root@localhost ~]# echo ${array[@]:1:3}
1 2 3
[root@localhost ~]# array=($(echo {a..z}))
[root@localhost ~]# echo ${array[@]}
a b c d e f g h i j k l m n o p q r s t u v w x y z
[root@localhost ~]# echo ${array[@]:1:3}
b c d
[root@localhost ~]# array=(1 2 3 4 5)
[root@localhost ~]# echo ${array[@]/3/three}
1 2 three 4 5

字符串增删改查匹配处理

基于模式取子串
${var#*word}:其中word可以是指定的任意字符
功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
示例:
file=“var/log/messages”
${file#*/}: log/messages
${file##*/}: messages
 ${var%word*}:其中word可以是指定的任意字符
功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
file="/var/log/messages"
${file%/*}: /var/log
${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
示例:
url=http://www.magedu.com:80
${url##*:} 80
${url%%:*} http       
 查找并删除
${var/pattern}:删除var表示的字符串中第一次被pattern匹配到的字符串
${var//pattern}:删除var表示的字符串中所有被pattern匹配到的字符串
${var/#pattern}:删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
字符大小写转换
${var^^}:把var中的所有小写字母转换为大写
${var,,}:把var中的所有大写字母转换为小写 

shell正则应用

第1章 shell正则应用

正则表达式regular expression, RE是一种字符模式,用于在查找过程中匹配指定的字符。

在大多数程序里,正则表达式都被置于两个正斜杠之间;例如/l[oO]ve/就是由正斜杠界定的正则表达式,它将匹配被查找的行中任何位置出现的相同模式。在正则表达式中,元字符是最重要的概念。

正则表达式的作用

>1.Linux正则表达式grep,sed,awk

2.大量的字符串文件需要进行配置,而且是非交互式的

3.过滤相关的字符串,匹配字符串,打印字符串

正则表达式注意事项

>1.正则表达式应用非常广泛,存在于各种语言中,例如:php,python,java等。

2.正则表达式和通配符特殊字符是有本质区别的。

3.要想学好grep、sed、awk首先就要掌握正则表达式。

1.1 基础正则表达式

元字符意义BRE,正则表达式实际就是一些特殊字符,赋予了他特定的含义。

正则表达式 描述

\    转义符,将特殊字符进行转义,忽略其特殊意义

^    匹配行首,awk中,^则是匹配字符串的开始 

$    匹配行尾,awk中,$则是匹配字符串的结尾

^$   表示空行

.    匹配除换行符\n之外的任意单个字符

.*   匹配所有

[ ]   匹配包含在[字符]之中的任意一个字符

[^ ]  匹配[^字符]之外的任意一个字符

[ - ]  匹配[]中指定范围内的任意一个字符

?    匹配之前的项1次或者0次

\+    匹配之前的项1次或者多次

\*    匹配之前的项0次或者多次, .*

()   匹配表达式,创建一个用于匹配的子串

{ n }  匹配之前的项n次,n是可以为0的正整数

{n,}  之前的项至少需要匹配n次

{n,m}  指定之前的项至少匹配n次,最多匹配m次,n<=m

|    交替匹配|两边的任意一项ab(c|d)匹配abc或abd

特定字符:

>[[:space:]]  空格
[[:digit:]]   [0-9]
[[:lower:]]   [a-z]
[[:upper:]]   [A-Z]
[[:alpha:]]   [a-Z]

1.2 grep正则表达式实战

I am lizhenya teacher!
I teach linux.
test
I like badminton ball ,billiard ball and chinese chess!
my blog is http: blog.51cto.com
our site is http:www.lizhenya.com
my qq num is 593528156
not 572891888887. 

1.2.1 过滤以m开头的行

[root@Shell ~]#** grep "^m" test.txt
my blog is http: blog.51cto.com
my qq num is 572891887.

1.2.2 过滤以m结尾的行

[root@Shell ~]#** grep "m$" test.txt
my blog is http: blog.51cto.com
our site is http:www.lizhenya.com

1.2.3 排除空行, 并打印行号

[root@student ~]#grep -vn "^$" lizhenya.txt

1.2.4 匹配任意一个字符,不包括空行

[root@student ~]# grep "." lizhenya.txt

1.2.5 匹配所有

[root@student ~]# grep ".*" lizhenya.txt

1.2.6 匹配单个任意字符

 [root@node1 ~]# grep "lizhen.a" lizhenya.txt

1.2.7 以点结尾的

 [root@student ~]# grep "\.$" lizhenya.txt

1.2.8 精确匹配到

 [root@student ~]# grep -o "8*" lizhenya.txt

1.2.9 匹配有abc的行

 [root@student ~]# grep "[abc]" lizhenya.txt

1.2.10 匹配数字所在的行"[0-9]"

 [root@student ~]# grep "[0-9]" lizhenya.txt

1.2.11 匹配所有小写字母[a-z]

[root@student ~]# grep "[a-z]" lizhenya.txt

1.2.12 重复8三次

 [root@student ~]# grep "8\{3\}" lizhenya.txt

1.2.13 重复数字8, 3-5次

 [root@student ~]# grep -E "8{3,5}" test.txt

1.2.14 至少1次或1次以上

 [root@student ~]# grep -E "8{1,}" lizhenya.txt

1.3 sed文本处理

sed是一个流编辑器, 非交互式的编辑器,它一次处理一行内容.

处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space)

接着用 sed 命令处理缓冲区中的内容,处理完成后, 把缓冲区的内容送往屏幕。

接着处理下一行,这样不断重复,直到文件末尾。

文件内容并没有改变,除非你 使用重定向存储输出。

Sed 要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

1.3.1 sed命令格式

sed [options] 'command' file(s)

1.3.2 sed正则使用

与 grep一样,sed 在文件中查找模式时也可以使用正则表达式(RE)和各种元字符。

正则表达式是括在斜杠间的模式,用于查找和替换,以下是sed支持的元字符。

使用基本元字符集 ^, $, ., *, [], [^], < >, (), {}
使用扩展元字符集 ?, +, { }, |, ( )
使用扩展元字符的方式 + sed -r

1.3.3 sed选项参数

-e 允许多项编辑
-n 取消默认的输出
-i 直接修改对应文件
-r 支持扩展元字符

1.3.4 sed`内置命令参数

a 在当前行后添加一行或多行
c 在当前行进行替换修改
d 在当前行进行删除操作
i 在当前行之前插入文本
p 打印匹配的行或指定行
n 读入下一输入行,从下一条命令进行处理
! 对所选行以外的所有行应用命令
h 把模式空间里的内容重定向到暂存缓冲区
H 把模式空间里的内容追加到暂存缓冲区
g 取出暂存缓冲区的内容,将其复制到模式空间,覆盖该处原有内容
G 取出暂存缓冲区的内容,将其复制到模式空间,追加在原有内容后面

1.3.5 先删除行,然后管道给后面的sed进行替换

[root@Shell ~]# sed '1,9d' passwd |sed 's#root#alex#g'

1.3.6 使用-e进行多次编辑修改操作

[root@Shell ~]# sed -e '1,9d' -e 's#root#alex#g' passwd

1.3.7 打印命令p

1.3.7.1 打印匹配halt的行

[root@Shell ~]# sed -n '/halt/p' passwd

1.3.7.2 打印第二行的内容

[root@Shell ~]#** sed -n '2p' passwd
bin:x:1:1:bin:/bin:/sbin/nologin

1.3.7.3 打印最后一行

[root@Shell ~]# sed -n '$p' passwd

1.3.8 追加命令a

1.3.8.1 给30行添加配置 \t tab键(需要转义) \n 换行符

[root@Shell ~]# sed -i '30a listen 80;' passwd

1.3.9 修改命令c

1.3.9.1 指定某行进行内容替换

[root@Shell ~]# sed -i '7c SELINUX=Disabled' /etc/selinux/config

1.3.9.2 正则匹配对应内容, 然后进行替换

[root@Shell ~]# sed -i '/^SELINUX=/c SELINUX=Disabled' /etc/selinux/config

1.3.9.3 非交互式修改指定的配置文件

[root@Shell ~]# sed -ri '/UseDNS/cUseDNS no' /etc/ssh/sshd_config
[root@Shell ~]# sed -ri '/GSSAPIAuthentication/c#GSSAPIAuthentication no' /etc/ssh/sshd_config
[root@Shell ~]# sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config

1.3.10 删除命令d

1.3.10.1 指定删除第三行, 但不会改变文件内容

[root@Shell ~]# sed '3d' passwd
[root@Shell ~]# sed '3{d}' passwd

1.3.10.2 从第三行删除到最后一行

[root@Shell ~]# sed '3,$d' passwd

1.3.10.3 删除最后一行

[root@Shell ~]# sed '$d' passwd

1.3.10.4 删除所有的行

[root@Shell ~]# sed '1,$d' passwd

1.3.10.5 匹配正则进行该行删除

[root@Shell ~]# sed /mail/d passwd

1.3.11 插入命令i

1.3.11.1 在文件的某一行上面添加内容

[root@Shell ~]# sed -i '30i listen 80;' passwd

1.3.12 写文件命令w

1.3.12.1 将匹配到的行写入到新文件中

[root@Shell ~]# sed -n '/root/w newfile' passwd

1.3.12.2 将passwd文件的第二行写入到newfile中

[root@Shell ~]# sed -n '2w newfile' passwd

1.3.13 获取下一行命令n

1.3.13.1 匹配root的行, 删除root行的下一列

[root@Shell ~]# sed '/root/{n;d}' passwd

1.3.13.2 替换匹配root行的下一列

[root@Shell ~]# sed '/root/{n; s/bin/test/}' passwd

1.3.14 暂存和取用命令h H g G

g: 将hold space中的内容拷贝到pattern space中,原来pattern space里的内容被覆盖
G:将hold space中的内容append到pattern space\n后
h: 将pattern space中的内容拷贝到hold space中,原来hold space里的内容被覆盖
H: 将pattern space中的内容append到hold space\n后
d: 删除pattern中的所有行,并读入下一新行到pattern中
D: 删除multiline pattern中的第一行,读入下一行

1.3.14.1 将第一行的写入到暂存区, 替换最后一行的内容

[root@Shell ~]# sed '1h;$g' /etc/hosts

1.3.14.2 将第一行的写入到暂存区, 在最后一行调用暂存区的内容

[root@Shell ~]# sed '1h;$G' /etc/hosts

1.3.14.3 将第一行的内容删除但保留至暂存区, 在最后一行调用暂存区内容追加至于尾部

[root@Shell ~]# sed -r '1{h;d};$G' /etc/hosts

1.3.14.4 将第一行的内容写入至暂存区, 从第二行开始进行重定向替换

[root@Shell ~]# sed -r '1h;2,$g' /etc/hosts

1.3.14.5 将第一行重定向至暂存区, 2-3行追加至暂存区, 最后追加调用暂存区的内容

[root@Shell ~]# sed -r '1h; 2,3H; $G' /etc/hosts

4 图解sed ‘1!G;h;$!d’ file

1 sed简介

sed是面向流的行编辑器。所谓面向流,是指接受标准输入的输入,输出内容到标准输出上。sed编辑器逐行处理文件(或输入),并将结果发送到屏幕。

具体过程如下: sed将处理的行读入到一个临时缓存区中(也称为模式空间pattern space),sed中的命令依次执行,直到所有命令执行完毕,完成后把该行发送到屏幕上,清理pattern space中的内容;接着重复刚才的动作,读入下一行,直到文件处理结束。

sed每处理完一行就将其从pattern space中删除,然后将下一行读入,进行处理和显示。处理完输入 文件的最后一行后,sed便结束运行。sed把每一行都存在临时缓冲区中,对这个副本进行编辑,所以不会修改原文件。

2.什么是Pattern Space,Hold Space

Pattern Space相当于车间,sed把流内容在这里进行处理,Hold Space相当于仓库,加工的半成品在这里进行临时存储。

由于各种原因,比如用户希望在某个条件下脚本中的某个命令被执行,或者希望模式空间得到保存以便下一次处理,都有可能使得sed在处理文件的时候不按照正常的流程来进行。这个时候,sed设置了一些高级命令来满足用户的要求。

一些高级命令

g: 将hold space中的内容拷贝到pattern space中,原来pattern space里的内容被覆盖
G:将hold space中的内容append到pattern space\n后
h: 将pattern space中的内容拷贝到hold space中,原来hold space里的内容被覆盖
H: 将pattern space中的内容append到hold space\n后
d: 删除pattern中的所有行,并读入下一新行到pattern中
D: 删除multiline pattern中的第一行,不读入下一行

1!G 第一行不执行G命令,从第二行开始执行

$!d 最后一行不删除

[root@localhost test]# cat file
1 1 1
2 2 2
3 3 3
[root@localhost test]# sed '1!G;h;$!d' file
3 3 3
2 2 2
1 1 1

图中P代表Pattern Space,H代表Hold Space。绿色代表pattern space中的数据,蓝色代表hold space中的数据。

1.3.15 反向选择命令!

1.3.15.1 除了第三行,其他全部删除

[root@Shell ~]# sed -r '3!d' /etc/hosts

1.3.16 sed匹配替换

s 替换命令标志

g 行内全局替换

i 忽略替换大小写

替换命令s

1.3.16.1 替换每行出现的第一个root

[root@Shell ~]# sed 's/root/alice/' passwd

1.3.16.2 替换以root开头的行

[root@Shell ~]# sed 's/^root/alice/' passwd

1.3.16.3 查找匹配到的行, 在匹配的行后面添加内容

[root@Shell ~]# sed -r 's/[0-9][0-9]$/& .5/' passwd

1.3.16.4 匹配包含有root的行进行替换

[root@Shell ~]# sed -r 's/root/alice/g' passwd

1.3.16.5 匹配包含有root的行进行替换,忽略大小写

[root@Shell ~]# sed -r 's/root/alice/gi' /etc/passwd

1.3.16.6 后向引用

[root@Shell ~]# sed -r 's#(Roo)#\1-alice#g' passwd
[root@Shell ~]# ifconfig eth0|sed -n '2p'|sed -r 's#(^.*et) (.*) (net.*$)#\2#g'

示例文件内容如下:

[root@lzy ~]# vim a.txt
/etc/abc/456
etc

删除文本中的内容,需加转义

[root@Shell ~]# sed -r '\/etc\/abc\/456/d' a.txt

如果碰到/符号, 建议使用#符替换

[root@Shell ~]# sed -r 's#/etc/abc/456#/dev/null#g' a.txt
[root@Shell ~]# sed -r 's@/etc/abc/456@/dev/null@' a.txt 

1.3.17 删除文件

1.3.17.1 删除配置文件中#号开头的注释行, 如果碰到tab或空格是无法删除

[root@Shell ~]# sed '/^#/d' file

1.3.17.2 删除配置文件中含有tab键的注释行

[root@Shell ~]# sed -r '/^[ \t]*#/d' file

1.3.17.3 删除无内容空行

[root@Shell ~]# sed -r '/^[ \t]*$/d' file

1.3.17.4 删除注释行及空行

[root@Shell ~]# sed -r '/^[ \t]*#/d; /^[ \t]*$/d' /etc/vsftpd/vsftpd.conf
[root@Shell ~]# sed -r '/^[ \t]*#|^[ \t]*$/d' /etc/vsftpd/vsftpd.conf
[root@Shell ~]# sed -r '/^[ \t]*($|#)/d' /etc/vsftpd/vsftpd.conf 

1.3.18 给文件行添加注释

1.3.18.1 将第二行到第六行加上注释信息

[root@Shell ~]# sed '2,6s/^/#/' passwd

1.3.18.2 将第二行到第六行最前面添加#注释符

[root@Shell ~]# sed -r '2,6s/.*/#&/' passwd

1.3.19 添加#注释符

[root@Shell ~]# sed -r '3,$ s/^#*/#/' passwd
# sed -r '30,50s/^[ \t]*#*/#/' /etc/nginx.conf 
# sed -r '2,8s/^[ \t#]*/#/' /etc/nginx.conf

1.4 Awk文本处理

awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。

awk数据可以来自标准输入、一个或多个文件,或其它命令的输出。

awk通常是配合脚本进行使用, 是一个强大的文本处理工具。

1.4.1 awk 的处理数据的方式

1.进行逐行扫描文件, 从第一行到最后一行

2.寻找匹配的特定模式的行,在行上进行操作

3.如果没有指定处理动作,则把匹配的行显示到标准输出

4.如果没有指定模式,则所有被操作的行都被处理

1.4.2 awk的语法格式

awk [options] 'commands' filenames
awk [options] -f awk-script-file filenames

1.4.3 选项 options

-F 定义输入字段分隔符,默认的分隔符, 空格或tab键

命令 command

行处理前 行处理 行处理后

BEGIN{} {} END{}

BEGIN发生在读文件之前

[root@Shell ~]# awk 'BEGIN{print 1/2}'
0.5

BEGIN在行处理前, 修改字段分隔符

[root@Shell ~]# awk 'BEGIN{FS=":"} {print $1}' /etc/passwd

BEGIN在行处理前, 修改字段读入和输出分隔符

[root@Shell ~]# awk 'BEGIN{FS=":";OFS="---"} {print $1,$2}' /etc/passwd
[root@Shell ~]# awk 'BEGIN{print 1/2} {print "ok"} END {print "Game Over"}' /etc/hosts
0.5
ok
ok
ok
Game Over

1.4.4 awk命令格式

1.4.4.1 匹配 awk 'pattern' filename

[root@Shell ~]# awk '/root/' /etc/passwd

1.4.4.2 处理动作 awk '{action}' filename

[root@Shell ~]# awk -F: '{print $1}' /etc/passwd

1.4.4.3 匹配+处理动作 awk 'pattern {action}' filename

[root@Shell ~]# awk -F ':' '/root/ {print $1,$3}' /etc/passwd
[root@Shell ~]# awk 'BEGIN{FS=":"} /root/{print $1,$3}' /etc/passwd

1.4.4.4 判断大于多少则输出什么内容 command |awk 'pattern {action}'

[root@Shell ~]# df |awk '/\/$/ {if ($3>50000) print $4}'

1.4.5 Awk工作原理

# awk -F: '{print $1,$3}' /etc/passwd

1.awk将文件中的每一行作为输入, 并将每一行赋给内部变量$0, 以换行符结束

2.awk开始进行字段分解,每个字段存储在已编号的变量中,从$1开始[默认空格分割]

3.awk默认字段分隔符是由内部FS变量来确定, 可以使用-F修订

4.awk行处理时使用了print函数打印分割后的字段

5.awk在打印后的字段加上空格,因为$1,$3 之间有一个逗号。逗号被映射至OFS内部变量中,称为输出字段分隔符, OFS默认为空格.

6.awk输出之后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕.

1.4.6 Awk内部变量

*1.$0保存当前记录的内容*

[root@Shell ~]# awk '{print $0}' /etc/passwd

*2.NR记录输入总的编号(行号)*

[root@Shell ~]# awk '{print NR,$0}' /etc/passwd
[root@Shell ~]# awk 'NR<=3' /etc/passwd

*3.FNR当前输入文件的编号(行号)*

[root@Shell ~]# awk '{print NR,$0}' /etc/passwd /etc/hosts
[root@Shell ~]# awk '{print FNR,$0}' /etc/passwd /etc/hosts

*4.NF保存行的最后一列*

[root@Shell ~]# awk -F ":" '{print NF,$NF}' /etc/passwd /etc/hosts

5.FS指定字段分割符, 默认是空格

以冒号作为字段分隔符

[root@Shell ~]# awk -F: '/root/{print $1,$3}' /etc/passwd
[root@Shell ~]# awk 'BEGIN{FS=":"} {print $1,$3}' /etc/passwd

以空格冒号tab作为字段分割

[root@Shell ~]# awk -F'[ :\t]' '{print $1,$2,$3}' /etc/passwd

6.OFS指定输出字段分隔符*

逗号映射为OFS, 初始情况下OFS变量是空格

[root@Shell ~]# awk -F: '/root/{print $1,$2,$3,$4}' /etc/passwd
[root@Shell ~]# awk 'BEGIN{FS=":"; OFS="+++"} /^root/{print $1,$2}' /etc/passwd

7.RS输入记录分隔符,默认为换行符[了解]

[root@Shell ~]# awk -F: 'BEGIN{RS=" "} {print $0}' /etc/hosts

8.ORS将文件以空格为分割每一行合并为一行[了解]*

[root@Shell ~]# awk -F: 'BEGIN{ORS=" "} {print $0}' /etc/hosts

*9.print格式化输出函数*

[root@Shell ~]# date|awk '{print $2,"5月份""\n",$NF,"今年"}'
[root@Shell ~]# awk -F: '{print "用户是:" $1 "\t 用户uid: " $3 "\t 用户gid:" $4}' /etc/passwd

printf 函数

[root@Shell ~]# awk -F: '{printf "%-15s %-10s %-15s\n", $1, $2, $3}' /etc/passwd

%s 字符类型

%d 数值类型

占 15 字符

- 表示左对齐,默认是右对齐

printf 默认不会在行尾自动换行,加\n

1.4.7 Awk模式动作

awk语句都由模式和动作组成。

模式部分决定动作语句何时触发及触发事件。

如果省略模式部分,动作将时刻保持执行状态。模式可以是条件语句或复合语句或正则表达式。

*1.正则表达式*

匹配记录(整行)

[root@Shell ~]# awk '/^root/' /etc/passwd
[root@Shell ~]# awk '$0 ~ /^root/' /etc/passwd

匹配字段:匹配操作符(~ !~)

[root@Shell ~]# awk '$1~/^root/' /etc/passwd
[root@Shell ~]# awk '$NF !~ /bash$/' /etc/passwd

*2.比较表达式*

比较表达式采用对文本进行比较,只有当条件为真,才执行指定的动作。

比较表达式使用关系运算符,用于比较数字与字符串。

关系运算符

运算符 含义 示例

< 小于 x<y

<= 小于或等于 x<=y

== 等于 x==y

!= 不等于 x!=y

>= 大于等于 x>=y

> 大于 x>y

uid为0的列出来

[root@Shell ~]# awk -F ":" '$3==0' /etc/passwd

uid小于10的全部列出来

[root@Shell ~]# awk -F: '$3 < 10' /etc/passwd

用户登陆的shell等于/bin/bash

[root@Shell ~]# awk -F: '$7 == "/bin/bash" ' /etc/passwd

第一列为alice的列出来

[root@Shell ~]# awk -F: '$1 == "alice" ' /etc/passwd

为alice的用户列出来

[root@Shell ~]# awk -F: '$1 ~ /alice/ ' /etc/passwd 
[root@Shell ~]# awk -F: '$1 !~ /alice/ ' /etc/passwd

磁盘使用率大于多少则,则打印可用的值

[root@Shell ~]# df |awk '/\/$/'|awk '$3>1000000 {print $4}'

*3.条件表达式*

[root@Shell ~]# awk -F: '$3>300 {print $0}' /etc/passwd
[root@Shell ~]# awk -F: '{if($3>300) print $0}' /etc/passwd
[root@Shell ~]# awk -F: '{if($3>5555){print $3} else {print $1}}' /etc/passwd

*4.运算表达式*

[root@Shell ~]# awk -F: '$3 * 10 > 500000' /etc/passwd
[root@Shell ~]# awk -F: 'BEGIN{OFS="--"} { if($3*10>50000) {print $1,$3} } END {print "打印ok"}' /etc/passwd
[root@Shell ~]# awk '/southem/{print $5 + 10}' datafile 
[root@Shell ~]# awk '/southem/{print $5 + 10.56}' datafile
[root@Shell ~]# awk '/southem/{print $8 - 10}' datafile 
[root@Shell ~]# awk '/southem/{print $8 / 2 }' datafile 
[root@Shell ~]# awk '/southem/{print $8 * 2 }' datafile 
[root@Shell ~]# awk '/southem/{print $8 % 2 }' datafile

*5.逻辑操作符和复合模式*

&&逻辑与 || 逻辑或 !逻辑非

匹配用户名为root并且打印uid小于15的行

[root@Shell ~]# awk -F: '$1~/root/ && $3<=15' /etc/passwd

匹配用户名为root或uid大于5000

[root@Shell ~]# awk -F: '$1~/root/ || $3>=5000' /etc/passwd

1.4.8 awk示例1

# awk '/west/' datafile 
# awk '/^north/' datafile 
# awk '$3 ~ /^north/' datafile 
# awk '/^(no|so)/' datafile 
# awk '{print $3,$2}' datafile
# awk '{print $3 $2}' datafile 
# awk '{print $0}' datafile 
# awk '{print "Number of fields: "NF}' datafile 
# awk '/northeast/{print $3,$2}' datafile
# awk '/^[ns]/{print $1}' datafile 
# awk '$5 ~ /\. [7-9]+/' datafile 
# awk '$2 !~ /E/{print $1,$2}' datafile 
# awk '$3 ~ /^Joel/{print $3 "is a nice boy."}' datafile 
# awk '$8 ~ /[0-9][0-9]$/{print $8}' datafile
# awk '$4 ~ /Chin$/{print "The price is $" $8 "."}' datafile 
# awk '/Tj/{print $0}' datafile 
# awk -F: '{print "Number of fields: "NF}' /etc/passwd 
# awk -F"[ :]" '{print NF}' /etc/passwd 

1.4.9 awk示例2

[root@Shell ~]# cat b.txt 
lzy lizhenya:is a:good boy!
[root@Shell ~]# awk '{print NF}' b.txt
4
[root@Shell ~]# awk -F ':' '{print NF}' b.txt
3
[root@Shell ~]# awk -F"[ :]" '{print NF}' .txt
6

1.4.10 Awk条件判断

if语句格式:{ if(表达式){语句;语句;... }}

打印当前管理员用户名称

[root@Shell ~]# awk -F: '{ if($3==0){print $1 "is adminisitrator"} }' /etc/passwd

统计系统用户数量

[root@Shell ~]# awk -F: '{ if($3>0 && $3<1000){i++}} END {print i}' /etc/passwd

统计普通用户数量

[root@Shell ~]# awk -F: '{ if($3>1000){i++}} END {print i}' /etc/passwd 

if...else 语句格式: {if(表达式){语句;语句;... }else{语句;语句;...}}

# awk -F: '{if($3==0){print $1} else {print $7}}' /etc/passwd
# awk -F: '{if($3==0) {count++} else{i++} }' /etc/passwd
# awk -F: '{if($3==0){count++} else{i++}} 
END{print " 管理员个数: "count ; print " 系统用户数: "i}' /etc/passwd

if...else if...else 语句格式:

{if(表达式 1){语句;语句;... }else if(表达式 2){语句;语句;. .. }else{语句;语句;... }}

[root@Shell ~]# awk -F: '{ if($3==0){i++} else if($3>0 && $3<1000){j++} else if($3>1000) {k++}} 
END {print i;print j;print k}' /etc/passwd

[root@Shell ~]# awk -F: '{ if($3==0){i++} else if($3>0 && $3<1000){j++} 
else if($3>1000) {k++}}
 END {print "管理员个数"i; print "系统用户个数" j; print "系统用户个 数" }' /etc/passwd
管理员个数1
系统用户个数29
0系统用户个数69

 

1.4.11 Awk循环语句

1.4.11.1 while循环

[root@Shell ~]# awk 'BEGIN{ i=1; while(i<=10){print i; i++} }'
[root@Shell ~]# awk -F: '{i=1; while(i<=NF){print $i; i++}}' /etc/passwd
[root@Shell ~]# awk -F: '{i=1; while(i<=10) {print $0; i++}}' /etc/passwd
[root@Shell ~]#cat b.txt
111 222
333 444 555
666 777 888 999
[root@Shell ~]# awk '{i=1; while(i<=NF){print $i; i++}}' b.txt

 

1.4.11.2 for循环

C 风格 for
[root@Shell ~]# awk 'BEGIN{for(i=1;i<=5;i++){print i} }' 
将每行打印 10 次
[root@Shell ~]# awk -F: '{ for(i=1;i<=10;i++) {print $0} }' passwd
[root@Shell ~]# awk -F: '{ for(i=1;i<=NF;i++) {print $i} }' passwd

1.4.12 awk数组概述

将需要统计的某个字段作为数组的索引,然后对索引进行遍历

1.4.12.1 统计/etc/passwd中各种类型shell 的数量*
# awk -F: '{shells[$NF]++} END{ for(i in shells){print i,shells[i]} }' /etc/passwd
1.4.12.2 站访问状态统计<当前时实状态ss>*
[root@Shell ~]# ss -an|awk '/:80/{tcp[$2]++} END {for(i in tcp){print i,tcp[i]}}'
1.4.12.3 统计当前访问的每个IP的数量<当前时实状态 netstat,ss>*
[root@Shell ~]# ss -an|awk -F ':' '/:80/{ips[$(NF-1)]++}
 END {for(i in ips){print i,ips[i]}}'

1.4.13 Awk数组案例

Nginx日志分析,日志格式如下:

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
           '$status $body_bytes_sent "$http_referer" '
           '"$http_user_agent" "$http_x_forwarded_for"';
52.55.21.59 - - [25/Jan/2018:14:55:36 +0800] "GET /feed/ HTTP/1.1" 404 162 "https:www.google.com/" 
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52" "-"

 

1.4.13.1 统计当天的PV量

[root@Shell ~]# grep "25/Jan/2018" log.bjstack.log |wc -l
[root@Shell ~]# awk "/25\/Jan\/2018/" log.bjstack.log |wc -l
[root@Shell ~]# awk '/25\/Jan\/2018/ {ips[$1]++} END {for(i in ips) {sum+=ips[i]} {print sum}}' log.bjstack.log

1.4.13.2 统计pv量

[root@Shell ~]# awk '$4>="[25/Jan/2018:15:00:00" && $4<="[25/Jan/2018:19:00:00 {print $0}"' log.bjstack.log |wc -l

1.4.13.3 统计一天内访问最多的10个IP*

[root@Shell ~]# awk '/25\/Jan\/2018/ {ips[$1]++} END {for(i in ips){ print ips[i],i}}' log.bjstack.log |sort -rn|head

1.4.13.4 统计访问次数最多的10个IP

[root@Shell ~]# awk '$4>="[25/Jan/2018:15:00:00" && $4<="[25/Jan/2018:19:00:00"' log.bjstack.log 
|awk '{ips[$1]++} END {for(i in ips){print ips[i],i}}'|sort -rn|head

1.4.13.5 统计当天访问大于100次的IP*

[root@Shell ~]# awk '/25\/Jan\/2018/ {ips[$1]++} END {for(i in ips){if(ips[i]>10){print i,ips[i]}}}' log.bjstack.log

1.4.13.6 统计当天访问最多的10个页面($request top 10)*

[root@Shell ~]# awk '/25\/Jan\/2018/ {request[$7]++} END {for(i in request){print request[i],i}}' log.bjstack.log |sort -rn|head

1.4.13.7 统计当天每个URL访问内容总大小($body_bytes_sent)*

[root@Shell ~]# awk '/25\/Jan\/2018/ {request[$7]++;size[$7]+=$10} END {for(i in request){print request[i],i,size[i]}}' log.bjstack.log |sort -rn|head

1.4.13.8 统计当天每个IP访问状态码数量($status)*

[root@Shell ~]# awk '{ip_code[$1 " " $9]++} END {for(i in ip_code){print ip_code[i],i}}' log.bjstack.log|sort -rn|head

 

1.4.13.9 统计2018年01月25日,访问状态码为404及出现的次数($status)*

[root@Shell ~]# grep "404" log.bjstack.log |wc -l
[root@Shell ~]# awk '{if($9=="404") code[$9]++}
 END {for(i in code){print i,code[i]}}' log.bjstack.log

1.4.13.10 统计2018年01月25日,8:30-9:00访问状态码是404*

[root@Shell ~]# awk '$4>="[25/Jan/2018:15:00:00" && $4<="[25/Jan/2018:19:00:00" && $9=="404" {code[$9]++} 
END {for(i in code){print i,code[i]}}' log.bjstack.log [root@Shell ~]# awk '$9=="404" {code[$9]++} END {for(i in code){print i,code[i]}}' log.bjstack.log

 

1.4.13.11 统计2018年01月25日,各种状态码数量,统计状态码出现的次数

[root@Shell ~]# awk '{code[$9]++} END {for(i in code){print i,code[i]}}' log.bjstack.log
[root@Shell ~]# awk '{if($9>=100 && $9<200) {i++}
else if ($9>=200 && $9<300) {j++}
else if ($9>=300 && $9<400) {k++}
else if ($9>=400 && $9<500) {n++}
else if($9>=500) {p++}}
END{print i,j,k,n,p,i+j+k+n+p}' log.bjstack.log

 

 

 

 

推荐阅读