shell脚本编程

shell概述

  1. Linux内核与用户之间的解释器程序<br /> 通常指 /bin/bash,负责向内核翻译及传达用户/程序指令,相当于操作系统的“外壳”

shell的使用方式

  - **交互式(命令行)**

人工干预、智能化程度高<br />    逐条解释执行、效率低

  - **非交互式(脚本)**<br />需提前设计、智能化难度大<br />批量执行、效率高<br />方便在后台悄悄的运行

常见的shell程序种类

[root@svr7 ~]# cat /etc/shells //查看所有解释器
/bin/sh //多数Unix默认的Shell
/bin/bash //多数Linux默认的Shell
/sbin/nologin //非登陆Shell
/bin/tcsh
/bin/csh
[root@svr7 ~]# sh //切换成sh解释器
sh-4.2# ls //利用sh解释器输入命令
sh-4.2#exit //退出sh解释器
[root@svr7 ~]# yum -y install ksh //安装新解释器
[root@svr7 ~]# ksh //进入新解释器

bash基本特性(优点)

快捷键、tab键补齐、历史命令、支持别名、重定向、管道操作

shell脚本

提前写好可执行的语句,可以完成特定任务的文件按顺序、批量化执行
解释型程序

shell脚本编写规范

1,声明解释器
#!/bin/bash
2,编写注释
# 可以描述脚本功能、变量作用等信息
3,执行指令
ls、cd……

脚本执行的方式

1.作为命令字:为脚本添加x权限,使用相对或者绝对路径执行

chmod u+x test01.sh
./test01.sh 或者 /opt/test01.sh
用户(root)---bash---bash---echo

2.使用解释器程序执行脚本,无需x权限

bash test01.sh
用户(root)—-bash —-bash—-echo 会开启新的子进程

3.使用source命令执行脚本,无需x权限

source test01.sh
用户(root)—-bash—-echo 不会开启新的子进程

4.调试Shell脚本:sh -x 开启调试模式

直接观察执行中的输出、报错信息
在可能出错的地方设置echo

简单的脚本示例

编写脚本,搭建yum

#!/bin/bash
# 搭建yum软件仓库
mkdir /abc
mount /dev/cdrom /abc
rm -rf /etc/yum.repos.d/*.repo
echo "[abc]
name=abcabc
baseurl=file:///abc
enabled=1
gpgcheck=0" > /etc/yum.repos.d/abc.repo

编写脚本,安装网站服务并开启,浏览网站时显示”web-test~~”

#!/bin/bash
# 搭建网站
yum -y install httpd
echo "web-test~~" > /var/www/html/index.html
systemctl restart httpd

编写脚本,安装ftp服务并开启且设置为开机自启

重定向标准输出
2> 重定向错误输出
&> 重定向所有输出

#!/bin/bash
yum -y install vsftpd &> /dev/null //将不需要的信息扔黑洞
systemctl restart vsftpd
systemctl enable vsftpd

shell变量

变量和常量

变量 使用固定的名称存放可能发生变化的值,可以提高脚本的灵活度、适应力,方便重复使用
常量 固定不变的内容

变量的设置和取消

  • 定义/赋值变量
    变量名=变量值
  • 查看变量
    引用变量值(调用变量):$变量名
    查看变量:echo $变量名、echo ${变量名}
    未定义的变量无取值;变量名以混淆时,以{}界定
  • 取消变量
    退出定义变量的Shell环境时,变量会自动失效
    手动取消:unset 变量名、变量名=

    变量的种类

    变量的分类角度

  • 存储类型
    整数型(int)、浮点型(float)、双精度浮点型(double)、字符型(char)、…….

  • 使用类型

    image.png
    自定义变量

  • 名称可以用数字、字母、下划线,不能以数字开头,不能使用特殊符号,

  • 等号两边不能有空格,尽量不要使用关键字和特殊字符,
  • 给同一个变量多次赋值时,最后一次的赋值生效
    格式:变量名称=值
    a=10 //创建变量(对变量赋值),名字是a,值是10
    a=30 //再次赋值,之前的会被覆盖
    echo $a //调用变量时使用$符号
    unset a //取消变量的定义
    a= //将变量a赋值为空,效果同上
    echo ${a}RMB //变量名容易与后续字符发生混淆时使用大括号隔开
    

    环境变量:由系统提前定义好,使用时直接调用

    配置文件:/etc/profile、~/.bash_profile
    env:查看所有环境变量
    set:查看所有变量
    常见的环境变量:
    USER 当前用户名 UID当前用户的id号 HOME当前用户家目录
    HOSTNAME 主机名 SHELL当前用户的解释器 PWD当前位置
    PATH 存储命令的路径 PS1 一级提示符 PS2 二级提示符

    预定义变量:用来保存脚本程序的执行信息,直接使用这些变量,不能赋值

    image.png
$! 后台运行的最后一个进程的进程号(PID)
$@ 与$*相同,$@以”$1” … “$ 2”…”$n” 的形式输出所有参数

当 $* 和 $@ 不被双引号” “包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。
但是当它们被双引号” “包含时,就会有区别了:

  • “$*”会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
  • “$@”仍然将每个参数都看作一份数据,彼此之间是独立的。

    位置变量:在执行脚本时提供的命令行参数

    表示为$n,n为序号;$1,$2,……..$10

    变量的拓展应用:变量值及控制范围

    1)三种界定符:引号和反撇号

    “ “ 双引号 界定范围
    a=10 echo “$a” //正常调用变量
    ‘ ‘ 单引号 界定范围 屏蔽特殊符号的功能
    echo ‘$a’ //无法调用变量,$被屏蔽
    反撇号 获取命令的执行结果,还可以使用$( )实现相同效果
    a=date //仅仅将四个字母赋值给a
    a=date //将date执行结果赋值给a
    a=$(date) //效果同上

    2)read标准输入取值:read -p “提示信息” 变量名 -t 指定超时秒数

    #!/bin/bash
    read -p "请输入用户名" u //-p是可以定义提示信息,u相当于自
    定义变量名称,可以存储用户看到提示信息后输入的字符
    useradd $u
    read -p "请输入密码" n
    echo "$n" | passwd --stdin $u
    
    stty -echo 屏蔽回显
    stty echo 恢复回显
    再次改良脚本:
    #!/bin/bash
    read -p "请输入用户名" u
    useradd $u
    stty -echo //隐藏键盘输入内容,设置密码时不显示
    read -p "请输入密码" n
    stty echo 
    echo "$n" | passwd --stdin $u
    

    3)export声明全局变量

    局部变量 仅仅在当前解释器进程中使用的变量
    全局变量 解释器产生的子进程中也可以使用的变量
    a=10 //创建变量
    export a //将已有变量a发布为全局效果
    export b=20  //创建变量的同时发布为全局效果
    export -n  b //将全局变量恢复为局部变量
    #注意,测试时要在父进程创建变量,然后可以执行bash进入子进程测试效果,如果要返回父进程要执行exit指令
    

    shell运算

    expr运算工具(运算,输出)

    image.png
    格式:expr 整数1 运算符 整数2
    乘法操作应采用*转义,避免被作为Shell通配符

    $[ ] 运算(使用$(( )) 可以实现相同效果,只运算不输出)

    格式:$[整数1 运算符 整数2……….]
    乘号操作*无需转义,运算符两侧可以无空格
    引用变量可省略$ 符号
    计算结果替换表达式本身,可结合echo命令输出

    变量的自增/减等操作:$[ ]替换或者let命令完成

    使用let命令,不输出结果,专用于变量的创建或者变量的自增减
    变量的自增减:
    常规写法 主流写法

    let a=a+1 let a++ 变量a加1
    let a=a-1 let a— 变量a减1
    let a=a+10 let a+=10 变量a加10
    let a=a-10 let a-=10 变量a减10
    let a=a2 let a=2 变量a乘以2
    let a=a/2 let a/=2 变量a除以2
    let a=a%3 let a%=3 变量a除以3取余数

    小数运算:使用bc,可以进行小数计算

    Bash内建机制仅支持整数值运算,expr命令、$[]算式替换不支持小数的运算
    所以使用bc实现小数运算,支持高精度的数制运算,直接运行bc可进入交互式运算界面,quit退出,设置 scale=n 可约束小数位
    ]# echo “scale=2;10/3” | bc
    3.33

    shell条件测试:可以赋予脚本智能判断的效果

    语法格式:test 选项 参数 或 [ 表达式 ]

    1)字符串比较

    -z 字符串的值为空
    -n 字符串的值不为空,相当于!-z
    == 两边是否相等,相等时条件测试算成功
    != 两边是否不等,不相等时条件测试算成功
    test a == a //方式一:test ,使用常量判断,再用echo $?测试结果
    [ a == a ] //方式二:中括号,使用常量判断,再用echo $?测试结果
    [ $a != $b ] //使用变量判断两个变量的值是否不相等
    [ "$c" == $b ] //如果变量为空,有可能报错,加双引号可以避免
    

    2)逻辑组合

    条件 && 指令 条件成功才执行指令
    条件 || 指令 条件失败才执行指令
    A && B &&c A、B任务都成功算成功
    A || B ||c A、B 任务有一个成功算成功
    [ root == $USER ] || exit //判断当前用户名是否为root,如果判断失败,则执行exit
    [ $USER != root ] && exit //判断当前用户名是否不为root,如果判断成功,则执行exit
    [ root != $USER ] && echo "非管理员不能执行该脚本" && exit //之前的语句可以改进成这个方式
    

    3)数值比较

    -eq 是否相等 -ne 是否不等 -gt 是否大于
    -ge 是否大于等于 -lt 是否小于 -le 是否小于等于
    编写脚本,每2分钟检查服务器的用户数量,如果发生变化则发邮件通知管理员
    编写脚本前 echo “测试” | mail -s test root 测试下邮件功能是否正常
    yum -y install mailx postfix //如果不正常就安装软件包
    systemctl start postfix //然后启动服务
    #!/bin/bash
    x=$(cat /etc/passwd | wc -l) //将目前用户数量赋值给变量x
    [ $x -gt 29 ] && echo "用户数量发生变化,服务器可能被入侵" | mail -s test root //如果目前用户数量大于29,就发邮件给管理员,29是之前查看的用户数量
    chmod u+x test01.sh //然后给脚本加x权限
    crontab -e //编写计划任务
    */2 * * * * /opt/test01.sh //定义每2分钟执行脚本
    

    4)文件比较

    -e 判断文件是否存在,不关心类型
    -f 判断文件是否存在,必须是普通文件
    -d 判断文件是否存在,必须是目录
    -r 判断当前用户对文件是否有读权限,对root无效
    -w 判断当前用户对文件是否有写权限,对root无效
    -x 判断当前用户对文件是否有x权限,目录对root无效
    [ -d /etc/vsftpd ] && echo "yes"
    

    if条件选择

    单分支结构

    if 条件测试;then //满足条件的话就执行下面指令
    执行指令
    fi
    #!/bin/bash
    if [ $UID -eq 0 ];then
    echo "我是管理员"
    echo ok
    fi
    

    双分支结构

    if 条件测试;then //满足条件的话就执行下面指令
    执行指令A //指令可以是一条,也可以是多条
    else //否则,不满足条件的话就执行下面指令
    执行指令B
    fi
    #!/bin/bash
    if [ $UID -eq 0 ];then
    echo "我是管理员"    
    echo ok
    else
    echo "我不是管理员"
    echo no
    fi
    
    使用if双分支编写ping脚本
    #!/bin/bash
    ping -c 3 -i 0.2 -W 1 $1 &> /dev/null //使用$1位置变量
    更方便
    if [ $? -eq 0 ];then
    echo "通了!"
    else
    echo "不通!"
    fi
    

    多分枝结构

    if 条件测试;then //满足条件的话就执行下面A指令
    执行指令A //指令可以是一条,也可以是多条
    elif 条件测试;then //满足条件的话就执行下面B指令
    执行指令B
    elif 条件测试;then //满足条件的话就执行下面C指令
    执行指令C
    else //否则,不满足条件的话就执行下面指令
    执行指令
    fi
    #!/bin/bash
    read -p "月考分数:" x
    if [ $x -ge 90 ];then
    echo "优秀"
    elif [ $x -ge 80 ];then
    echo "良好"
    elif [ $x -ge 60 ];then
    echo "及格"
    else
    echo "不及格"
    fi
    

    case分支结构:功能类似if,编写时语句比if精简

    语法结构

    case 调用的变量名 in
    模式1) //检查变量的实际取值,与预设值匹配,则执行对应的操作
    指令;;
    模式2)
    指令;;
    *) //默认输出脚本用法
    指令
    esac

    脚本示例

    #!/bin/bash
    case $1 in
    t) //如果$1是t就执行touch任务
    touch $2;;
    m) //如果$1是m就执行mkdir任务
    mkdir $2;;
    r) //如果$1是r就执行rm任务
    rm -rf $2;;
    *)
    echo "请输入t或者m或者r"
    esac
    

    使用case分支编写控制nginx服务的脚本

    #!/bin/bash
    case $1 in
    start|kai) //输入start或者kai 就开启nginx
    /usr/local/nginx/sbin/nginx;;
    stop|STOP|s|guan) //输入stop或STOP或s或guan就关闭nginx
    /usr/local/nginx/sbin/nginx -s stop;;
    re|cq) //输入re或者cq就重启nginx
    /usr/local/nginx/sbin/nginx -s stop
    /usr/local/nginx/sbin/nginx;;
    cx|status) //输入cx或者status就查询nginx状态
    netstat -ntulp | grep -q nginx s //查询nginx,但不输出结果 -q选项是保持沉默
    [ $? -eq 0 ] && echo "nginx正在运行" || echo "nginx未开启";; //判断,如果上述任务成功,说明nginx服务开启,就显示"nginx正在运行",如果上述任务失败,说名nginx服务关闭,就显示"nginx未开启"
    *)
    echo "请输入start或stop"
    esac
    

    shell循环结构

    for循环:可以定义循环次数

    for 变量名称 in 值1 值2 值3 。。。。
    do
    任务
    done

    //此处变量名可以自定义,通常用i,值的多少决定了下面do与done之间的任务执行多少次,每个值之间有空格,这里是有3个值,所以就循环执行指令3次

    C语言风格的for循环

    for ((初值;条件;步长))
    do
    任务
    done

    编写脚本,可以测试192.168.2.1~192.168.2.10

    #!/bin/bash
    for i in {1..10}
    do
    ping -c 3 -i 0.2 -W 1 192.168.2.$i &> /dev/null
    if [ $? -eq 0 ];then
    echo "192.168.2.$i 通了"
    else
    echo "192.168.2.$i 不通"
    fi
    done
    

    改良版,可以统计最终通了和不通的数量

    #!/bin/bash
    x=0
    y=0
    for i in {1..10}
    do
    ping -c 3 -i 0.2 -W 1 192.168.2.$i &> /dev/null
    if [ $? -eq 0 ];then
    echo "192.168.2.$i 通了"
    let x++ //如果通了一台就把变量x+1
    else
    echo "192.168.2.$i 不通"
    let y++ //如果不通了一台就把变量y+1
    fi
    done
    echo "$x台通了,$y台不通"
    

    while循环:条件式循环

    while 条件测试
    do
    任务
    done

    //根据条件的结果决定是否要执行任务,条件测试成功的话就执行,如果失败立刻结束循环

    脚本示例

    ```shell

    !/bin/bash

    while : #冒号代表永远正确 do echo abc sleep 0.1 //休息0.1秒 done

!/bin/bash

n=10 while [ $n -ge 5 ] #可以根据条件决定是否要循环 do echo abc sleep 0.1 let n— //每次循环将n-1 done

<a name="TPC4x"></a>
#### 循环的控制
**exit** 可以终止循环,但脚本也终止<br />**break** 可以终止循环,继续循环后的任务<br />**continue** 可以终止当前循环,继续下一次循环<br />编写脚本,帮用户进行整数求和,如果用户输入0就结束求和,并输出结果
```shell
#!/bin/bash
x=0
while : //while循环后面写冒号代表永远正确可以无限循环
do
read -p "请输入一个整数求和(0是结束并输出结果):" n
[ -z $n ] && continue //如果n是空值则重新进行循环任务
[ $n -eq 0 ] && break //如果n是0则退出循环执行循环后任务
let x+=n //不断的将n的值保存在x里
done
echo "总和是$x"

shell函数:

可以利用一个名称存储公共的语句块,实现精简脚本方便后期调用的目的
使用函数的好处:使脚本代码更简洁,增强易读性;提高Shell脚本的执行效率
适用于比较复杂的启动/终止控制操作,方便在需要时多次调用

echo输出不同的颜色

echo -e “\033[34mABCD\033[0m” //使用echo输出不同颜色的内容

#!/bin/bash //定义不同颜色的函数
a(){
echo -e "\033[$1m$2\033[0m"
}
a 31/91 red //各种颜色的色块
a 32/92 green
a 33/93 yellow
a 34/94 blue
a 35/95 purple

函数语法格式

abc(){ //创建函数
指令
}
abc //调用函数

function 函数名 {
指令
}

利用函数完善之前控制nginx服务,使输出信息时有不同颜色

#!/bin/bash
a(){
echo -e "\033[$1m$2\033[0m"
}
case $1 in
.........
netstat -ntulp | grep -q nginx
[ $? -eq 0 ] && a 32 "nginx正在运行" || a 31 "nginx未开启";; //输出不同的颜色
*)
echo "请输入start或stop"
esac

文本的处理

字符串的处理

1)字符串的截取

${变量名称:截取位置:截取长度}

[root@proxy opt]# a=abcd
[root@proxy opt]# echo ${a:1:2} //从第二位截取两位
bc
[root@proxy opt]# echo ${a:0:2} //从头截取两位,截取位置的0可以不写
ab

编写脚本,可以生成8位随机字符用作密码

#!/bin/bash
x=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
for i in {1..8}
do
n=$[RANDOM%62] //得到0~61随机数存在变量n中
a=${x:n:1} //截取到1个随机字符,存在变量a中
c=$a$c //不断往c中追加,此时c相当于一个袋子
done
echo $c //最后喊出,得到8位长度随机字符串

2)字符串替换

${变量名/旧/新} //只替换第1个匹配结果
${变量名//旧/新} //替换全部匹配结果

示例

a=112233 
echo ${a/2/6} //将1个字符2换成6
echo ${a//2/6} //将所有字符2换成6
a=1122233
echo ${a/11/} //将11替换成空,相当于删除

3)字符串的删除,可以删除两边

${变量名#要删除的内容} 掐头,从左往右最短匹配
${变量名##要删除的内容} 掐头,从左往右最长匹配
${变量名%要删除的内容} 去尾,从右往左最短匹配
${变量名%%要删除的内容} 去尾,从右往左最长匹配

示例

a=abcdef //创建变量,作为素材
echo ${a#abc} //掐头,删除到abc
echo ${a%def} //去尾,删除到def
echo ${a#abcde} //掐头,删除到abcde
a=abcdefghijk //创建变量,作为素材
echo ${a#abcdefghi} //掐头,删除到i
echo ${a#*i} //效果同上,精简写法
echo ${a%defghijk} //去尾,删除到d
echo ${a%d*} //效果同上,精简写法

脚本应用:可以批量修改扩展名

#!/bin/bash
for i in $(ls *.txt) //找到所有的txt文件交给for循环
do
n=${i%.*} //用去尾的方法删除扩展名
mv $i $n.doc //再将源文件扩展名修改为doc
done

4)定义变量初值(备用)

${变量名:-初值}
若变量值非空则返回$变量的值,否则返回字符串“初值”

脚本应用:改密码脚本

#!/bin/bash
read -p "请输入用户名:" u
[ -z $u ] && echo "必须输入用户名!" && exit
useradd $uread -p "请输入密码(默认123456):" n
echo ${n:-123456} | passwd --stdin $u //n值为空时,启用默认密码123456

正则表达式

可以使用若干符号配合某工具对字符串进行增删改查操作

基本元字符

行首尾及单字匹配

image.png

grep ^root user //找以root开头的行
grep bash$ user //找以bash结尾的行
grep ^$ user //找空行
grep -v ^$ user //显示除了空行的内容
grep "." user //找任意单个字符,文档中每个字符都可以理解为任意字符
grep "r..t" user //找rt之间有2个任意字符的行
grep "r.t" user //找rt之间有1个任意字符的行,没有匹配内容,就无输出

未定匹配次数

image.png

grep "ro\{1,\}t" user //使用基本正则找o出现1次以及1次以上
egrep "ro{1,}t" user //使用扩展正则,效果同上,比较精简
egrep "ro+t" user //使用扩展正则,效果同上,最精简
grep "." user //找任意单个字符,文档中每个字符都可以理解为任意字符
grep "r..t" user //找rt之间有2个任意字符的行
grep "r.t" user //找rt之间有1个任意字符的行,没有匹配内容,就无输出
grep "*" user //错误用法,*号是匹配前一个字符任意次,不能单独使用
grep "ro*t" user //找rt,中间的o有没有都行,有几次都行
grep ".*" user //找任意,包括空行 .与*的组合在正则中相当于通配符的效果

{ }限定次数

image.png

grep "ro\{1,2\}t" user //找rt,中间的o可以有1~2个
grep "ro\{2,6\}t" user //找rt,中间的o可以有2~6个
grep "ro\{1,\}t" user //找rt,中间的o可以有1个以及1个以上
grep "ro\{3\}t" user //找rt,中间的o必须只有有3个

其他元字符

[ ]范围内单字匹配

image.png
[:space:]:空白字符
[:punct:]:标点符号
[:lower:]:小写字母
[:upper:]:大写字母
[:alpha:]:大小写字母
[:digit:]:数字
[:alnum:]:数字和大小写字母

grep "[root]" user //找r、o、t任意一个字符 
grep "[rot]" user //效果同上
grep "[^rot]" user //显示r或o或t以外的内容
grep "[0123456789]" user //找所有数字
grep "[0-9]" user //效果同上
grep "[^0-9]" user //显示数字以外内容
grep "[a-z]" user //找所有小写字母
grep "[A-Z]" user //找所有大写字母
grep "[a-Z]" user //找所有字母
grep "[^0-9a-Z]" user //找所有符号

整体及边界匹配

image.png

egrep "(0:){2}" user //找连续的2个0: 小括号的作用是将字符组合为一个整体
egrep "root|bin" user //找有root或者bin的行
egrep "the\b" abc.txt //在abc.txt文件中找the,右边不允许出现数字、字母、下划线
egrep "\bthe\b" abc.txt //两边都不允许出现数字、字母、下划线
egrep "\<the\>" abc.txt //效果同上
egrep "the\w" abc.txt //在abc.txt文件中找the*,*为数字字母下划线

sort排序文件并输出

  • sort命令将文件进行排序,并将排序结果标准输出。sort命令既可以从特定的文件,也可以从stdin中获取输入
  • 语法格式:sort [参数] [文件] | -b | 忽略每行前面开始出的空格字符 | | —- | —- | | -c | 检查文件是否已经按照顺序排序 | | -d | 排序时,处理英文字母、数字及空格字符外,忽略其他的字符 | | -f | 排序时,将小写字母视为大写字母 | | -i | 排序时,除了040至176之间的ASCII字符外,忽略其他的字符 | | -m | 将几个排序号的文件进行合并 | | -M | 将前面3个字母依照月份的缩写进行排序 | | -n | 依照数值的大小排序 | | -o <输出文件> | 将排序后的结果存入制定的文件 | | -r | 以相反的顺序来排序 | | -t <分隔字符> | 指定排序时所用的栏位分隔字符 | | -k | 指定需要排序的栏位 |

参考实例

sort将文件/文本的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出

[root@linuxcool ~]# cat sort.txt 
 AA:10:1.1
 CC:30:3.3
 DD:40:4.4
 BB:20:2.2
 FF:60:6.6
 FF:60:6.6
 EE:50:5.5
 [root@linuxcool ~]# sort sort.txt 
 AA:10:1.1
 BB:20:2.2
 CC:30:3.3
 DD:40:4.4
 EE:50:5.5
 FF:60:6.6
 FF:60:6.6

忽略相同行使用-u选项

[root@linuxcool ~]# cat sort.txt 
 AA:10:1.1
 CC:30:3.3
 DD:40:4.4
 BB:20:2.2
 FF:60:6.6
 FF:60:6.6
 EE:50:5.5
 [root@linuxcool ~]# sort -u sort.txt 
 AA:10:1.1
 BB:20:2.2
 CC:30:3.3
 DD:40:4.4
 EE:50:5.5
 FF:60:6.6

sort的-n、-r、-k、-t选项的使用

[root@linuxcool ~]# cat sort.txt 
 AA:BB:CC
 aa:30:1.9
 cc:50:3.3
 dd:20:4.2
 bb:10:2.4
 ee:40:5.3
 ee:60:5.1
 [root@linuxcool ~]# sort -nk 2 -t: sort.txt
 AA:BB:CC
 bb:10:2.4
 dd:20:4.2
 aa:30:1.9
 ee:40:5.3
 cc:50:3.3
 ee:60:5.1
 [root@linuxcool ~]# sort -nrk 3 -t: sort.txt
 ee:40:5.3
 ee:60:5.1
 dd:20:4.2
 cc:50:3.3
 bb:10:2.4
 aa:30:1.9
 AA:BB:CC

sed (Stream EDitor) 流式编辑器

可以对文档进行非交互式增删改查,逐行处理
非交互,基于模式匹配过滤及修改文本
逐行处理,并将结果输出到屏幕

sed用法

1,前置指令 | sed 选项 条件 指令
2,sed 选项 条件 指令 被处理文档
选项 :-n 屏蔽默认输出 -r 支持扩展正则 -i 修改源文件
条件 :行号 /字符串/
指令 :p 输出 d 删除 s 替换 a 行下追加 i 行上添加 c 替换整行

sed p(输出)/d(删除)命令

sed -n 'p' user //输出所有行
sed -n '3p' user //输出第3行
sed -n '2,4p' user //输出2~4行
sed -n '2p;4p' user //输出第2行与第4行
sed -n '3,+1p' user //输出第3行以及后面1行
sed -n '1~2p' user //输出奇数行1为起始行2为步长
sed -n '/^root/p' user //输出以root开头的行 
sed -n '/root/p' user //输出包含root的行
sed -nr '/^root|^bin/p' user //输出以root开头的行或bin开头的行,|是扩展正则,需要r选项
sed -n '1!p' user //输出除了第1行的内容,!是取反
sed -n '$p' user //输出最后一行
sed -n '=' user //输出行号,如果是$=就是最后一行的行号
#以上操作,如果去掉-n,再将p指令改成d指令就是删除
sed '3,5d' abc.txt #删除第3~5行 
sed '/xml/d' abc.txt #删除所有包含xml的行 
sed '/xml/!d' abc.txt #删除不包含xml的行 
sed '/^install/d' abc.txt #删除以install开头的行 
sed '$d' abc.txt #删除文件的最后一行 
sed '/^$/d' abc.txt #删除所有空行

sed s(替换)命令

image.png

sed's/2017/6666/'shu.txt //把所有行的第1个2017替换成6666
sed's/2017/6666/2'shu.txt //把所有行的第2个2017替换成6666
sed'1s/2017/6666/'shu.txt //把第1行的第1个2017替换成6666
sed'3s/2017/6666/3'shu.txt //把第3行的第3个2017替换成6666
sed's/2017/6666/g'shu.txt //所有行的所有个2017都替换
sed'/2024/s/2017/6666/g'shu.txt //找含有2024的行,将所有2017替换成6666
sed '4,7s/^/#/' a.txt //将4~7行注释掉
#如果想把 /bin/bash 替换成 /sbin/sh 怎么操作?
sed 's/\/bin\/bash/\/sbin\/sh/' user //使用转义符号可以成功,但不方便
sed 's!/bin/bash!/sbin/sh!' user //最佳方案,更改s的替换符
sed -i '/SELINUX/s/enforcing/disabled/' /etc/selinux/config # 修改selinxu的配置文件模式

脚本练习
搭建httpd服务,用82号端口开启服务
#!/bin/bash
setenforce 0 //关闭selinux
yum -y install httpd &> /dev/null //安装网站
echo "sed-test~~~" > /var/www/html/index.html //定义默认页
sed -i '/^Listen 80/s/0/2/' /etc/httpd/conf/httpd.conf //修改配置文件,将监听端口修改为82
systemctl restart httpd //开服务
systemctl enable httpd //设置开机自启
然后运行脚本
curl 192.168.2.5:82 //脚本运行之后,测试82端口看到页面即可 
sed-test~~~
netstat -ntulp | grep httpd //检查服务的端口是否为82

找到系统使用bash的账户名,然后按照“用户名 —> 密码” 的格式存储在一个文件中
#!/bin/bash
u=$(sed -n '/bash$/s/:.*//p' /etc/passwd) //找到passwd文档中以bash结尾的行,然后将行中冒号以及冒号后面内容都删除此处的p代表仅仅显示s替换成功的行,最后赋值给u
for i in $u //将那些用bash的账户名交给for循环
do
n=$(grep $i: /etc/shadow) //用每个账户名去shadow中找对应信息
n=${n#*:} //掐头,从左往右删除到第1个冒号cat
n=${n%%:*} //去尾,从右往左删除到最后一个冒号
#经过上述步骤,n就是最终要的密码了
echo "$i --> $n" //按格式喊出,如果要存到文件中就用追加重定向
done

sed其他指令(a行下追加 i行上添加 c替换整行)

image.png

sed 'a 666' user //所有行的下面追加666
sed '1a 666' user //第1行的下面追加666
sed '/^bin/a 666' user //在以bin开头的行的下面追加666
sed 'i 666' user //所有行的上面添加666
sed '5i 666' user //第5行的上面添加666
sed '$i 666' user //最后1行的上面添加666
sed 'c 666' user //所有行都替换成666
sed '1c 666' user //替换第1行为666

awk指令:可以实现精确搜索并输出 ,逐行处理

awk用法

1,前置指令 | awk 选项 条件 指令
2,awk 选项 条件 指令 被处理文档
选项 -F 定义分隔符
指令 print
条件 /字符串/
内置变量 $1第一列 $2第二列 $3第三列 … $0 所有列 NR行号 NF 列号

awk '{print}' abc.txt //输出所有
awk '/to/{print}' abc.txt //输出有to的那行

awk内置变量

内置变量 $1第一列 $2第二列 $3第三列 … $0 所有列 NR行号 NF 列号

awk '{print $2}' abc.txt //输出所有行的第2列
awk '/to/{print $1}' abc.txt //输出有to的那行的第1列
awk '{print $0}' abc.txt //输出所有行所有列
awk '{print $0,$1}' abc.txt //输出所有行所有列,第1列
awk '{print NR}' abc.txt //输出所有行的行号
awk '{print NR,$0}' abc.txt //输出所有行的行号,所有列
awk '{print NR,NF}' abc.txt //输出所有行的行号,列号(有几列)

awk -F: '{print $1}' user //文档中如果没有空格,可以用F修改分隔符
awk -F: '{print $1,$6}' user //使用冒号作为列的分隔符,显示第1、6列
awk -F: '{print $1" 的家目录是 "$6}' user //还可以输出常量,加双引号即可

关于awk内置变量的书签:

收集根分区剩余容量

df -h | awk '/\/$/{print $4}' //使用df -h 作为前置指令交给awk处理找到以/结尾的行,并输出第4列
df -h | awk '/\/$/{print "根分区剩余容量是"$4}' //然后加常量输出

收集网卡流量信息

ifconfig eth0 | awk '/RX p/{print "eth0网卡接收的数量量是"$5}'
ifconfig eth0 | awk '/TX p/{print "eth0网卡发送的数量量是"$5}'

awk的条件

/字符串/ : 还可以使用正则 ~ 包含 !~不包含

awk -F: '$6~/bin/{print}' user //输出第6列包含bin的行
awk -F: '$6!~/bin/{print}' user //输出第6列不包含bin的行

使用数字或者字符串:== != > >= < <=

awk -F: '$3<3{print}' user //输出第3列小于3的行
awk -F: '$3<=3{print}' user //输出第3列小于等于3的行
awk -F: 'NR==2{print}' user //输出第2行
awk -F: 'NR>2{print}' user //输出行号大于2的行

逻辑组合:&&并且 ||或者

awk -F: 'NR==2||NR==4{print}' user //找行号是2或者4的行
awk -F: 'NR==2||NR==40{print}' user //如果只有一个条件满足就显示一个
awk -F: '$7~/bash/&&$3<=500{print}' user //找第7列包含bash并且第3列小于等于500的行
awk 'NR==2&&NR==4{print}' user //找行号既是2又是4的行,不存在,无输出
awk -F: '$7~/bash/&&NR<=3{print}' user //找第7列包含bash并且行号是1~3的
awk -F: '$7~/bash/||NR<=3{print}' user //找第7列包含bash或者行号是1~3的
awk -F: '$1~/root/' user //找第1列包含root的行
如果有用户叫root6,也会搜到,比较宽松的搜索方式,如果任务就是{print}的话可以省略不写
awk -F: '$1=="root"' user //找第1列完全等于root的行多一个字符少一个字符都不行,比较严格的搜索方式

运算

awk 'NR%2==0{print NR,$0}' user //在条件中使用运算,找到将行号除以2余数等于0的行,然后输出该行的行号和所有列,相当于输出偶数行

awk处理时机:可以执行额外任务

BEGIN任务 执行1次,读取文档之前执行
逐行任务 执行n次,读取文档时执行
END任务 执行1次,读取文档之后执行

awk -F: 'BEGIN{print "ok"}{print $1}END{print "ok"}' user
awk 'BEGIN{print NR}{print NR}END{print NR}' user

awk求和、求平均、求最大值、求最小值

cat data|awk '{sum+=$1} END {print "Sum = ", sum}'
cat data|awk '{sum+=$1} END {print "Average = ", sum/NR}'
cat data|awk 'BEGIN {max = 0} {if ($1>max) max=$1 } END {print "Max=", max}'
#min的初始值设置一个超大数即可)
awk 'BEGIN {min = 1999999} {if ($1<min) min=$1} END {print "Min=", min}'

利用awk处理时机,输出下列内容

image.png

awk 'BEGIN{print "User\tUID\tHome"}' //第1步输出表头信息 \t制表符
awk -F: '{print $1"\t"$3"\t"$6}' user //第2步输出内容
awk 'END{print "总计"NR"行" }' user //第3步输出结尾
awk -F: 'BEGIN{print "User\tUID\tHome"}{print $1"\t"$3"\t"$6}END{print "总计"NR"行"}' user  //合在一起写

awk数组+for循环实现高级搜索

数组 相当于可以存储多个值的特殊变量
数组名[下标]=下标对应的值

awk数组

awk 'BEGIN{a[1]=10;a[2]=20;print a[2],a[1]}' //使用awk测试数组,首先创建数组a,下标1对应值是10,下标2对应值是20,然后输出下标是2与下标是1的值
awk 'BEGIN{a["abc"]="abcabc";a["xyz"]="xyzxyz";print a["xyz"]}' //数组的下标和值都可以不是数字,测试时加双引号即可

示例

准备一个文档,里面有6行,每行分别是abc、xyz、abc、opq、xyz、abc 然后按照awk逐行处理的工作特点使用awk ‘{a[$1]++}’ shu.txt 走完每一行得到下列结果
但不会输出到屏幕
a[$1]++ a[abc]++ a[abc]=1
a[$1]++ a[xyz]++ a[xyz]=1
a[$1]++ a[abc]++ a[abc]=2
a[$1]++ a[opq]++ a[opq]=1
a[$1]++ a[xyz]++ a[xyz]=2
a[$1]++ a[abc]++ a[abc]=3
如果要输出到屏幕可以使用命令
awk ‘{a[$1]++}END{print a[“abc”]}’ shu.txt

awk+for循环

for(变量名 in 数组名){print 变量名}//这个格式可以查看数组的所有下标
awk '{a[$1]++}END{for(i in a){print i,a[i]}}' shu.txt  //使用逐行任务与数组收集文档shu.txt中的信息,然后在END任务中使用for循环显示所有数组a的下标与值

awk实际脚本应用

使用awk统计网站访问量

setenforce 0 //关闭selinux
systemctl stop firewalld //关闭防火墙
systemctl restart httpd //开启网站服务

初步统计

curl 192.168.4.7:82 //如果端口没改过就不用敲
awk '{print $1}' /var/log/httpd/access_log  //初步统计,不完美

进阶统计

awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' 
#/var/log/httpd/access_log //将上述的文件替换成网站的日志,就可以最终用来查看日志得到可以得到哪个ip来访以及来访的次数

最终统计

awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log | sort -nr -k 2 
//使用sort命令增加排序功能,-n是以数字形式排序,-r是降序, -k是指定为第几列排序

编写安全监测脚本

要求监测如下数据项目:cpu负载;网卡流量;内存剩余容量;磁盘剩余容量;计算机账户数量;当前登陆账户数量;计算机当前开启的进程数量;本机已安装的软件包数量

#!/bin/bash
while :
do
clear
free -h | awk '/^Mem:/{print "剩余内存容量是"$4}'
df -h | awk '/\/$/{print "根分区剩余容量是"$4}'
awk 'END{print "用户总数是"NR"个"}' /etc/passwd
who | awk 'END{print "登录用户数量是"NR"个"}'
uptime | awk '{print "cpu的15分钟平均负载是"$NF}'
rpm -qa | awk 'END{print "安装的软件包数量是"NR"个"}'
sleep 3
done

防止ssh暴力破解密码

/var/log/secure是安全日志,如果有人登陆时输入错误密码的话信息会记录下来,这种信息可以用awk抓取出来

awk '/Failed password for root/{ip[$11]++}END{for(i in ip){print i,ip[i]}}' /var/log/secure 
#统计安全日志中访问root账户密码输入错误的ip地址与次数