史上最全的Shell指南-下篇

11 流程控制

11.1 函数进阶

11.1.1 函数嵌套

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	所谓的函数嵌套,主要是在函数间或者文件间相互使用的一种方式。它主要有三种样式:
样式1:函数间调用
	- 函数体内部调用其他的函数名
样式2:文件间调用
	- 函数体内部调用另外一个文件的函数名
	- 需要额外做一步文件source的加载动作
	注意:我们将专门提供函数的文件称为 -- 函数库
样式3:函数自调用
	- 函数体内部调用自己的函数名,将复杂的逻辑简单化

简单实践

函数间调用实践1-图形信息打印

	按照信息提示,分别打印 三角形 和 等腰梯形
        *                  *****
       * *                *******
      * * *              *********
     * * * *            ***********
    * * * * *          *************
[root@localhost ~]# cat function_drawn_graph.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

graph_type=(三角形 梯形)
# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------查看可以绘制的图形---------------"
    echo -e " 1: 三角形  2: 梯形"
    echo -e "-------------------------------------------\033[0m"
}

# 定制打印左侧空格效果
left_bland_func(){
   layer_num="$1"
   sub_num="$2"
   for m in $(seq $[${layer_num}-${sub_num}]);do
        echo -n " "
   done
}

# 打印图形的核心内容部分
kernel_character_func(){
   char_num="$1"
   char_mark="$2"
   for j in $(seq ${char_num});do
       echo -n "${char_mark}"
   done
}
# 定制打印三角形的函数
triangle_func(){
    # 接收函数传参
    layer_num=$1
    # 定制打印n层的三角形
    for i in $(seq 1 ${layer_num});do
       # 定制打印三角形左侧的空格效果
       left_bland_func ${layer_num} $i
       # 定制打印三角形核心部分
       kernel_character_func $i "* "
       # 打印完每行就换行
       echo
   done
}
# 定制梯形的功能函数
trapezium_func(){
    print_num=${layer_num}
    for i in $(seq 1 ${layer_num});do
        # 定制打印梯形左侧的空格效果
        left_bland_func ${layer_num} $i
        # 定制打印梯形核心部分
        kernel_character_func $print_num "*"
        let print_num+=2
        echo
    done
}
# 选择服务操作类型
while true;do
    menu
    read -p "> 请输入要查看的资源信息类型: " graph_id
    case ${graph_type[$graph_id-1]} in
        "三角形")
            read -p "> 请输入三角形绘制的层数: " layer_num
            triangle_func ${layer_num}
            ;;
        "梯形")
            read -p "> 请输入梯形绘制的层数: " layer_num
            # 定制打印n层的梯形
            trapezium_func ${layer_num}
            ;;
        *)
            echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
    esac
done
脚本执行后效果
[root@localhost ~]# /bin/bash function_drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 2
> 请输入梯形绘制的层数: 5
    *****
   *******
  *********
 ***********
*************
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

文件间调用实践2-拆分function_drawn_graph.sh脚本

需求:拆分绘图脚本文件
	1 将脚本文件中的功能逻辑函数拆分出来以单独的文件存在
	2 脚本文件保留核心逻辑功能
创建功能函数库文件目录
[root@localhost ~]# mkdir lib

查看库文件内容
[root@localhost ~]# cat lib/drawn_func.sh
#!/bin/bash
# 功能:打印相关图形功能函数库
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------查看可以绘制的图形---------------"
    echo -e " 1: 三角形  2: 梯形"
    echo -e "-------------------------------------------\033[0m"
}

# 定制打印三角形左侧空格效果
left_bland_func(){
   layer_num="$1"
   sub_num="$2"
   for m in $(seq $[${layer_num}-${sub_num}]);do
        echo -n " "
   done
}

# 打印图形的核心内容部分
kernel_character_func(){
   char_num="$1"
   char_mark="$2"
   for j in $(seq ${char_num});do
       echo -n "${char_mark}"
   done
}
# 定制打印三角形的函数
triangle_func(){
    # 接收函数传参
    layer_num=$1
    # 定制打印n层的三角形
    for i in $(seq 1 ${layer_num});do
       # 定制打印三角形左侧的空格效果
       left_bland_func ${layer_num} $i
       # 定制打印三角形核心部分
       kernel_character_func $i "* "
       # 打印完每行就换行
       echo
   done
}
# 定制梯形的功能函数
trapezium_func(){
    print_num=${layer_num}
    for i in $(seq 1 ${layer_num});do
        # 定制打印梯形左侧的空格效果
        left_bland_func ${layer_num} $i
        # 定制打印梯形核心部分
        kernel_character_func $print_num "*"
        let print_num+=2
        echo
    done
}
查看脚本框架文件
[root@localhost ~]# cat function_drawn_graph-lib.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.3
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
graph_type=(三角形 梯形)

# 加载功能函数库文件
source ./lib/drawn_func.sh

# 选择服务操作类型
while true;do
    menu
    read -p "> 请输入要查看的资源信息类型: " graph_id
    case ${graph_type[$graph_id-1]} in
        "三角形")
            read -p "> 请输入三角形绘制的层数: " layer_num
            triangle_func ${layer_num}
            ;;
        "梯形")
            read -p "> 请输入梯形绘制的层数: " layer_num
            # 定制打印n层的梯形
            trapezium_func ${layer_num}
            ;;
        *)
            echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
    esac
done
脚本执行效果
[root@localhost ~]# /bin/bash function_drawn_graph-lib.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

11.1.2 函数自调用

这一节,我们从 简单实践、案例实践、小结 三个方面来学习。

简单实践

简介

	函数自调用也称函数递归,说白了就是 函数调用自身,实现数据递归能力的实现

实践-函数自调用

需求: 实现数学阶乘的实践
	示例:5的阶乘
		 完整格式:5! = 1 * 2 * 3 * 4 * 5 = 120
		 简写格式:5! = 5 * (1 * 2 * 3 * 4) = 5 * 4!
	公式: x! = x * (x-1)!
查看脚本内容
[root@localhost ~]# cat function_func_test1.sh
#!/bin/bash
# 功能:函数自调用实践

# 定制功能函数框架
self_func(){
    # 接收一个参数
    num=$1
    if [ ${num} -eq 1 ];then
        echo 1
    else
        # 定制一个临时本地变量,获取递减后的值
        local temp=$[ ${num} - 1 ]
        # 使用函数自调用方式获取内容
        local result=$(self_func $temp)
        # 格式化输出信息
        echo $[ $result * ${num} ]
    fi
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的阶乘:" value
    result=$(self_func ${value})
    echo "${value}的阶乘是: ${result}"
done
脚本执行效果
[root@localhost ~]# /bin/bash function_func_test1.sh
请输入一个您要查询的阶乘:5
5的阶乘是: 120
请输入一个您要查询的阶乘:6
6的阶乘是: 720
请输入一个您要查询的阶乘:7
7的阶乘是: 5040
请输入一个您要查询的阶乘:^C
[root@localhost ~]#

案例实践

实践1-遍历制定目录下的所有文件

准备工作
[root@localhost ~]# mkdir -p dir/{softs/{nginx,tomcat},logs,server/{java,python}}
[root@localhost ~]# touch dir/softs/{nginx/nginx.conf,tomcat/server.xml}
[root@localhost ~]# touch dir/logs/user{1..3}.log
[root@localhost ~]# touch dir/server/{java/java.jar,python/python.py}
[root@localhost ~]# tree dir/
dir/
├── logs
│   ├── user1.log
│   ├── user2.log
│   └── user3.log
├── server
│   ├── java
│   │   └── java.jar
│   └── python
│       └── python.py
└── softs
    ├── nginx
    │   └── nginx.conf
    └── tomcat
        └── server.xml

7 directories, 7 files
[root@localhost ~]# cat function_scan_dir.sh
#!/bin/bash
# 功能:扫描目录下所有文件
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制功能函数框架
# 定制目录扫描功能函数
scan_dir() {
    # 定制临时局部功能变量
	# cur_dir 当前目录 workdir 工作目录
    local cur_dir workdir
	
	# 接收要检查的目录,进入到目录中
    workdir=$1
    cd ${workdir}
	
	# 对工作目录进行简单判断,根目录没有父目录
    if [ ${workdir} = "/" ]
    then
        cur_dir=""
    else
        cur_dir=$(pwd)
    fi
    
	# 查看当前目录下的文件列表
    for item in $(ls ${cur_dir})
    do
        # 如果文件是目录,则继续查看目录下文件
        if test -d ${item};then
            cd ${item}
            scandir ${cur_dir}/${item}
            cd ..
        # 如果文件是普通文件,则输出信息即可
        else
            echo ${cur_dir}/${item}
        fi
    done
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的目录:" value
    if [ -d ${value} ]
    then
        scandir ${value}
    else
        echo "您输入的不是目录,请重新输入!"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_scan_dir.sh
请输入一个您要查询的目录:dir
/root/dir/logs/user1.log
/root/dir/logs/user2.log
/root/dir/logs/user3.log
/root/dir/server/java/java.jar
/root/dir/server/python/python.py
/root/dir/softs/nginx/nginx.conf
/root/dir/softs/tomcat/server.xml
请输入一个您要查询的目录:^C
[root@localhost ~]#
结果显示:
	该脚本达到了我们需要的目录遍历效果

11.1.3 综合练习

这一节,我们从 案例解读、脚本实践、小结 三个方面来学习。

案例解读

案例需求

使用shell脚本绘制一个杨辉三角

案例解读

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1、每行数字左右对称,从1开始变大,然后变小为1。   
2、第n行的数字个数为n个,所有数字和为 2^(n-1)。  
3、每个数字等于上一行的左右临近两个数字之和。
4、第n行的数字依次为 1、1×(n-1)、1×(n-1)×(n-2)/2、1×(n-1)×(n-2)/2×(n-3)/3 ...   
...

脚本实践

脚本实践

查看脚本内容
[root@localhost ~]# cat yanghui_triangle.sh
#!/bin/bash
# 功能:shell定制杨辉三角功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 判断输入是否为整数
check_int(){
    # 设定数据标识
    flag=true
    read -p "请输入一个数据值: " layer_num
    # 通过在循环内部进行数据操作判断是否是数据
    while $flag;do
      expr $layer_num + 0 > /dev/null 2>&1
      if [ $? -eq 0 ]
      then
          flag=false
      else
          read -p "请输入一个数据值: " layer_num
      fi
    done
}

# 定制一个数组
declare -a num_array

check_int
# 定制杨辉三角的行数变量 row
for(( row=1; row<=layer_num; row++ ))
do
   #打印杨辉三角的左侧空白
   for k in $(seq $[$layer_num - $row])
   do
       echo -n "    "
   done
   # 定制每行的数据获取
   for(( col=1; col<=row; col++ ))
   do
       # 第n行的第1个和第n行的第n个数字为1
       if [ $col -eq 1  -o $row -eq $col ]
       then
         # 设定每行的两个边界数字为1
         num_array[$row$col]=1
       else
         # 获取上一行的两个临近数据
         let row_up=row-1  # 获取上一行的数据
         let col_up=col-1  # 获取上一行的临近数据
         # 获取当前行的数据值为 上一行临近数据的数据和
         let num_array[$row$col]=${num_array[$row_up$col_up]}+${num_array[${row_up}${col}]}
       fi
   done
   # 打印每行的数据
   for(( col=1; col<=row; col++ ))
   do
       printf "%-8s" ${num_array[$row$col]}
   done
   echo
done
脚本执行效果
[root@localhost ]# /bin/bash yanghui_triangle.sh
请输入一个数据值: 8
                            1
                        1       1
                    1       2       1
                1       3       3       1
            1       4       6       4       1
        1       5       10      10      5       1
    1       6       15      20      15      6       1
1       7       21      35      35      21      7       1

函数嵌套改造

脚本改造后内容
[root@localhost ~]# cat yanghui_triangle.sh
#!/bin/bash
# 功能:shell定制杨辉三角功能
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制一个数组
declare -a num_array

# 判断输入是否为整数
check_int(){
    # 设定数据标识
    flag=true
    read -p "请输入一个数据值(q退出): " layer_num
    [ $layer_num == "q" ] && exit
    # 通过在循环内部进行数据操作判断是否是数据
    while $flag;do
      expr $layer_num + 0 > /dev/null 2>&1
      if [ $? -eq 0 ]
      then
          flag=false
      else
          read -p "请输入一个数据值: " layer_num
      fi
    done
}

# 定制左侧空格打印逻辑
left_blank_func(){
   # 获取参数值
   layer_num=$1
   row=$2
   # 空格打印逻辑
   for k in $(seq $[$layer_num - $row])
   do
       echo -n "    "
   done
}

# 获取每行的数据值
col_num_count(){
   # 获取参数值
   row=$1
   # 数据获取逻辑   
   for(( col=1; col<=row; col++ ))
   do
       # 第n行的第1个和第n行的第n个数字为1
       if [ $col -eq 1  -o $row -eq $col ]
       then
         # 设定每行的两个边界数字为1
         num_array[$row$col]=1
       else
         # 获取上一行的两个临近数据
         let row_up=row-1  # 获取上一行的数据
         let col_up=col-1  # 获取上一行的临近数据
         # 获取当前行的数据值为 上一行临近数据的数据和
         let num_array[$row$col]=${num_array[$row_up$col_up]}+${num_array[${row_up}${col}]}
       fi
   done
}
# 每行数据打印逻辑
col_num_print(){
   # 获取参数值
   row=$1
   # 数据打印逻辑
   for(( col=1; col<=row; col++ ))
   do
       printf "%-8s" ${num_array[$row$col]}
   done
   echo
}
while true
do
  check_int
  # 定制杨辉三角的行数变量 raw
  for(( row=1; row<=layer_num; row++ ))
  do
     #打印杨辉三角的左侧空白
     left_blank_func $layer_num $row
     # 获取数据的值
     col_num_count $row
     # 打印每行的所有数据
     col_num_print $row
  done
done
[root@localhost ~]# /bin/bash yanghui_triangle.sh
请输入一个数据值(q退出): 5
                1
            1       1
        1       2       1
    1       3       3       1
1       4       6       4       1
请输入一个数据值(q退出): q
[root@localhost ~]#

小结


12 脚本自动化

12.1 脚本信号

12.1.1 信号基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	当我们在构建一些更高级的脚本的时候,就会涉及到如何在linux系统上来更好的运行和控制它们,到目前为止,我们运行脚本的方式都是以实时的模式,在命令行来运行它。但是这并不是脚本唯一的运行方式,我们可以在linux系统中以更丰富的方式来运行它们,甚至在脚本遇到不可查的异常中止时候,以关闭linux终端界面的方式终止脚本。
	这些能力都是基于信号的机制来实现了

信号

	linux使用信号与系统上运行的进程进行通信,想要对shell的脚本控制,只需要传递相关信号给shell脚本即可。
信号描述信号描述
1SIGHUP挂起进程15SIGTERM优雅的终止进程
2SIGINT终止进程17SIGSTOP无条件停止进程,不终止进程
3SIGQUIT停止进程18SIGTSTP停止或暂停进程,不终止进程
9SIGKILL无条件终止进程19SIGCONT继续运行停止的进程
    默认情况下,bash shell会忽略收到的任何SIGQUIT(3)和SIGTERM(15)信号(正因为这样交互式shell才不会被意外终止)。但是bash shell会处理收到的SIGHUP(1)和SIGINT(2)信号。

    如果bash shell收到SIGHUP信号,它会退出。但在退出之前,它会将信号传给shell启动的所有进程(比如shell脚本)。通过SIGINT信号,可以中断shell,Linux内核停止将CPU的处理时间分配给shell,当这种情况发生时,shell会将SIGINT信号传给shell启动的所有进程。

生成信号

终止进程:
	ctrl+c,
暂停进程:
	ctrl+z,停止的进程继续保留在内存中,并能从停止的位置继续运行
恢复进程:
	jobs查看运行任务,fg num 重新执行
杀死进程:
	kill -9 pid

简单实践

实践1-终止进程

[root@localhost ~]# sleep 1000
^C
[root@localhost ~]#

实践2-挂起进程

[root@localhost ~]# sleep 1000
^Z
[1]+  已停止               sleep 1000
[root@localhost ~]# ps aux  | grep sleep
root      39067  0.0  0.0 108052   360 pts/0    T    17:28   0:00 sleep 1000

实践3-恢复进程

查看所有挂起进程
[root@localhost ~]# jobs
[1]+  已停止               sleep 1000

恢复挂起进程的id
[root@localhost ~]# fg 1
sleep 1000
^C
[root@localhost ~]#

实践4-杀死进程

后台执行命令
[root@localhost ~]# sleep 1000 &
[1] 39074
[root@localhost ~]# ps aux  | grep sleep | grep -v grep
root      39074  0.0  0.0 108052   360 pts/0    S    17:30   0:00 sleep 1000

强制杀死进程
[root@localhost ~]# kill -9 39074
[root@localhost ~]#
[1]+  已杀死               sleep 1000
[root@localhost ~]# jobs

12.1.2 信号捕捉

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	shell编程提供了一种方式,让我们可以随意的控制脚本的运行状态,这就需要涉及到信号的捕获操作。在shell编程中,我们可以借助于 trap命令实现指定shell脚本要watch哪些linux信号并从shell中拦截。如果脚本收到了trap命令中列出的信号,它会阻止它被shell处理,而在本地处理。

trap命令格式

命令格式
	trap commands signals
	
命令示例:
	# 收到指定信号后,执行自定义指令,而不会执行原操作
    trap '触发指令' 信号
     
    # 忽略信号的操作
    trap '' 信号

    # 恢复原信号的操作
    trap '-' 信号
    
    # 列出自定义信号操作
    trap -p
    
    # 当脚本退出时,执行finish函数
    trap finish EXIT

简单实践

实践1-捕获终止信号

查看脚本内容
[root@localhost ~]# cat signal_trap_test1.sh
#!/bin/bash
# 功能:脚本信号捕捉

# 捕获关闭信号
trap "你敢关我,就不关,气死你" SIGINT SIGTERM
trap "走了,不送" EXIT

# 检测逻辑效果
while true
do
    read -p "请输入一个数据:" value
    echo "您输入的数据是: ${value}"
done
脚本执行效果
[root@localhost ~]# /bin/bash signal_trap_test1.sh
请输入一个数据:4
您输入的数据是: 4
请输入一个数据:^Csignal_trap_test1.sh:行1: 你敢关我,就不关,气死你: 未找到命令

您输入的数据是:
请输入一个数据:^Z
[1]+  已停止               /bin/bash signal_trap_test1.sh
[root@localhost ~]#
[root@localhost ~]# jobs
[1]+  已停止               /bin/bash signal_trap_test1.sh
[root@localhost ~]# fg 1
/bin/bash signal_trap_test1.sh

您输入的数据是:
请输入一个数据:3
您输入的数据是: 3
另开一个终端,直接kill进程
[root@localhost ~]# ps aux | grep sign
root      39142  0.0  0.0 113288  1460 pts/0    S+   17:43   0:00 /bin/bash signal_trap_test1.sh
[root@localhost ~]# kill -9 39142

回到之前的终端查看效果
[root@localhost ~]# fg 1
/bin/bash signal_trap_test1.sh

您输入的数据是:
请输入一个数据:3
您输入的数据是: 3
请输入一个数据:已杀死

实践2-捕获正常退出

查看脚本内容
[root@localhost ~]# cat signal_trap_test2.sh
#!/bin/bash
# 功能:脚本信号捕捉

# 捕获关闭信号
trap "echo '走了.不送'" EXIT

value="0"
# 检测逻辑效果
while true
do
    read -p "请输入一个数据:" value
    if [ ${value} == "9" ]
    then
        exit
    else
        echo "您输入的数据是: ${value}"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash signal_trap_test2.sh
请输入一个数据:3
您输入的数据是: 3
请输入一个数据:9
走了.不送

实践3-移除捕获

查看脚本内容
[root@localhost ~]# cat signal_trap_test3.sh
#!/bin/bash
# 功能:移除脚本信号捕捉

# 捕获关闭信号
trap "echo '走了.不送'" EXIT

i=1
# 检测逻辑效果
while [ $i -le 3 ]
do
    read -p "请输入一个数据:" value
    if [ ${value} == "9" ]
    then
        exit
    else
        echo "您输入的数据是: ${value}"
    fi
    let i+=1
done

# 移除捕获信号
trap - EXIT
echo "移除了捕获信号"
脚本执行效果
[root@localhost ~]# /bin/bash signal_trap_test3.sh
请输入一个数据:9
走了.不送
[root@localhost ~]# /bin/bash signal_trap_test3.sh
请输入一个数据:1
您输入的数据是: 1
请输入一个数据:2
您输入的数据是: 2
请输入一个数据:3
您输入的数据是: 3
移除了捕获信号

结果显示:
	在没有走到信号捕获移除的时候,捕获仍然生效

12.2 expect

12.2.1 expect基础

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

场景需求

在日常工作中,经常会遇到各种重复性的"手工交互"操作,虽然没有什么技术含量,但是相当的重要。在实际的工作场景中,这种重复性的手工操作动作,非常的繁多,但是对于量大的工作来说,效率就非常低效了。所以我们就需要有一种工具,能够简化我们重复的手工操作。

expect简介

expect是一个免费的编程工具,由DonLibes制作,作为Tcl脚本语言的一个扩展,它可以根据程序的提示,模拟标准输入提供给程序,从而实现自动的交互式任务,而无需人为干预,可以用作Unix系统中进行应用程序的自动化控制和测试的软件工具。

说白了,expect就是一套用来实现自动交互功能的软件。它主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信。在使用的过程中,主要是以脚本文件的样式来存在

官方网站:
	https://www.nist.gov/services-resources/software/expect
工具手册:
	man expect

软件部署

安装软件
[root@localhost ~]# yum install expect -y

查看效果
[root@localhost ~]# expect -v
expect version 5.45
进入专用的命令交互界面
[root@localhost ~]# expect
expect1.1>  ls
anaconda-ks.cfg
expect1.2> exit
命令帮助
    -c:	执行脚本前先执行的命令,可多次使用,多个命令之间使用;隔开
    -d:	debug模式,可以在运行时输出一些诊断信息,与在脚本开始处使用exp_internal 1相似。
    -D:	启用交换调式器,可设一整数参数。
    -f:	从文件读取命令,仅用于使用#!时。如果文件名为"-",则从stdin读取(使用"./-"从文件名为-的文件读取)。
    -i:	交互式输入命令,使用"exit""EOF"退出输入状态。
    --:	标示选项结束(如果你需要传递与expect选项相似的参数给脚本时),可放到#!行:#!/usr/bin/expect --。
    -v:	显示expect版本信息

简单实践

语法解读

	在进行expect脚本编写的时候,我们需要记住 -- expect 用的不是我们普通的shell或者python语法,它使用的是tlc语法。

	Tcl 全称是 Tool command Language。它是一个基于字符串的命令语言,基础结构和语法非常简单,易于学习和掌握。Tcl 语言是一个解释性语言,所谓解释性是指不象其他高级语言需要通过编译和联结,它象其他 shell 语言一样,直接对每条语句顺次解释执行。

	Tcl 数据类型简单。对 Tcl 来说,它要处理的数据只有一种——字符串。Tcl 将变量值以字符串的形式进行存储,不关心它的实际使用类型。

输出语法

输出:tcl使用”puts"关键字来作为输出语句
样式:puts <-nonewline> string
属性解析:
	如果string中间有特殊字符,可以使用 {} 或者 "" 将其作为一个小组,共同输出
	-nonewline 代表输出结果的时候,不输出换行符
	put 和 puts 都可以在命令行使用,但是脚本中,最好用puts
[root@localhost ~]# expect
expect1.1> puts hello				# 输出一个字符串内容
hello
expect1.2> puts "hello world"		# 输出包含特殊字符的字符串,不能用单引号
hello world
expect1.3> puts {hello world}		# 输出包含特殊字符的字符串
hello world
expect1.4> puts -nonewline "hello world"  # 输出内容的时候,不换行
hello worldexpect1.5>

脚本基础

1 文件名后缀   
	.expect 作为标识符
2 文件首行,要指定命令的执行解释器 
	#!/usr/bin/expect
3 脚本文件的执行
	expect 脚本名
脚本内容示例
[root@localhost ~]# cat expect_test.expect
#!/usr/bin/expect
# 设定一个环境变量
set var nihao
# 输出环境变量
puts $var

脚本执行效果
[root@localhost ~]# expect expect_test.expect
nihao

12.2.2 语法实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

赋值语法

赋值:tcl 使用“set”关键字来定义参数,不必指定变量值的类型,因为变量值的类型仅一种——字符串
样式:set varName [value] 
注意:
	变量名是由 数字、下划线、字符组成,数字不能开头,大小写敏感。
expect1.7> set a Hello			# 设定一个变量名a
Hello
expect1.8> put $a				# 使用$ 符号获取变量名的存储值
Hello
expect1.9> put "$a"				# 使用 "" 方式打印变量的值
Hello
expect1.10> put {$a}			# {} 有别于"" 的特点在于原字符输出
$a
expect1.11> set b $a			# 变量的传递
Hello
expect1.12> puts $b
Hello

替换语法

替换(解析):
	- $符号可以实现引用替换,用于引用变量名代替的参数值,但是TCL对嵌套的”$”不于理睬
	- [] 方括号“[]”完成命令替换。用“[]”将一条命令括起来,命令执行完成后,返回结果
expect1.20> set b [set a 5]					# 相当于 set b $a,传递赋值
5
expect1.21> puts $b
5
expect1.22> set c [expr 5 * 10 ]			# expr是执行系统命令,将计算结果交给c
50
expect1.24> puts $c
50

注意事项

变量的设定
expect1.13> set var value					# 设定一个普通变量名
value
expect1.14> puts $var						# 获取变量名的值
value

不支持嵌套$
expect1.15> set var1 $$value				# TCL不支持嵌套的$
can't read "value": no such variable
    while executing
"set var1 $$value"
expect1.16> set var1 $$var					# 由于$var 已经是变量,所以前面的$就无效了
$value
expect1.17> puts $var1
$value

原字符输出
expect1.18> set var2 {$var1}				# {} 代表原字符输出
$var1
expect1.19> puts $var2
$var1

脚本实践

内置变量

对于tcl来说,它内部包含了大量的内置变量,可以让我们实现快速的功能操作。

常见的内置变量有:
	argc	指命令行参数的个数。
	argv	指包含命令行参数的列表。
	argv0	是指被解释的文件或由调用脚本的名称的文件名。
	env		用于表示是系统环境变量的内容,普通变量我们还是使用$即可
	tcl_version	返回Tcl解释器的最新版本,注意不是expect的版本号

内置参数实践

[root@localhost ~]# cat expect_test1.expect
#!/usr/bin/expect
# 查看当前文件传递的参数数量
puts "当前文件传递的参数数量: $argc"

# 查看当前文件传递的参数
puts "当前文件传递的参数: $argv"

# 查看当前文件名称
puts "当前文件名称: $argv0"

# 获取变量值
puts "当前系统变量PATH的值是: $env(PATH)"
set key value
puts "普通变量 key 的值是: $key"

# 查看版本信息
puts "当前tcl版本信息: $tcl_version"
脚本执行效果
[root@localhost ~]# expect expect_test1.expect
当前文件传递的参数数量: 0
当前文件传递的参数:
当前文件名称: expect_test1.expect
当前系统变量PATH的值是: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
普通变量 key 的值是: value
当前tcl版本信息: 8.5

12.2.3 交互基础

这一节,我们从 脚本基础、简单实践、小结 三个方面来学习。

脚本基础

命令解释器

命令解释器
#!/usr/bin/expect
#!/usr/bin/expect -f    从文件中读取自动化命令
#!/usr/bin/expect -     如果文件名为 - ,那么从终端输入中读取
#!/usr/bin/expect -i	交互式输入命令
#!/usr/bin/expect -- 	脚本传递的选项参数和expect选项相似的参数给脚本
注意:
	#!是对脚本的解释器程序路径,脚本的内容是由解释器解释的
	
注释信息
# 被注释的信息

常见符号

{ }:
	作用1:保留所有字符原有的意思,而不做解释,类似于shell中的单引号
		样式:set var {"nihao hehehe"}
	作用2:代码块儿,但是两个 {} 边界必须在一起。
		正确样式:
		if {代码块1 } {
   			代码块2
			}
		错误示例:
		if {$count < 0}
        {
           break;
        }
	注意:
		无论什么时候,{}边界符号与其他内容都最好有空格隔开,尤其是边界外的内容
[]:
	作用:执行命令,类似shell中的 ``(反引号)或者 $()
	样式:set count [expr $count - 1 ]
	
注意:
	在expect 中,没有小括号的概念和应用

常用命令

set 		设定环境变量
				格式:set 变量名 变量值
				样式:set host "192.168.8.12"

				
spawn 		启动新的进程,模拟手工在命令行启动服务
				格式:spawn 手工执行命令
				样式:spawn ssh python@$host

expect 		接收一个新进程的反馈信息,我们根据进程的反馈,再发送对应的交互命令
				格式:expect "交互界面用户输入处的关键字"
				样式:expect "*password*"
				
send 		接收一个字符串参数,并将该参数发送到新进程。
				格式:send "用户输入的信息"	
				样式:send "$password\r"
				
interact 	退出自动化交互界面,进入用户交互状态,如果需要用户交互的话,这条命令必须在最后一行
				格式:interact
				样式:interact
				
其他命令
	exit		退出expect脚本
	expect eof	expect执行内容的结束标识符,退出当前脚本,与interact只能存在一个
	puts		输出变量的信息,相当于linux命令中的echo
	wait		退出程序后,等待时间,回收僵尸进程
	disconnect	断开一个进程连接,但让它在后台继续运行。
	exp_continue 	expect获取期望后,还会有另外的期望,那么我们就把多个期望连续执行

简单实践

实践1-简单的登录交互脚本

查看脚本内容
[root@localhost ~]# cat login_test.expect
#!/usr/bin/expect

# 1 设定环境变量
set username python

# 2 发起远程登录请求
spawn ssh $username@10.0.0.12

# 3 识别用户输入的位置关键字
expect "yes/no"

# 4 发送正确的信息
send "yes\r"

# 5 识别密码关键字,并传递密码信息
send "\r"
expect "password:"
send "123456\r"

# 6 切换回用户交互界面
interact

注意:
	由于password前面会涉及到一次Enter操作,所以在password匹配前,输入一次 \r
清理历史记录
[root@localhost ~]# rm -f .ssh/know_hosts

执行脚本内容
[root@localhost ~]# expect login_test.expect
spawn ssh python@10.0.0.12
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes

Warning: Permanently added '10.0.0.12' (ECDSA) to the list of known hosts.
python@10.0.0.12's password:
[python@localhost ~]$ id
uid=1000(python) gid=1000(python)=1000(python)

实践2-脚本结合

expect 除了使用专用的expect脚本来实现特定功能之外,它还可以与其他脚本嵌套在一起进行使用。最常用的结合方式就是 shell结合。

在于shell结合使用的时候,无非就是将expect的执行命令使用 <<-EOF  。。。 EOF 包装在一起即可。
样式:
/usr/bin/expect<<-EOF
spawn ...
...
expect eof 
EOF

注意:
	由于expect在shell中是作为一个子部分而存在的,所以,一般情况下,expect结束的时候,使用eof命令表示expect的内容到此结束
查看脚本内容
[root@localhost ~]# cat expect_auto_login.sh
#!/bin/bash
# 功能:shell自动登录测试
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
host="$1"
username="$2"
password="$3"

/usr/bin/expect <<-EOF
# 发出连接进程
spawn ssh ${username}@${host}

# - 正常登陆
expect {
    "yes/no*" {  send "yes\n"; exp_continue  }
    "password:" {send "${password}\n";}
}
puts "测试完毕!!!"
expect eof
EOF
脚本测试效果
[root@localhost ~]# /bin/bash expect_auto_login.sh 10.0.0.12 python 123456
spawn ssh python@10.0.0.12
python@10.0.0.12's password: 测试完毕!!!

[python@localhost ~]$ exit
[root@localhost ~]#

12.2.4 综合案例

这一节,我们从 自动分区、用户实践、小结 三个方面来学习。

基础知识

简介

	当系统配置完毕后,我们可以采用fdisk命令对额外的磁盘进行磁盘分区。而expect可以实现这个效果。

手工演示

[root@localhost ~]# fdisk /dev/sdc
欢迎使用 fdisk (util-linux 2.23.2)。

更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。

Device does not contain a recognized partition table
使用磁盘标识符 0x17fc4c8a 创建新的 DOS 磁盘标签。

命令(输入 m 获取帮助):n					# 输入n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p				 # 输入p
分区号 (1-4,默认 1)# 输入 Enter
起始 扇区 (2048-41943039,默认为 2048)# 输入 Enter
将使用默认值 2048
Last 扇区, +扇区 or +size{K,M,G} (2048-41943039,默认为 41943039)# 输入Enter
将使用默认值 41943039
分区 1 已设置为 Linux 类型,大小设为 20 GiB

命令(输入 m 获取帮助):wq				# 输入wq
The partition table has been altered!

Calling ioctl() to re-read partition table.
正在同步磁盘。
[root@localhost ~]# mkfs -t ext4 /dev/sdc
mke2fs 1.42.9 (28-Dec-2013)
/dev/sdc is entire device, not just one partition!
无论如何也要继续? (y,n) y
文件系统标签=
OS type: Linux
块大小=4096 (log=2)
分块大小=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
1310720 inodes, 5242880 blocks
262144 blocks (5.00%) reserved for the super user
第一个数据块=0
Maximum filesystem blocks=2153775104
160 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
        4096000

Allocating group tables: 完成
正在写入inode表: 完成
Creating journal (32768 blocks): 完成
Writing superblocks and filesystem accounting information: 完成

[root@localhost ~]# mkdir /haha
[root@localhost ~]# mount /dev/sdc /haha
[root@localhost ~]# echo nihao > /haha/h.txt
[root@localhost ~]# ls /haha
h.txt  lost+found

脚本实践

查看脚本内容
[root@localhost ~]# cat expect_auto_partition.sh
#!/bin/bash
# 功能:shell自动磁盘分区格式化测试
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
mount_dir='/disk_check'

# 检测基本环境
[ -f /usr/bin/expect ] && echo "expect 环境正常" || ("expect 环境异常" && exit)

# 查看磁盘列表
fdisk -l | grep "磁盘 /dev/s"

# 定制磁盘分区操作
read -p "请输入需要挂载得硬盘路径: " disk_name
	
# expect自动分区操作
/usr/bin/expect << EOF
set timeout 30
spawn bash -c "fdisk ${disk_name}"
expect "命令*" {send "n\r"} 
expect "*default p*" {send "p\r"}
expect "*默认 1*" {send "\r"}
expect "起始 扇区*" {send "\r"}
expect "Last 扇区*" {send "\r"}
expect "命令*" {send "wq\r"} 
expect eof
interact	
EOF

# expect自动格式化操作
read -p "请输入硬盘的类型: " disk_type
/usr/bin/expect << EOF
set timeout 30
spawn bash -c "mkfs -t ${disk_type} ${disk_name}"
expect "*y,n*" {send "y\r"} 
expect eof
interact	
EOF

# 磁盘挂载测试
[ -d ${mount_dir} ] && rm -rf ${mount_dir} 
mkdir ${mount_dir}
mount ${disk_name} ${mount_dir}
echo disk_check > ${mount_dir}/check.txt
[ -f ${mount_dir}/check.txt ] && echo "${mount_dir} 挂载成功" || (echo "${mount_dir} 挂载失败" && exit)
umount ${mount_dir}
[ ! -f ${mount_dir}/check.txt ] && echo "${mount_dir} 卸载成功" || (echo "${mount_dir} 卸载失败" && exit)
rm -rf ${mount_dir}

创建用户实践

案例需求

	借助于expect实现在特定的主机上批量创建用户

脚本实践

查看脚本内容
[root@localhost ~]# cat expect_auto_register.sh
#!/bin/bash
# 功能:shell自动远程主机创建用户测试
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制初始变量
login_user='root'
login_pass='123456'
host_file='ip.txt'
new_user='shuji-1'
new_pass='123456'

# 批量创建用户
cat ${host_file} | while read ip
do
    expect <<-EOF
        set timeout 30
        spawn ssh $login_user@$ip
        expect {
            "yes/no" { send "yes\n";exp_continue }
            "password" { send "${login_pass}\n" }
        }

        expect "]#" { send "useradd ${new_user}\n" }
        expect "]#" { send "echo ${new_pass} |passwd --stdin ${new_user}\n" }
        expect "]#" { send "who\n" }
        expect "]#" { send "exit\n" }
        expect eof
	EOF
done
脚本执行后效果
[root@localhost ~]# /bin/bash expect_auto_register.sh
spawn ssh root@10.0.0.12
root@10.0.0.12's password:
[root@localhost ~]# useradd shuji-1
[root@localhost ~]# echo 123456 |passwd --stdin shuji-1
更改用户 shuji-1 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@localhost ~]# who
root     pts/1        2022-06-26 08:47 (10.0.0.12)
root     pts/2        2022-06-26 07:15 (10.0.0.1)
[root@localhost ~]# exit
登出
Connection to 10.0.0.12 closed.
spawn ssh root@10.0.0.13
The authenticity of host '10.0.0.13 (10.0.0.13)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.13' (ECDSA) to the list of known hosts.
root@10.0.0.13's password:
Last login: Sun Jun 26 07:16:20 2022 from 10.0.0.1
[root@localhost ~]# useradd shuji-1
[root@localhost ~]# echo 123456 |passwd --stdin shuji-1
更改用户 shuji-1 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@localhost ~]# who
root     tty1         2022-06-25 23:57
root     pts/0        2022-06-26 08:47 (10.0.0.12)
root     pts/1        2022-06-26 07:16 (10.0.0.1)
[root@localhost ~]# exit
登出
Connection to 10.0.0.13 closed.
校验用户创建
[root@localhost ~]# id shuji-1
uid=1001(shuji-1) gid=1001(shuji-1)=1001(shuji-1)
[root@localhost ~]# ssh root@10.0.0.13 id shuji-1
root@10.0.0.13's password:
uid=1001(shuji-1) gid=1001(shuji-1)=1001(shuji-1)

13 正则表达式

13.1 基础实践

13.1.1 基础知识

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

需求

	我们之前的一些操作,很大程度上都是基于特定的关键字来进行实践的,尤其是面对一些灵活的场景,我们因为过于限定一些关键字,导致灵活性上表现比较差。在shell中,它其实有一种机制,能够让我们结合特定的符号,实现非常灵活的内容操作。
	这就是正则表达式,正则表达式是用于描述字符排列和匹配模式的一种语法规则,通过它我们可以实现字符串的模式分割、匹配、查找及替换等操作。从而在各种业务逻辑的基础上,扩充数据层面的匹配,让脚本的适用性更大。

简介

	REGEXP 全称Regular Expressions,它是我们通过一些字符所定义的’linux程序用来筛选文本的模式模板。linux相关程序(比如sed、awk、grep、等)在输入数据的时候,使用正则表达式对数据内容进行匹配,将匹配成功的信息返回给我们。
	正则表达式被非常多的程序和开发语言支持:你能够想象到的编程语言,linux几乎所有编辑信息、查看信息的命令 等。

基本逻辑

	正则表达式模式,可以接收大量的数据来源,然后借助通配符、元字符、关键字等来标识数据流中的信息,将匹配成功的数据留存下来,为我们使用。

表达式分类

基本正则表达式:
	BRE Basic Regular Expressions
	- 借助于基本的属性信息实现内容的精准匹配
扩展正则表达式:
	ERE Extended Regular Expressions
	- 借助于扩展符号的能力,实现更大范围的信息匹配

简单实践

通配符和正则

1 正则表达式用来在文件中匹配符合条件的字符串,主要是目的是包含匹配。
	- grep、awk、sed 等命令可以支持正则表达式。
2 通配符用来匹配符合条件的文件名,通配符是完全匹配。
	- ls、find、cp 之类命令不支持正则表达式,可以借助于shell通配符来进行匹配。
		 .:匹配任意一个字符
         *:匹配任意内容
         ?:匹配任意一个内容
        []:匹配中括号中的一个字符

通配符实践

创建基本环境
[root@localhost ~]# touch user-{1..3}.sh  {a..d}.log
[root@localhost ~]# ls
a.log  b.log  d.log c.log 
user-1.sh  user-2.sh  user-3.sh

*匹配任意字符
[root@localhost ~]# ls *.log
a.log  b.log  c.log  d.log
[root@localhost ~]# ls u*
user-1.sh  user-2.sh  user-3.sh

?匹配一个字符
[root@localhost ~]# ls user?3*
user-3.sh

[]匹配中括号中的一个字符
[root@localhost ~]# ls user-[13]*
user-1.sh  user-3.sh

13.1.2 字符匹配

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	正则存在的根本就是对数据的匹配,而数据基本上都是有字符组成的,而正则表达式提供了非常多的字符匹配表达式,常见的表达式模式有:
单字符匹配
	.   匹配任意单个字符,当然包括汉字的匹配
	[]  匹配指定范围内的任意单个字符
		- 示例:[shuji][0-9][a-z][a-zA-Z]
	[^] 匹配指定范围外的任意单个字符
		- 示例:[^shuji] 
	 |  匹配管道符左侧或者右侧的内容

简单实践

准备配置文件

[root@localhost ~]# cat keepalived.conf
! Configuration File for keepalived

global_defs {
   router_id kpmaster
}

vrrp_instance VI_1 {
    state MASTER
    interface ens33
    virtual_router_id 50
    nopreempt
    priority 100
    advert_int 1
    virtual_ipaddress {
        192.168.8.100
    }
}

实践1-单字符过滤

.过滤单个字符
[root@localhost ~]# grep 'st..e' keepalived.conf
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
[root@localhost ~]# grep 'ens..' keepalived.conf
    interface ens33

实践2-范围单字符过滤

[] 过滤范围字符
[root@localhost ~]# grep 'i[a-z]t' keepalived.conf
    interface ens33
    virtual_router_id 50
    advert_int 1
    virtual_ipaddress {
[root@localhost ~]# grep 'i[a-n]t' keepalived.conf
    interface ens33
    advert_int 1
[root@localhost ~]# grep '[b-c]' keepalived.conf
global_defs {
vrrp_instance VI_1 {
    interface ens33
[root@localhost ~]# egrep '[x-z]' keepalived.conf
    priority 100

实践3-反向单字符过滤

只要包括的内容,都不要显示
[root@localhost ~]# grep '[^a-Z_ }{0-5]' keepalived.conf
! Configuration File for keepalived
        192.168.8.100

实践4-过滤特定的字符范围

[root@localhost ~]# egrep 'state|priority' keepalived.conf
    state MASTER
    priority 100
    
[root@localhost ~]# egrep 'st|pri' keepalived.conf
   router_id kpmaster
vrrp_instance VI_1 {
    state MASTER
    priority 100

13.1.3 锚定匹配

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的锚定匹配,主要是在字符匹配的前提下,增加了字符位置的匹配
常见符号
    ^ 					行首锚定, 用于模式的最左侧
    $ 					行尾锚定,用于模式的最右侧
    ^PATTERN$ 			用于模式匹配整行
    ^$ 					空行
    ^[[:space:]]*$ 		空白行
    \<\b   		   词首锚定,用于单词模式的左侧
    \>\b        	   词尾锚定,用于单词模式的右侧
    \<PATTERN\>     	匹配整个单词
注意: 
	单词是由字母,数字,下划线组成

简单实践

准备实践文件

[root@localhost ~]# cat nginx.conf
#user  nobody;
worker_processes  1;

http {
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践1-行首位地址匹配

行首位置匹配
[root@localhost ~]# grep '^wor' nginx.conf
worker_processes  1;

行尾位置匹配
[root@localhost ~]# grep 'st;$' nginx.conf
        server_name  localhost;

实践2-关键字匹配

关键字符串匹配
[root@localhost ~]# grep '^http {$' nginx.conf
http {
[root@localhost ~]# grep '^w.*;$' nginx.conf
worker_processes  1;

实践3-空行匹配

空行匹配
[root@localhost ~]# grep '^$' nginx.conf


[root@localhost ~]# grep  '^[[:space:]]*$' nginx.conf


# 反向过滤空行
[root@localhost ~]# grep -v '^$' nginx.conf
#user  nobody;
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践4-单词匹配

单词首部匹配
[root@localhost ~]# grep '\bloca' nginx.conf
        server_name  localhost;
        location / {
[root@localhost ~]# grep '\<loca' nginx.conf
        server_name  localhost;
        location / {
        
单词尾部匹配
[root@localhost ~]# grep 'ion\>' nginx.conf
        location / {
[root@localhost ~]# grep 'ion\b' nginx.conf
        location / {
        
单词内容匹配
[root@localhost ~]# grep '\<index\>' nginx.conf
            index  index.html index.htm;
[root@localhost ~]# grep '\<sendfile\>' nginx.conf
    sendfile        on;

13.1.4 分组符号

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	当我们使用正则模式匹配到的内容有很多项的时候,默认会全部输出。如果我们仅仅需要特定顺序的一个匹配内容的话,就用到我们这一节的知识点 -- 分组。
所谓的分组,其实指的是将我们正则匹配到的内容放到一个()里面
    - 每一个匹配的内容都会在一个独立的()范围中
    - 按照匹配的先后顺序,为每个()划分编号
    - 第一个()里的内容,用 \1代替,第二个()里的内容,用\2代替,依次类推
    - \0 代表正则表达式匹配到的所有内容
注意:
	() 范围中支持|等字符匹配内容。从而匹配更多范围的信息
	关于()信息的分组提取依赖于文件的编辑工具,我们可以借助于 sed、awk功能来实现
	提示: sed -r 's/原内容/修改后内容/'
示例:
	(M|m)any  	可以标识 Many 或者 many

简单实践

准备配置文件

准备zookeeper的配置文件
[root@localhost ~]# cat zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/server/zookeeper/data
dataLogDir=/data/server/zookeeper/logs
clientPort=2181
server.1=10.0.0.12:2182:2183
server.2=10.0.0.13:2182:2183
server.3=10.0.0.14:2182:2183:observer
4lw.commands.whitelist=stat, ruok, conf, isro

实践1-分组信息匹配实践

获取zookeeper的集群相关信息
[root@localhost ~]# egrep  '(server.[0-9])' zoo.cfg
server.1=10.0.0.12:2182:2183
server.2=10.0.0.13:2182:2183
server.3=10.0.0.14:2182:2183:observer

[root@localhost ~]# egrep  '(init|sync)Limit' zoo.cfg
initLimit=10
syncLimit=5

实践2-信息的提取

借助于sed的编辑文件功能,实现特定信息的提取
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\1/"
server.1
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\2/"
10.0.0.12
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\3/"
2182
[root@localhost ~]# grep server.1 zoo.cfg  | sed -r "s/(.*)=(.*):(.*):(.*)/\4/"
2183

13.2 进阶知识

13.2.1 限定符号

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的限定符号,主要指的是,我们通过正则表达式匹配到内容后,前面内容重复的次数,常见的服号如下:
常见符号
	* 		匹配前面的字符任意次,包括0次,贪婪模式:尽可能长的匹配
    .* 		任意长度的任意字符
    ? 		匹配其前面的字符出现0次或1次,即:可有可无
    + 		匹配其前面的字符出现最少1次,即:肯定有且 >=1{m} 	匹配前面的字符m次
    {m,n} 	匹配前面的字符至少m次,至多n次
    {,n}  	匹配前面的字符至多n次,<=n
    {n,}  	匹配前面的字符至少n次

简单实践

准备文件

[root@localhost ~]# cat file.txt
ac
abbcd
abbbce
abbbbbc
abcf

实践1-精确匹配

精确匹配 以a开头 c结尾 中间是有b或者没有b 长度不限的字符串
[root@localhost ~]# egrep "^ab*c$" file.txt
ac
abbbbbc

精确匹配 以a开头 c结尾 中间只出现一次b或者没有b的字符串
[root@localhost ~]# egrep "^ab?c$" file.txt
ac

精确匹配 以a开头 中间是有b且至少出现一次 长度不限的字符串
[root@localhost ~]# egrep "^ab+" file.txt
abbcd
abbbce
abbbbbc
abcf

精确匹配 以a开头 中间是有b且至少出现两次最多出现四次 长度不限的字符串
[root@localhost ~]# egrep "^ab{2,4}" file.txt
abbcd
abbbce
abbbbbc

精确匹配 以a开头 中间是有b且正好出现三次的字符串
[root@localhost ~]# egrep "^ab{3}" file.txt
abbbce
abbbbbc

精确匹配 以a开头 中间是有b且至少出现两次的字符串
[root@localhost ~]# egrep "^ab{2,}" file.txt
abbcd
abbbce
abbbbbc

14 正则表达式

14.1 进阶知识

14.1.1 扩展符号

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

字母模式匹配
    [:alnum:] 字母和数字
    [:alpha:] 代表任何英文大小写字符,亦即 A-Z, a-z
    [:lower:] 小写字母,示例:[[:lower:]],相当于[a-z]
    [:upper:] 大写字母
数字模式匹配
    [:digit:] 十进制数字
    [:xdigit:]十六进制数字
符号模式匹配
    [:blank:] 空白字符(空格和制表符)
    [:space:] 包括空格、制表符(水平和垂直)、换行符、回车符等各种类型的空白
    [:cntrl:] 不可打印的控制字符(退格、删除、警铃...)
    [:graph:] 可打印的非空白字符
    [:print:] 可打印字符
    [:punct:] 标点符号
    
注意:
	在使用该模式匹配的时候,一般用[[ ]],
		- 第一个中括号是匹配符[] 匹配中括号中的任意一个字符
		- 第二个[]是格式 如[:digit:]
属性模式匹配
    \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [\f\r\t\v]\S 匹配任何非空白字符。等价于 [^\f\r\t\v]
    \w 匹配一个字母,数字,下划线,汉字,其它国家文字的字符,等价于[_[:alnum:]]
    \W 匹配一个非字母,数字,下划线,汉字,其它国家文字的字符,等价于[^_[:alnum:]]

简单实践

准备文件

[root@localhost ~]# cat file1.txt
acd
abc
a_c
aZc
aZd
a c
a3c

精确匹配实践

以a开头c结尾  中间a-zA-Z0-9任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:alnum:]]c$" file1.txt
abc
aZc
a3c

以a开头c结尾  中间是a-zA-Z任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:alpha:]]c$" file1.txt
abc
aZc

以a开头c结尾  中间是0-9任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:digit:]]c$" file1.txt
a3c

以a开头c结尾  中间是a-z任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:lower:]]c$" file1.txt
abc



以a开头c结尾  中间是A-Z任意字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:upper:]]c$" file1.txt
aZc

以a开头c结尾  中间是可打印符号  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:print:]]c$" file1.txt
abc
a_c
aZc
a c
a3c

以a开头c结尾  中间是符号字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:punct:]]c$" file1.txt
a_c

以a开头c结尾  中间是空格或者TAB符字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:blank:]]c$" file1.txt
a c
[root@localhost ~]# egrep "^a[[:space:]]c$" file1.txt
a c

以a开头c结尾  中间是十六进制字符  长度为三个字节的字符串
[root@localhost ~]# egrep "^a[[:xdigit:]]c$" file1.txt
abc
a3c

14.1.2 目标检测

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

需求

	定制站点或目标主机的检测平台,在对站点域名和主机ip检测之前,判断输入的语法是否正确。

ip检测

定制ip地址文件
[root@localhost ~]# cat testip.txt
112.456.44.55
256.18.56.1
10.0.0.12

匹配ip地址
[root@localhost ~]# egrep '(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|2[0-5][0-9]|25[0-4])$'  testip.txt
10.0.0.12

网址检测

定制ip地址文件
[root@localhost ~]# cat testsite.txt
http://www.baidu.com
www.126.com
163.com
http.example.comcom

匹配ip地址
[root@localhost ~]# egrep '((http|https|ftp):\/\/)?(www\.)?([0-Z]+\.)([a-Z]{2,5})$'  testsite.txt
http://www.baidu.com
www.126.com
163.com

简单实践

脚本内容

查看脚本内容
[root@localhost ~]# cat target_check.sh
#!/bin/bash
# 功能:定制主机存活的检测功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制目标类型变量
target_type=(主机 网站)

# 定制检测ip地址格式的函数
check_ip(){
    # 接收函数参数
    IP=$1
    ip_regex='(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|2[0-5][0-9]|25[0-4])$'
    # 判断ip地址是否有效
    echo $IP | egrep "${ip_regex}" >/dev/null && echo "true" || echo "false"
}

# 定制网址的格式检测函数
check_url(){
    # 接收函数参数
    site=$1
    site_regex='((http|https|ftp):\/\/)?(www\.)?([0-Z]+\.)([a-Z]{2,5})$'
    # 判断网址地址是否有效
    echo $site | egrep "${site_regex}" >/dev/null && echo "true" || echo "false"
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------确定检测目标类型---------------"
    echo -e " 1: 主机  2: 网站"
    echo -e "-------------------------------------------\033[0m"
}

# 目标主机检测过程
host_ip_check(){
    read -p "> 请输入要检测的主机ip: " ip_addr
    result=$(check_ip ${ip_addr})
    if [ ${result} == "true" ];then
       ping -c1 -W1 ${ip_addr} &> /dev/null && echo "${ip_addr} 状态正常" || echo "${ip_addr} 状态不可达"
    else
       echo "目标ip格式异常"
    fi
}

# 目标站点检测过程
net_site_check(){
    read -p "> 请输入要检测的网站地址: " site_addr
    result=$(check_url ${site_addr})
    if [ ${result} == "true" ];then
        curl -s -o /dev/null ${site_addr} && echo "${site_addr} 状态正常" || echo "${site_addr} 状态异常"
    else
        echo "目标网址格式异常"
    fi
}

# 定制帮助信息
Usage(){
    echo "请输入正确的检测目标类型"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要检测的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "主机" ];then
        host_ip_check
    elif [ ${target_type[$target_id-1]} == "网站" ];then
        net_site_check
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash target_check.sh
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 1
> 请输入要检测的主机ip: 1aaa
目标ip格式异常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 1
> 请输入要检测的主机ip: 10.0.0.12
10.0.0.12 状态正常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 1
> 请输入要检测的主机ip: 10.0.0.13
10.0.0.13 状态不可达
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 2
> 请输入要检测的网站地址: www
目标网址格式异常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 2
> 请输入要检测的网站地址: www.baidu.com
www.baidu.com 状态正常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: 2
> 请输入要检测的网站地址: www.nihaoxxxxxx.com
www.nihaoxxxxxx.com 状态异常
---------------确定检测目标类型---------------
 1: 主机  2: 网站
-------------------------------------------
> 请输入要检测的目标类型: ^C
[root@localhost ~]#

14.1.3 登录检测

这一节,我们从 需求简介、简单实践、小结 三个方面来学习

基础知识

简介

	在很多的应用交互页面,经常会出现一些用户输入的信息:
		账号登录场景: 比如用户名、密码、手机号、邮箱之类的校验信息

手机号匹配

准备手机号文件
[root@localhost ~]# cat phone.txt
13412345678
135666666667
13a12345678
198123456

过滤真正的手机号
[root@localhost ~]# egrep '\<1[3-9][0-9]{9}\>' phone.txt
13412345678

邮箱地址匹配

定制邮箱地址文件
[root@localhost ~]# cat testemail.txt
admin@qq.com
1881111@gmail.eduedu
10.0.0.12
"shuji@qq.com
123_shuji@12306.cn

匹配邮箱地址
[root@localhost ~]# egrep  "^[0-Z_]+\@[0-Z]+\.[0-Z]{2,5}$" testemail.txt
admin@qq.com
123_shuji@12306.cn

简单实践

脚本内容

查看脚本内容
[root@localhost ~]# cat register_login_manager.sh
#!/bin/bash
# 功能:定制管理界面的登录注册功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制目标类型变量
target_type=(登录 注册)

# 定制普通变量
user_regex='^[0-Z_@.]{6,15}$'
passwd_regex='^[0-Z.]{6,8}$'
phone_regex='^\<1[3-9][0-9]{9}\>$'
email_regex='^[0-Z_]+\@[0-Z]+\.[0-Z]{2,5}$'

# 检测用户名规则
check_func(){
    # 接收函数参数
    target=$1
    target_regex=$2
    # 判断目标格式是否有效
    echo $target | egrep "${target_regex}" >/dev/null && echo "true" || echo "false"
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台登录界面---------------"
    echo -e " 1: 登录  2: 注册"
    echo -e "-------------------------------------------\033[0m"
}

# 定制帮助信息
Usage(){
    echo "请输入正确的操作类型"
}

# 管理平台用户注册过程
user_register_check(){
    read -p "> 请输入用户名: " login_user
    user_result=$(check_func ${login_user} ${user_regex})
    if [ ${user_result} == "true" ];then
        read -p "> 请输入密码: " login_passwd
        passwd_result=$(check_func ${login_passwd} ${passwd_regex})
        if [ ${passwd_result} == "true" ];then
            read -p "> 请输入手机号: " login_phone
            phone_result=$(check_func ${login_phone} ${phone_regex})
            if [ ${phone_result} == "true" ];then
                read -p "> 请输入邮箱: " login_email
                email_result=$(check_func ${login_email} ${email_regex})
                if [ ${email_result} == "true" ];then
                    echo -e "\e[31m----用户注册信息内容----"
                    echo -e " 用户名称: ${login_user}"
                    echo -e " 登录密码: ${login_passwd}"
                    echo -e " 手机号码: ${login_phone}"
                    echo -e " 邮箱地址: ${login_email}"
                    echo -e "------------------------\033[0m"
                    read -p "> 是否确认注册[yes|no]: " login_status
                    [ ${login_status} == "yes" ] && echo "用户 ${login_user} 注册成功" && exit || return
                else
                   echo "邮箱地址格式不规范"
                fi
            else
                echo "手机号码格式不规范"
            fi
        else
            echo "登录密码格式不规范"
        fi
    else
        echo "用户名称格式不规范"
    fi
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "登录" ];then
        echo "开始登录管理平台..."
    elif [ ${target_type[$target_id-1]} == "注册" ];then
        user_register_check
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash register_login_manager.sh
---------------管理平台登录界面---------------
 1: 登录  2: 注册
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入用户名: root12345
> 请输入密码: 12345678
> 请输入手机号: 13412345678
> 请输入邮箱: qq@123.com
----用户注册信息内容----
 用户名称: root12345
 登录密码: 12345678
 手机号码: 13412345678
 邮箱地址: qq@123.com
------------------------
> 是否确认注册[yes|no]: yes
用户 root12345 注册成功
[root@localhost ~]# /bin/bash register_login_manager.sh
---------------管理平台登录界面---------------
 1: 登录  2: 注册
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入用户名: admin123
> 请输入密码: 12345678
> 请输入手机号: 14456789090
> 请输入邮箱: qq@qq.com
----用户注册信息内容----
 用户名称: admin123
 登录密码: 12345678
 手机号码: 14456789090
 邮箱地址: qq@qq.com
------------------------
> 是否确认注册[yes|no]: no
---------------管理平台登录界面---------------
 1: 登录  2: 注册
-------------------------------------------
> 请输入要操作的目标类型:

15 sed命令

15.1 基础实践

15.1.1 基础语法

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

场景

	shell脚本虽然功能很多,但是它最常用的功能还是处理文本文件,尤其是在正常的业务操作流程场景中,比如检查日志文件、读取配置、处理数据等现象,虽然我们能够使用echo、cat、<<>>|等符号实现文件内容的操作,但是整个过程有些繁琐。所以我们需要一种更为轻便的文本编辑工具,sed就是其中的一种。

简介

	sed(Stream EDitor) 属于一种数据流式的行文件编辑工具。因为它编辑文件的时候,在内存中开辟一块额外的模式空间(pattern space),然后以行为单位读取文件内容到该空间中,接着sed命令处理该空间中的内容,默认在当前终端界面打印内容,然后清空模式空间内容,再来读取第二行内容,依次循环下去。
    作用:用来自动编辑一个或多个文件,简化对文件的反复操作,编写转换程序等。
    参考:http://www.gnu.org/software/sed/manual/sed.html

语法格式

基本格式	
	sed [参数] '<匹配条件> [动作]' [文件名]
注意:
	匹配条件和动作两侧有'
	动作可以有多个,彼此间使用;隔开,比如 '2p;4p'
参数详解:
    参数为空	 表示sed的操作效果,实际上不对文件进行编辑,缓存区所有信息都显示
    -n			不输出模式空间内容到屏幕,即不自动打印所有内容
    -e			基于命令实现对文件的多点编辑操作
    -f			从指定文件中读取编辑文件的”匹配条件+动作”
    -r			支持使用扩展正则表达式
    -i.bak		复制文件原内容到备份文件,然后对原文件编辑
    -i			表示对文件进行编辑

注意:
   mac版本的bash中使用 -i参数,必须在后面单独加个东西: -i ''
    -i -r 支持  -ri   支持
    -ir   不支持
    -ni   危险选项,会清空文件
匹配条件分为两种:数字行号或者关键字匹配
数字行号:
	空 表示所有行				n 表示第n行				$ 表示末尾行
	n,m 表示第n到m行内容		n,+m 表示第n到n+m行
	~步进	1~2 表示奇数行		2~2 表示偶数行

关键字匹配格式:
	'/关键字/'
    注意:
        隔离符号 / 可以更换成 @、#、!等符号
        根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。
        /关键字1/,/关键字2/ 表示关键字1所在行到关键字2所在行之间的内容
        n,/关键字2/ 表示从第n行到关键字2所在行之间的内容
动作详解
    -a[\text]			在匹配到的内容下一行增加内容,支持\n实现多行追加
    -i[\text]			在匹配到的内容当前行增加内容
    -c[\text]			在匹配到的内容替换内容
    -d|p				删除|打印匹配到的内容
    -s					替换匹配到的内容
    W /path/somefile 	保存模式匹配的行至指定文件
    r /path/somefile	读取指定文件的文本至模式空间中
    =					为模式空间中的行打印行号
    !					模式空间中匹配行取反处理
注意:
	上面的动作应该在参数为-i的时候使用,不然的话不会有效果

简单实践

准备工作

模板文件内容
[root@localhost ~]# cat sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed7 sed8 sed9

实践1-打印信息

默认打印信息
[root@localhost ~]# sed '2p' sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed4 sed5 sed6			# 这一行才是操作的内容,其他的都是缓存区自动输出
nihao sed7 sed8 sed9

打印第2行,不输出缓存区默认的其他信息
[root@localhost ~]# sed -n '2p' sed.txt 
nihao sed4 sed5 sed6

打印第1,3行,不输出缓存区默认的其他信息
[root@localhost ~]# sed -n '1p;3p' sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

打印网卡信息
[root@localhost ~]# ifconfig eth0 | sed -n '2p'
        inet 10.0.0.12  netmask 255.255.255.0  broadcast 10.0.0.255
        
打印磁盘信息
[root@localhost ~]# df | sed -n '/^\/dev\/sd/p'
/dev/sda2      19911680 2772988 17138692   14% /
/dev/sda1       1038336  145380   892956   15% /boot

实践2-匹配内容打印

打印包含sed4的行
[root@localhost ~]# sed -n '/sed4/p' sed.txt 
nihao sed4 sed5 sed6

打印奇数行
[root@localhost ~]# sed -n '1~2p' sed.txt      
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

打印偶数行
[root@localhost ~]# sed -n '0~2p' sed.txt
nihao sed4 sed5 sed6

实践3-文件编辑

-e实现多次文件编辑动作
[root@localhost ~]# sed -n -e '1p' -e '3p' sed.txt    
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

将文件操作命令输出到一个文件
[root@localhost ~]# echo -e "1p\n3p" > sed_script
借助文件里面的命令实现文件编辑
[root@localhost ~]# sed -n -f sed_script sed.txt 
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

实践4-其他信息显示

取反显示
[root@localhost ~]# sed -n '2!p' sed.txt            
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9


查看内容属于第几行
[root@localhost ~]# sed -n '/sed4/=' sed.txt  
2

15.1.2 内容替换

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	sed的文本替换动作是使用频率最高的一种样式。它的基本表现样式如下:
命令格式:
	sed -i [替换格式] [文件名]
	源数据 | sed -i [替换格式]
	
注意:替换命令的写法
	's###'  --->  's#原内容##' ---> 's#原内容#替换后内容#'
	隔离符号 / 可以更换成 @、#、!等符号
表现样式:
    样式一:替换指定匹配的内容
        sed -i '行号s#原内容#替换后内容#列号' [文件名]
        echo "源数据" | sed -i '行号s#原内容#替换后内容#列号'
    样式二:替换所有的内容
        sed -i 's#原内容#替换后内容#g' [文件名]
    	echo "源数据" | sed -i '行号s#原内容#替换后内容#g'
    样式三: 替换指定的内容
    	sed -i '行号s#原内容#&新增信息#列号' [文件名]
    	- 这里的&符号代表源内容,实现的效果是 '原内容+新内容'

简单实践

实践1-替换每行首个匹配内容

格式:sed -i 's#原内容#替换后内容#' 文件名

替换首每行的第1个sed为SED
[root@localhost ~]# sed -i 's#sed#SED#' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED1 sed2 sed3
nihao SED4 sed5 sed6
nihao SED7 sed8 sed9

实践2-替换全部匹配内容

格式:sed -i 's#原内容#替换后内容#g' 文件名

替换全部sed为des
[root@localhost ~]# sed -i 's#sed#SED#g' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED1 SED2 SED3
nihao SED4 SED5 SED6
nihao SED7 SED8 SED9

关于全部替换还有另外一种命令叫直接转换 y
[root@localhost ~]# sed 'y/SED/sed/' sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed7 sed8 sed9

实践3-指定行号替换首个匹配内容

格式:sed -i '行号s#原内容#替换后内容#' 文件名

替换第2行的首个SED为sed
[root@localhost ~]# sed -i '2s#SED#sed#' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED1 SED2 SED3
nihao sed4 SED5 SED6
nihao SED7 SED8 SED9

实践4-首行指定列号替换匹配内容

格式:sed -i 's#原内容#替换后内容#列号' 文件名

替换每行的第2个SED为sed
[root@localhost ~]# sed -i 's#SED#sed#2' sed.txt
[root@localhost ~]# cat sed.txt 
nihao SED1 sed2 SED3
nihao sed4 SED5 sed6
nihao SED7 sed8 SED9

实践5-指定行号列号匹配内容

格式:sed -i '行号s#原内容#替换后内容#列号' 文件名

替换第3行的第2个SED为sed
[root@localhost ~]# sed -i '3s#SED#sed#2' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED sed2 SED3
nihao sed4 SED5 sed6
nihao SED7 sed8 sed9

实践6-综合实践

借助正则的分组功能实现ip地址获取
[root@localhost ~]# ifconfig eth0 | sed -n '2p' | sed -r 's#.*inet (.*) net.*#\1#'
10.0.0.12
[root@localhost ~]# ifconfig eth0 | sed -n '2p' | sed -r 's#.*inet ##' | sed -r 's# net.*##' 
10.0.0.12

借助正则的分组功能实现信息的精确获取
[root@localhost ~]# echo '/etc/sysconfig/network' | sed -r 's#(.*\/)([^/]+\/?$)#\2#'
network
[root@localhost ~]# echo '/etc/sysconfig/network' | sed -r 's#(.*\/)([^/]+\/?$)#\1#'
/etc/sysconfig/

15.1.3 增加操作

这一节,我们从 追加实践、插入实践、小结 三个方面来学习

追加实践

基本语法

作用:
	在指定行号的下一行增加内容
格式:
	sed -i '行号a\增加的内容' 文件名
注意:
    如果增加多行,可以在行号位置写个范围值,彼此间使用逗号隔开,例如
    sed -i '1,3a\增加内容' 文件名

实践1-基于行号实践

指定行号增加内容
[root@localhost ~]# sed -i '2a\zengjia-2' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED sed2 SED3
nihao sed4 SED5 sed6
zengjia-2
nihao SED7 sed8 sed9
指定1~3每行都增加内容
[root@localhost ~]# sed -i '1,3a\tongshi-2' sed.txt 
[root@localhost ~]# cat sed.txt 
nihao SED sed2 SED3
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

插入实践

基本语法

作用:
	在指定行号的当行增加内容
格式:
	sed -i '行号i\增加的内容' 文件名
注意:
    如果增加多行,可以在行号位置写个范围值,彼此间使用逗号隔开,例如
    sed -i '1,3i\增加内容' 文件名

实践1-基于行号实践

指定行号增加内容
[root@localhost ~]# sed -i '1i\insert-1' sed.txt 
[root@localhost ~]# cat sed.txt 
insert-1
nihao SED sed2 SED3
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

指定1~3每行都增加内容
[root@localhost ~]# sed -i '1,3i\insert-2' sed.txt 
[root@localhost ~]# cat sed.txt
insert-2
insert-1
insert-2
nihao SED sed2 SED3
insert-2
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

15.1.4 删除替换

这一节,我们从 删除实践、替换实践、小结 三个方面来学习

删除实践

基本语法

作用:
	指定行号删除
格式:
	sed -i '行号d' 文件名
注意:
    如果删除多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3d' 文件名

实践1-基于行号实践

删除第4行内容
[root@localhost ~]# sed -i '4d' sed.txt 
[root@localhost ~]# cat sed.txt 
insert-2
insert-1
insert-2
insert-2
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

删除多行(1-6行)内容
[root@localhost ~]# sed -i '1,6d' sed.txt
[root@localhost ~]# cat sed.txt
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

替换实践

基本语法

作用:
	指定行号进行整行替换
格式:
	sed -i '行号c\内容' 文件名
注意:
    如果替换多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3c\内容' 文件名

实践1-基于行号实践

替换第3行内容
[root@localhost ~]# sed -i '3c\tihuan-1' sed.txt
[root@localhost ~]# cat sed.txt
tongshi-2
zengjia-2
tihuan-1
nihao SED7 sed8 sed9


指定1~3行都替换成一行内容
[root@localhost ~]# sed -i '1,3c\tihuan-3' sed.txt
[root@localhost ~]# cat sed.txt
tihuan-3
nihao SED7 sed8 sed9

15.1.5 加载保存

这一节,我们从 加载实践、保存实践、小结 三个方面来学习

加载实践

基本语法

作用:
	加载文件内容到指定行号的位置
格式:
	sed -i '行号r 文件名1' 文件名
注意:
    如果在多行位置加载,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3r 文件名1' 文件名

实践1-基于行号实践

加载第3行内容
[root@localhost ~]# sed -i '2r sed.txt' sed.txt
[root@localhost ~]# cat sed.txt
tihuan-3
nihao SED7 sed8 sed9
tihuan-3
nihao SED7 sed8 sed9
注意;
	由于缓存区中文件内容的顺序变化,导致加载的内容顺序不一致

制定内容文件,加载到2-4行下面
[root@localhost ~]# sed -i '2,4r sed_script' sed.txt
[root@localhost ~]# cat sed_script
1p
3p
[root@localhost ~]# cat sed.txt
tihuan-3
nihao SED7 sed8 sed9
1p
3p
tihuan-3
1p
3p
nihao SED7 sed8 sed9
1p
3p

保存实践

基本语法

作用:
	指定行号保存到其他位置
格式:
	sed -i '行号w 文件名' 文件名
注意:
    如果多行保存,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3w 文件名' 文件名
    文件名已存在,则会覆盖式增加

实践1-基于行号实践

保存第3行内容
[root@localhost ~]# sed -i '2w sed_test' sed.txt
[root@localhost ~]# cat sed_test
nihao SED7 sed8 sed9



指定2~4行内容保存到一个文件中
[root@localhost ~]# sed -i '1,4w sed_test' sed.txt
[root@localhost ~]# cat sed_test
tihuan-3
nihao SED7 sed8 sed9
1p
3p

15.2 进阶实践

15.2.1 匹配进阶

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	我们之前的所有操作基本上都是基于行的操作,其实本质上还有另外一些操作 -- 基于内容的操作。语法格式如下:
内容匹配:
	'/关键字内容/'
    注意:
        隔离符号 / 可以更换成 @、#、!等符号
        根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。
        /关键字1/,/关键字2/ 表示关键字1所在行到关键字2所在行之间的内容
        n,/关键字2/ 表示从第n行到关键字2所在行之间的内容
        /关键字1/,n, 表示从关键字1所在行到第n行之间的内容
        /关键字1/,+n, 表示从关键字1所在行到(所在行+n行)之间的内容

简单实践

实践1-内容的简单匹配显示

查看匹配的内容
[root@localhost ~]# sed -n '/send/p' nginx.conf
    sendfile        on;

匹配内容间的多行信息
[root@localhost ~]# sed -n '/send/,/server/p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

    server {

查看匹配内容到第6行的内容
[root@localhost ~]# sed -n '/send/,6p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;
 
查看第1行到匹配行的内容
[root@localhost ~]# sed -n '1,/send/p' nginx.conf
#user  nobody;
worker_processes  1;

http {
    sendfile        on;
    
查看匹配内容和下面三行的内容
[root@localhost ~]# sed -n '/send/,+3p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

    server {

通过 !p 去除空行匹配
[root@localhost ~]# sed -n '/^$/!p' nginx.conf
#user  nobody;
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
借助分组功能,实现多信息的剔除
[root@localhost ~]# sed -rn '/^(#|$)/!p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践2-分组信息显示

获取制定文件所在的路径信息
[root@localhost ~]# echo "/etc/sysconfig/network" |sed -r 's#(^/.*/)([^/]+/?)#\1#'
/etc/sysconfig/

获取制定文件名称
[root@localhost ~]# echo "/etc/sysconfig/network" |sed -r 's#(^/.*/)([^/]+/?)#\2#'
network

获取ip地址
[root@localhost ~]# ifconfig eth0 |sed -nr "2s/[^0-9]+([0-9.]+).*/\1/p"
10.0.0.12

获取MAC地址
[root@localhost ~]# ifconfig eth0 |sed -nr "4s/[^0-9]+([0-Z:]+).*/\1/p"
00:0c:29:23:23:8c

15.2.2 修改实践

这一节,我们从 多点操作、增改实践、小结 三个方面来学习

多点操作

简介

我们可以借助 '动作1;动作2' 或者 -e '动作1' -e '动作2' 的方式实现多操作的并行实施

实践1-内容的过滤编辑

不显示所有空行和注释信息
[root@localhost ~]# sed '/^#/d;/^$/d' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

先剔除空行,然后不显示所有包含注释的信息
[root@localhost ~]# sed -rn '/^$/d;/^[[:space:]]*#/!p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践2-借助于 i.bak 方式对有效信息进行过滤

编辑文件的时候,原内容备份到一个额外的文件
[root@localhost ~]# sed -i.bak '/^#/d;/^$/d' nginx.conf
[root@localhost ~]# cat nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
[root@localhost ~]# grep '#' nginx.conf.bak
#user  nobody;

增改实践

实践1-借助于&符号实现内容的扩充式更改编辑

查看原内容
[root@localhost ~]# head -n 1 /etc/passwd
root:x:0:0:root:/root:/bin/bash

对原内容进行扩充替换
[root@localhost ~]# head -n 1 /etc/passwd | sed -n 's/root/&user/1p'
rootuser:x:0:0:root:/root:/bin/bash
[root@localhost ~]# head -n 1 /etc/passwd | sed -n 's/root/&user/gp'
rootuser:x:0:0:rootuser:/rootuser:/bin/bash

实践2-借助于s实现内容的替换式更改编辑

获取没有被注释的信息
[root@localhost ~]# sed -n '/^#/!p' /etc/fstab

UUID=5583bd7c-cc9f-4e19-b453-c224102f3ed5 /      xfs     defaults        0 0
UUID=cbd246cd-1df8-4fe7-9040-823cd0978837 /boot  xfs     defaults        0 0

将注释的信息进行替换
[root@localhost ~]# sed -rn '/^#/!s@^@#@p' /etc/fstab
#
#UUID=5583bd7c-cc9f-4e19-b453-c224102f3ed5 /     xfs     defaults        0 0
#UUID=cbd246cd-1df8-4fe7-9040-823cd0978837 /boot xfs     defaults        0 0

实践3-借助于 i|a 对文件进行 插入|追加 式更改编辑

基于内容匹配相关信息并打印
[root@localhost ~]# sed -n '/listen/p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
基于内容匹配追加1行内容
[root@localhost ~]# sed '/listen/a\\tlisten\t\t80;' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        listen          80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
基于内容匹配插入2行内容 -- 借助于\n的换行功能,将1行变成两行
[root@localhost ~]# sed '/listen/i\\tlisten\t\t80;\n\tlisten\t\t8080;' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen          80;
        listen          8080;
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

实践4-借助于 环境变量和s|c 对文件进行 修改|替换 式更改编辑

定制环境变量
[root@localhost ~]# port=8080

使用多点修改
[root@localhost ~]# sed -r -e "s/listen.*;/listen\t$port;/" -e '/server_name/c \\tserver_name '$(hostname):$port';' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen  8080;
        server_name localhost:8080;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
注意:
	这里涉及到环境变量的解读,千万不要被单引号转义了

15.2.3 高阶用法1

这一节,我们从 基础知识、缓存实践、小结 三个方面来学习。

基础知识

简介

	对于sed命令来说,除了我们经常使用的模式空间之外,它还支持一个叫暂存空间(Hold Space)的模式,所谓的暂存空间,也就是说,将模式空间中的数据,临时保存到暂存空间,从而实现更为强大的功能。
	相关业务逻辑流程如下:
空间解读:
	缓存空间用于sed的内容模式匹配,一般称为模式空间
		- 模式空间内的信息可以输出到终端界面,除非模式空间的内容被删除或取消打印导致模式空间清空
	附加于缓存空间的附加缓存空间,一般称为暂存空间
		- 通过相关命令可以在模式空间信息清零之前,暂存到附加缓存空间,便于后续使用
	两个空间之间,可以基于一些高阶命令实现信息的传递
常见的高阶命令  
    n 读取匹配到的行的下一行覆盖至模式空间
    N 读取匹配到的行的下一行追加至模式空间
    d 删除模式空间中的行
    D 如果模式空间包含换行符,则循环删除换行符前的内容,直至不包含任何换行符后,执行后续d操作

简单实践

实践1-模式空间覆盖

查看逐行读取的信息
[root@localhost ~]# seq 6 | sed -n "p"
1
2
3
4
5
6

n 读取匹配到的行的下一行覆盖至模式空间
[root@localhost ~]# seq 6 | sed -n "n;p"
2
4
6
解读:
	第一次读的是1,"n;"的作用是读取2,然后覆盖模式空间的1

2次n代表读取到第3行,将前的内容覆盖
[root@localhost ~]# seq 6 | sed -n "n;n;p"
3
6

3次n代表读取到第4行,将前面的内容覆盖
[root@localhost ~]# seq 6 | sed -n "n;n;n;p"
4

获取匹配内容的下一行,覆盖匹配的内容
[root@localhost ~]# seq 6 | sed -n "/3/{n;p}"
4
解读:
	/3/ 代表的是匹配的3内容,然后{} 代表一个表达式区域,n;p 代表下一行覆盖式打印

实践2-模式空间清零

n 读取匹配到的行的下一行覆盖至模式空间
[root@localhost ~]# seq 6 | sed -n "n;p"
2
4
6
解读:
	第一次读的是1,"n;"的作用是读取2,然后覆盖模式空间的1
	
d 删除模式空间中的行
[root@localhost ~]# seq 6 | sed -n "n;d"
[root@localhost ~]#
解读:
	n每覆盖一次,都用d删除一次,最终导致不会输出任何内容

实践3-模式空间扩容

查看默认的信息输出
[root@localhost ~]# seq 6
1
2
3
4
5
6

N 读取匹配到的行的下一行追加至模式空间
[root@localhost ~]# seq 6 | sed 'N;s/\n//'
12
34
56
解读:
	第一次读的是1,"N;"的作用是读取2,然后追加到模式空间的1的后面
	然后使用s将换行\n替换为空,实现1和2的合并
	
通过-e,多一个N,就相当于为模式空间扩容了一个位置
[root@localhost ~]# seq 6 | sed -e 'N;s/\n//' -e 'N;s/\n//'
123
456
可以看到:
	1个空间+2个扩容 一共三个空间内容

15.2.4 高阶用法2

这一节,我们从 暂存实践、其他实践、小结 三个方面来学习。

暂存实践

简介

	我们可以在缓存空间和暂存空间中进行数据的简单读取,还可以对数据进行一些复杂性的编辑操作
常见的高阶命令
    P 打印模式空间开端至\n内容,并追加到默认输出之前
    h 把模式空间中的内容覆盖至暂存空间中
    H 把模式空间中的内容追加至暂存空间中
    g 从暂存空间取出数据覆盖至模式空间
    G 从暂存空间取出内容追加至模式空间
    x 把模式空间中的内容与暂存空间中的内容进行互换

实践1-暂存空间基本实践

获取指定内容信息
[root@localhost ~]# seq 4 > seq.txt
[root@localhost ~]# cat -e seq.txt
1$
2$
3$
4$
结果显示;
	每一行后面都有换行符号$
h 把模式空间中的内容覆盖至暂存空间中 G 从暂存空间取出内容追加至模式空间
[root@localhost ~]# sed -e '/2/h' -e '$G' seq.txt
1
2
3
4
2
解读:
	/2/h 将匹配到的内容存储到 暂存空间
	$ 正常信息输出的时候,不输出暂存空间的信息
	G 代表信息操作完毕后,将暂存区的内容,追加到模式空间

取消$,每次输出信息的时候,同时输出缓存区和暂存区的内容
[root@localhost ~]# sed -e '/2/h' -e 'G' seq.txt
1
					# 此时暂存区为空
2
2					# 此时暂存区内容为2
3
2					# 此时暂存区内容为2
4
2					# 此时暂存区内容为2
结果显示:
	每次输出信息的时候,都会输出暂存区信息

numG 代表仅在num位置输出暂存区信息
[root@localhost ~]# sed -e '/2/h' -e '1G' seq4.txt
1
				# 在第1个位置输出暂存区信息
2
3
4

num1,num2G 代表仅在num1-num2范围的位置输出暂存区信息
[root@localhost ~]# sed -e '/2/h' -e '1,2G' seq4.txt
1
				# 在第1个位置输出暂存区信息
2
2				# 在第2个位置输出暂存区信息
3
4

num!G 代表在num之外的位置输出暂存区信息
[root@localhost ~]# sed -e '/2/h' -e '1!G' seq4.txt
1
2
2
3
2
4
2

实践2-暂存区使用后,清理模式空间内容

查看文件内容
[root@localhost ~]# cat -e seq.txt
1$
2$
3$

将匹配的内容转移至暂存区,然后清理模式空间
[root@localhost ~]# sed -e '/2/{h;d}' -e 'G' seq.txt
1
					# 此时暂存区为空
3					# 缓存区被清理,所以没有输出2
2
4
2

实践3-暂存区使用后,处理清理模式空间内容,

将匹配的内容转移至暂存区,然后清理模式空间,接着将暂存区信息输出到特定的位置
[root@localhost ~]# sed -e '/2/{h;d}' -e '/3/{G;}' seq4.txt
1
3
2
4
解读:
	暂存区的信息在/3/后面显示
	-e '/3/{G;}' 可以简写为 -e '/3/G'

g 从保持空间取出数据覆盖至模式空间
[root@localhost ~]# sed -e '/2/{h;d}' -e '/3/g' seq4.txt
1
2
4
解读:
	g 的作用,是将/3/匹配到的缓存区内容被暂存区的信息覆盖,则缓存区内容是2
	
x 把模式空间中的内容与暂存空间中的内容进行互换
[root@localhost ~]# sed -e '/2/{h;d}' -e '/3/{x;G}' seq4.txt
1
2				# 缓存区的3被暂存区的2替换了
3				# 暂存区的2被缓存区的3替换了
4
解读:
	x 的作用,是将/3/匹配到的缓存区内容和暂存区的信息交换,则缓存区内容是2,暂存区是3

其他实践

实践1-内容倒序实践

查看文件内容
[root@localhost ~]# cat seq.txt
1
2
3
4
除了第1行不输出暂存区,其他都输出暂存区值
[root@localhost ~]# sed -e '1!G' seq.txt
1
2
					# 第2处位置的暂存区为空
3
					# 第3处位置的暂存区为空
4
					# 第4处位置的暂存区为空

h将所有模式空间的内容覆盖到暂存区 
[root@localhost ~]# sed -e '1!G;h' seq.txt
1					# 暂存区在第1处缓存区不输出
----
2					# 缓存空间的1覆盖暂存区,然后在当前缓存区的2之后输出--追加
1					
---
3					# 缓存空间的21覆盖暂存区,然后在当前缓存区的3之后输出--追加
2					
1
--- 
4					# 缓存空间的321覆盖暂存区,然后在当前缓存区的4之后输出--追加
3
2
1

$!d 代表除了最后一个位置内容不删除,其他的都清除掉
[root@localhost ~]# sed -e '1!G;h;$!d' seq.txt
4
3
2
1

实践2-提取关键信息的前一行

查看文件内容
[root@localhost ~]# cat seq4.txt
1
2
3
4

除了第1行不被暂存区覆盖,其他缓存区被暂存区覆盖
[root@localhost ~]# sed -e '1!g' seq4.txt
1
 					# 第2处位置被暂存区的空覆盖
					# 第3处位置被暂存区的空覆盖
					# 第4处位置被暂存区的空覆盖 
将匹配到的3不打印,存放到暂存区,然后仅输出缓存区信息
[root@localhost ~]# sed -n '/3/!p;h' seq4.txt
1
2
4
解读:
	/3/ 匹配到第3行的内容3,使用!p不输出,然后依次将缓存区内容覆盖暂存区
	最终输出的时候仅有一个区显示,另一个区为空
[root@localhost ~]# sed -n '/3/g;p;h' seq4.txt
1		# 条件不匹配,交给h动作,缓存区的1会覆盖到暂存区
2		# 条件不匹配,交给h动作,缓存区的2会覆盖到暂存区
2		# 条件不匹配,交给h动作,暂存区的2通过g覆盖缓存区的3,所以输出2
4

{} 是shell中的一种独立区域,所做操作会直接影响当前的shell环境
[root@localhost ~]# sed -n '/3/{g;p};h' seq4.txt
2	
	{g;p} 代表直接将暂存区域的数据2覆盖缓存空间,由于{} 直接作用于shell环境
	所以p输出的时候,暂存区没有信息,缓存区域有2,仅仅输出2

16 sed命令

16.1 进阶实践

16.1.1 脚本实践

这一节,我们从 案例需求、简单实践、小结 三个方面来学习。

案例需求

需求

案例描述:
	搭建一个ftp服务器
	
属性要求
    1 不支持本地用户登录		
        local_enable=NO
    2 匿名用户可以上传 新建 删除	 
        anon_upload_enable=YES  anon_mkdir_write_enable=YES	
    3 匿名用户限速500KBps  
        anon_max_rate=500000

准备工作

使用sed获取ip地址
ipaddr=$(ifconfig eth0 | sed -n '2p' | sed -e 's/.*inet \(.*\) net.*/\1/g')
iptail=$(echo $ipaddr|cut -d'.' -f4)
ipremote=10.0.0.13
修改主机名
hostname server$iptail.localhost.com
echo "HOSTNAME=server$iptail.localhost.com" >> /etc/sysconfig/network
echo "$ipaddr server$iptail.localhost.com" >> /etc/hosts

环境部署

安装软件
yum -y install vsftpd lftp

备份配置
cp /etc/vsftpd/vsftpd.conf{,.default}

清理无效信息
sed -rn '/^(#|$)/!p' /etc/vsftpd/vsftpd.conf
sed -ri '/^#/d;/^$/d' /etc/vsftpd/vsftpd.conf

禁止本地登录
sed -i '/local_enable/c\local_enable=NO' /etc/vsftpd/vsftpd.conf

允许匿名操作
sed -i '$a anon_upload_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_mkdir_write_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_other_write_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_max_rate=512000' /etc/vsftpd/vsftpd.conf

启动服务
service vsftpd restart

数据操作

测试验证
chmod 777 /var/ftp/pub
cp /etc/hosts /var/ftp/pub

测试下载
cd /tmp
lftp $ipaddr <<end
cd pub
get hosts
exit
end

检查下载后的文件
ls /tmp/hosts
	注意:文件存在则匿名用户下载成功
	

测试上传、创建目录、删除目录等
cd /tmp
lftp $ipaddr << end
cd pub
mkdir test1
mkdir test2
put /etc/group
rmdir test2
exit
end

测试效果
tree /var/ftp/pub/ 
	注意:文件存在则匿名用户操作成功

简单实践

脚本实践

查看脚本内容
[root@localhost ~]# cat vsftpd_install_manager.sh
#!/bin/bash
# 功能:定制vsftpd环境部署功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
net_card="eth0"
vsftpd_conf='/etc/vsftpd/vsftpd.conf'

# 定制命令变量
ipaddr=$(ifconfig ${net_card} | sed -n '2p' | sed -e 's/.*inet \(.*\) net.*/\1/g')
iptail=$(echo $ipaddr|cut -d'.' -f4)

# 定制目标类型变量
target_type=(部署 下载 操作)

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台登录界面---------------"
    echo -e " 1: 部署软件 2: 下载测试  3: 操作测试"
    echo -e "-------------------------------------------\033[0m"
}

# 设定主机名
hostname_set(){
    hostname server$iptail.localhost.com
    echo "HOSTNAME=server$iptail.localhost.com" >> /etc/sysconfig/network
    echo "$ipaddr server$iptail.localhost.com" >> /etc/hosts
}

# 软件安装
softs_install(){
    read -p "请输入需要安装的软件,多个用空格隔开:" soft
    yum -y install $soft &>/dev/null
}

# 修改配置
update_config(){
    cp ${vsftpd_conf}{,.default}
    sed -ri '/^#/d;/^$/d' ${vsftpd_conf}
    sed -i '/local_enable/c\local_enable=NO' ${vsftpd_conf}
    sed -i '$a anon_upload_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_mkdir_write_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_other_write_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_max_rate=512000' ${vsftpd_conf}
    service vsftpd restart &>/dev/null && echo "vsftpd服务启动成功"
}

# 定制部署流程
soft_deploy(){
    hostname_set
    softs_install
    update_config
}

# 匿名用户测试下载动作
download_test(){
    # 准备数据目录及配套文件
    chmod 777 /var/ftp/pub
    cp /etc/hosts /var/ftp/pub

    # 测试演示
    cd /tmp
    lftp $ipaddr <<-end
    cd pub
    get hosts
    exit
        end
}

# 匿名用户测试操作权限
operator_test(){
    #测试上传、创建目录、删除目录等
    cd /tmp
    lftp $ipaddr <<-end
    cd pub
    mkdir test1 test2
    put /etc/group
    rmdir test2
    exit
        end
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始登录管理平台..."
        soft_deploy

    elif [ ${target_type[$target_id-1]} == "下载" ];then
        download_test
        # 测试结果
        if [ -f /tmp/hosts ];then
            echo "匿名用户下载成功"
            rm -f /tmp/hosts
        else
            echo "匿名用户下载失败"
        fi
    elif [ ${target_type[$target_id-1]} == "操作" ];then
        operator_test
        if [ -d /var/ftp/pub/test1 ];then
            if [ ! -d /var/ftp/pub/test2 ];then
                if [ -f /var/ftp/pub/group ];then
                    echo "匿名操作权限正常"
                    rm -rf /var/ftp/pub
                fi
            fi
        fi
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash vsftpd_install_manager.sh
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型: 1
开始登录管理平台...
请输入需要安装的软件,多个用空格隔开:vsftpd lftp
vsftpd服务启动成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型: 2
匿名用户下载成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型: 3
匿名操作权限正常
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试
-------------------------------------------
> 请输入要操作的目标类型:

小结


17 awk实践

17.1 基础实践

17.1.1 基础知识

这一节,我们从 基础知识、语法解读、小结 三个方面来学习

基础知识

简介

	在日常计算机管理中,总会有很多数据输出到屏幕或者文件,这些输出包含了标准输出、标准错误输出。默认情况下,这些信息全部输出到默认输出设备---屏幕。然而,大量的数据输出中,只有一小部分是我们需要重点关注的,我们需要把我们需要的或者关注的这些信息过滤或者提取以备后续需要时调用。早先的学习中,我们学过使用grep来过滤这些数据,使用cut、tr命令提出某些字段,但是他们都不具备提取并处理数据的能力,都必须先过滤,再提取转存到变量,然后在通过变量提取去处理,比如:
内存使用率的统计步骤
	1) 通过free -m提取出内存总量,赋值给变量 memory_totle
	2)通过free -m提取出n内存使用量,赋值给变量memory_use
	3)通过数学运算计算内存使用率
	
	需要执行多步才能得到内存使用率,那么有没有一个命令能够集过滤、提取、运算为一体呢?当然,就是今天我要给大家介绍的命令:awk

awk简介

	awk全称 Aho Weinberger Kernighan报告生成器,awk的三个字母是来自于三个作者的首字母。它是一个功能非常强大的文档编辑工具,它不仅能以行为单位还能以列为单位处理文件,并且还具有格式化文本输出功能。目前它受自由软件基金会(FSF)进行开发和维护,通常也称它为 GNU AWK,AWK有多种版本:
    AWK:原先来源于 AT & T 实验室的的AWK
    NAWK:New awk,AT & T 实验室的AWK的升级版
    GAWK:即GNU AWK。所有的GNU/Linux发布版都自带GAWK,它与AWK和NAWK完全兼容	
原理解读
	awk 认为文件中的每一行是一条记录,记录与记录的分隔符为换行符,每一列是一个字段 字段与字段的分隔符默认是一个或多个空格或tab制表符.
	
	awk的工作方式是逐行读取文本数据,将每一行数据视为一条记录(record)每条记录以字段分隔符分成若干字段,然后输出各个字段的值.然后以查找匹配某个特定模式的文本行,并对这些文本执行制定动作。

语法解读

基本格式

格式:
    awk [参数] '[动作]' [文件名]
    awk [参数] –f 动作文件 var=value [文件名]
    awk [参数] 'BEGIN段 [动作] END段' [文件名]
注意:
	动作的格式  '匹配条件{打印动作}'
常见参数:
    -F				指定列的分隔符,默认一行数据的列分隔符是空格
    -f file 		指定读取程序的文件名
    -v var=value	自定义变量
awk程序运行优先级是:
    1 BEGIN: 在开始处理数据流之前执行,可选项
    2 动作: 如何处理数据流,必选项
    3 END: 处理完数据流后执行,可选项
常见动作
    print	显示内容
    $0		显示当前行所有内容
    $n		显示当前行的第n列内容,如果存在多个$n,它们之间使用逗号(,)隔开
注意:
	如果打印的内容是变量,则无需在变量两侧加上双引号,其他的都应该加双引号

其他功能

printf 格式化显示内容
	printf [-v var] format [item1,item2,...]
	注意:
		printf输出需要指定换行符号,format的格式必须与后面item对应
		常见格式:
			%c		显示字符的ASCII码		%d|i 	显示十进制整数		%e|E	显示科学计数法数值
			%f		显示浮点数			 %s		 显示字符串			%u	  显示无符号整数
			%%		显示%本身				
		修饰符:
			%#[.#]	第一个#控制显示宽度,第二个#表示小数点后的精度,例如%3.1f
			%-		左对齐,%-15s
			%+		显示数值的正负符号,%+d
常见内置变量
    FILENAME 	当前输入文件的文件名,该变量是只读的
    NR 			指定显示行的行号
    FNR			多文件时候,分别计数
    NF 			表示字段数量
    OFS 		输出格式的列分隔符,缺省是空格
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,指定输入时的换行符,原换行符($)仍有效
    ORS			输出记录分隔符,输出时用指定符号代替换行符
    ARGC|ARGV[n] 获取命令的参数个数|参数内容

准备工作

[root@localhost ~]# cat awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

17.1.2 基础语法

这一节,我们从 信息查看、定制查看、小结 三个方面来学习

信息查看

字段提取

字段提取:提取一个文本中的一列数据并打印输出,它提供了相关的内置变量。
    $0 表示整行文本
    $1 表示文本行中的第一个数据字段
    $2 表示文本行中的第二个数据字段
    $N 表示文本行中的第N个数据字段
    $NF 表示文本行中的最后一个数据字段
    NR 代表行的行号,在动作外部表示特定行
注意:
   如果打印多列信息,需要使用逗号隔开,否则是内容合并

实践1-打印列信息

打印第1列的内容
[root@localhost ~]# awk '{print $1}' awk.txt
nihao
nihao
nihao

打印第3列内容
[root@localhost ~]# awk '{print $3}' awk.txt
awk2
awk5
awk8

打印最后一列信息
[root@localhost ~]# awk '{print $NF}' awk.txt
awk3
awk6
awk9

打印所有内容
[root@localhost ~]# awk '{print $0}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

实践2-打印多列信息

打印第3,8列内容
[root@localhost ~]# awk '{print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8

打印信息时候,合并信息
[root@localhost ~]# awk '{print $1$3}' awk.txt
nihaoawk2
nihaoawk5
nihaoawk8

使用\t实现内容的分割,需要用""扩住
[root@localhost ~]# awk '{print $1"\t"$3}' awk.txt
nihao   awk2
nihao   awk5
nihao   awk8

打印列外普通信息
[root@localhost ~]# awk '{print "hello awk"}' awk.txt
hello awk
hello awk
hello awk
注意:
	如果没有$n的话,表示 print动作执行的次数与文件行数一致
	
打印fstab的关键信息
[root@localhost ~]# grep "^UUID" /etc/fstab |awk {'print $1,$3'}
UUID=5583bd7c-cc9f-4e19-b453-c224102f3ed5 xfs
UUID=cbd246cd-1df8-4fe7-9040-823cd0978837 xfs

实践3-行号信息输出

打印每列的行号信息
[root@localhost ~]# awk '{print NR,$0}' awk.txt
1 nihao awk1 awk2 awk3
2 nihao awk4 awk5 awk6
3 nihao awk7 awk8 awk9

按照行号打印对应列的内容
[root@localhost ~]# awk '{print NR, $NR}' awk.txt
1 nihao
2 awk4
3 awk8

实践4-打印特定行内容

制定行号打印信息
[root@localhost ~]# awk  'NR==1 {print NR,$1,$3}' awk.txt
1 nihao awk2
[root@localhost ~]# awk  'NR==2 {print NR,$1,$3}' awk.txt
2 nihao awk5

定制查看

简介

	awk默认的信息查看是以空格作为列分隔符的,而对于非空格作为分隔符的内容,我们需要借助于专门的语法实现信息的分割,这里主要用到以下知识:
常见参数:
    -F			指定列的分隔符,默认一行数据的列分隔符是空格
常见内置变量
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,指定输入时的换行符,原换行符($)仍有效
    注意:
    	一般情况下,在输出信息之前进行格式的调整,需要在BEGIN{}部分设定

实践1-简单演示

准备文件内容
[root@localhost ~]# head -n1 /etc/passwd > passwd.txt
[root@localhost ~]# cat passwd.txt
root:x:0:0:root:/root:/bin/bash

使用普通awk命令展示
[root@localhost ~]# awk '{print $1}' passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk '{print $2}' passwd.txt

[root@localhost ~]# awk '{print $0}' passwd.txt
root:x:0:0:root:/root:/bin/bash
结果显示:
	文件中只有一列,无法被awk默认分离

实践2-定制分隔符

设定分隔符实现信息的分隔效果
[root@localhost ~]# awk -F ':' '{print $0}' passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F ':' '{print $1}' passwd.txt
root
[root@localhost ~]# awk -F ':' '{print $1,$2}' passwd.txt
root x
[root@localhost ~]# awk -F ':' '{print $1,$7}' passwd.txt
root /bin/bash

基于-v方式设定输入分隔符的环境变量FS
[root@localhost ~]# awk -v FS=":" '{print $1FS$7}' passwd.txt
root:/bin/bash
虽然-v FS 和 -F 都可以设定入口数据的列分隔符,如果混用的话,-F的优先级高一点
[root@localhost ~]# awk -v FS=":" -F":" '{print $1FS$7}' passwd.txt
root:/bin/bash
[root@localhost ~]# awk -F":" -v FS=":"  '{print $1FS$7}' passwd.txt
root:/bin/bash

实践3-统计案例

获取网址域名信息
[root@localhost ~]# cat domain.txt
http://www.example.org/index.html
http://www.example.org/1.html
http://api.example.org/index.html
http://upload.example.org/index.html
http://img.example.org/3.html
http://search.example.org/2.html

对相关信息进行统计
[root@localhost ~]# awk -F[/]+ '{print $2}' domain.txt  | uniq -c
      2 www.sswang.org
      1 api.sswang.org
      1 upload.sswang.org
      1 img.sswang.org
      1 search.sswang.org
[root@localhost ~]# awk -F[/]+ '{print $(NF-1)}' domain.txt  | uniq -c 
      2 www.sswang.org
      1 api.sswang.org
      1 upload.sswang.org
      1 img.sswang.org
      1 search.sswang.org

17.1.3 显示语法

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk支持格式化输出相关信息。它主要依赖两种方法:
属性方法
    OFS 		输出格式的列分隔符,缺省是空格
    ORS			输出记录分隔符,输出时用指定符号代替换行符
print方法
	printf [-v var] format [item1,item2,...]
	注意:
		printf输出需要指定换行符号,format的格式必须与后面item对应
		常见格式:
			%c		显示字符的ASCII码		%d|i 	显示十进制整数		
			%e|E	显示科学计数法数值	  %u	  显示无符号整数
			%f		显示浮点数			 %s		 显示字符串			
			%%		显示%本身				
		修饰符:
			%#[.#]	第一个#控制显示宽度,第二个#表示小数点后的精度,例如%3.1f
			%-		左对齐,%-15s
			%+		显示数值的正负符号,%+d

简单实践

实践1-列输出分隔符实践

借助于-v方式为命令行输出相关信息
[root@localhost ~]# awk -F ':' -v OFS="~~~" '{print $1,$7}' passwd.txt
root~~~/bin/bash

借助于BEGIN语句设定环境变量
[root@localhost ~]# awk -F":" 'BEGIN{OFS="~"} {print NR,$1,$7}' passwd.txt
1~root~/bin/bash

在BEGIN内部同时实现多个环境变量
[root@localhost ~]# awk 'BEGIN{FS=":";OFS="-"}NR==1{print $1,$3,$NF}' passwd.txt
root-0-/bin/bash

实践2-行输入输出分隔符实践

定制输入分隔符
[root@server12 ~]# seq 7 | awk 'BEGIN{RS=""}{print $1,$2,$3,$4,$5,$6,$7}'
1 2 3 4 5 6 7

借助于-v方式为行分隔符输出相关信息
[root@localhost ~]# awk -F ':' -v ORS="|" '{print $NR,$0}' awk.txt
nihao awk1 awk2 awk3 nihao awk1 awk2 awk3| nihao awk4 awk5 awk6| nihao awk7 awk8 awk9|[root@localhost ~]#

借助于BEGIN语句设定环境变量
[root@localhost ~]# awk -F":" 'BEGIN{ORS="|"} {print NR,$0}' awk.txt
nihao awk1 awk2 awk3 nihao awk1 awk2 awk3|2 nihao awk4 awk5 awk6|3 nihao awk7 awk8 awk9|[root@localhost ~]#

实践3-printf格式化输出实践

使用print的格式信息
[root@localhost ~]# awk '{printf "%s\n",$1}' awk.txt
nihao
nihao
nihao
[root@localhost ~]# awk '{printf "%s",$1}' awk.txt; echo
nihaonihaonihao

多信息格式化嵌套
[root@localhost ~]# awk '{printf "%d--%s--%s\n", NR,$1,$NR}' awk.txt
1--nihao--nihao
2--nihao--awk4
3--nihao--awk8

数字的格式化输出
[root@localhost ~]# awk '{printf "%4.2f--%s\n", NR,$1}' awk.txt
1.00--nihao
2.00--nihao
3.00--nihao

字符的格式化输出
[root@localhost ~]# awk '{printf "%-8s%s\n", NR,$1}' awk.txt
1       nihao
2       nihao
3       nihao

17.1.4 优先级实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk为了体现程序的逻辑顺序,划分了三个代码段,其结构如下:

格式显示:
	BEGIN{}: 读入第一行文本之前执行的语句,一般用来初始化操作
    {}: 逐行处理的执行命令
    END{}: 处理完最后以行文本后执行,一般用来处理输出结果

简单实践

实践1-分结构实践

BEGIN设定数据处理的前置准备
[root@localhost ~]# awk 'BEGIN{OFS=":"} {print NR,$0}' awk.txt
1:nihao awk1 awk2 awk3
2:nihao awk4 awk5 awk6
3:nihao awk7 awk8 awk9

{}定制输出的内容样式
[root@localhost ~]# awk '{ print "第一列:"$1,"第二列:"$2 }' awk.txt
第一列:nihao 第二列:awk1
第一列:nihao 第二列:awk4
第一列:nihao 第二列:awk7

END设定信息处理完毕后的收尾动作
[root@localhost ~]# awk 'END{printf "----------------\n行数总计: %2d\n", NF}' awk.txt
----------------
行数总计:  4

实践2-优先级演示

[root@localhost ~]# awk -F: 'BEGIN{print "begin中的NR值:" NR}  NR==11 {print "命令中的NR值: " NR}END{print "END中的NR值: " NR}' /etc/passwd
begin中的NR值:0
命令中的NR值: 11
END中的NR值: 23
结果可知:
	begin的优先级 > 命令优先级 > END的优先级

实践3-组合演练

BEGIN 和 {} 实现信息的头部格式化
[root@localhost ~]# awk 'BEGIN{print "第一列\t第二列\n----------------"}{print $1"\t"$2}' awk.txt
第一列  第二列
----------------
nihao   awk1
nihao   awk4
nihao   awk7
完全组合实现信息的头部和尾部格式化操作
[root@localhost ~]# awk 'BEGIN{print "第一列\t第二列\n----------------";total=0;}{print $1"\t"$2;total = NR}END{printf "----------------\n行数总计: %2d\n", total}' awk.txt
第一列  第二列
----------------
nihao   awk1
nihao   awk4
nihao   awk7
----------------
行数总计:  3
统计文件格式化
[root@localhost ~]# awk -F":" 'BEGIN{printf "---------------------------\n%-12s|%9s|\n---------------------------\n","用户名","shell类型"}{printf "%-15s|%10s|\n---------------------------\n",$1,$7}END{printf "用户总数总: %2d\n", NR}' passwd.txt
---------------------------
用户名         |  shell类型|
---------------------------
root           | /bin/bash|
---------------------------
用户总数总:  1
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89

格式化显示
[root@localhost ~]# awk 'BEGIN{printf "----------------------\n|%-3s|%2s|%2s|%2s|\n----------------------\n","姓名","语文","数学","历史";} NR>=2 {printf "|%-3s|%4d|%4d|%4d|\n",$1,$2,$3,$4} END{printf "----------------------\n学生 总数总: %2d\n", NR}' course_scores.txt
----------------------
|姓名 |语文|数学|历史|
----------------------
|张三 | 100|  56|  99|
|李四 |  90|  68|  89|
|王五 |  50|  78|  67|
|赵六 |  80|  99|  89|
----------------------
学生总数总:  5

17.1.5 变量实践

这一节,我们从 基础知识、数组实践、小结 三个方面来学习

基础知识

简介

	在shell中,除了通用的字符串操作之外,还具有一些丰富灵活性的小功能,比如变量操作、数组操作类的。awk所支持的变量主要有两种类型:内置变量和自定义变量

变量类型

内置变量
	所谓的内置变量主要就是awk内部已经定制好的变量,我们可以直接拿过来使用,这些常见的方法我们基本上都演示过了,比如:
	FILENAME 	当前输入文件的文件名,该变量是只读的
	FIELDWIDTHS	以空格分隔的数字列表,用空格定义每个数据字段的精确宽度
    NR 			指定显示行的行号
    FNR			多文件时候,分别计数
    NF 			表示字段数量
    OFS 		输出格式的列分隔符,缺省是空格
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,指定输入时的换行符,原换行符($)仍有效
    ORS			输出记录分隔符,输出时用指定符号代替换行符
    ARGC|ARGV[n] 获取命令的参数个数|参数内容
自定义变量
	所谓的自定义变量,主要是根据实际情况,自己定义一些所谓的变量,然后再awk逻辑操作的过程中作为辅助性的措施。自定义变量的定制方法:
	-v var=value
	它可以在 命令行、BEGIN、{}、END 等位置进行使用

简单实践

实践1-内置变量的进阶使用

NF 查看当前所在目录
[root@localhost ~]# echo $PWD | awk -F / '{print $NF}'
root
[root@localhost ~]# cd /etc/sysconfig/
[root@localhost /etc/sysconfig]# echo $PWD | awk -F / '{print $NF}'
sysconfig

查看文件名称
[root@localhost ~]# echo /etc/sysconfig/network-scripts/ifcfg-eth0 | awk -F / '{print $NF}'
ifcfg-eth0

查看文件所在路径
[root@localhost ~]# echo /etc/sysconfig/network-scripts/ifcfg-eth0 | awk -F / '{print $(NF-1)}'
network-scripts

实践2-NR行号的作用

快速获取ip地址
[root@localhost ~]# ifconfig eth0 | awk 'NR==2{print $2}'
10.0.0.12
[root@localhost ~]# ifconfig eth0 | awk '/netmask/{print $2}'
10.0.0.12

快速获取文件行数
[root@localhost ~]# awk -F: 'END{print NR}' /etc/passwd
23
[root@localhost ~]# awk -F: 'END{print NR}' /etc/sysconfig/network-scripts/ifcfg-eth0
20

实践3-FNR 和 FILENAME 获取文件基本信息

获取文件基本信息
[root@localhost ~]# awk '{print FNR,FILENAME,$0}' /etc/issue /etc/redhat-release
1 /etc/issue \S
2 /etc/issue Kernel \r on an \m
3 /etc/issue
1 /etc/redhat-release CentOS Linux release 7.9.2009 (Core)
解析:
	FNR 获取文件内容,同时在前面增加行号
	FILENAME 获取文件名称
	
FIELDWIDTHS:重定义列宽并打印,注意不可以使用$0打印所有,因为$0是打印本行全内容,不会打印你定义的字段
[root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="5 2 8"}NR==1{print $1,$2,$3}' passwd.txt
root: x: 0:0:root

实践4-命令参数

ARGC 获取命令行参数的个数,包括awk命令
[root@localhost ~]# awk  'NR==1 {print ARGC }' awk.txt
2

ARGV:将命令行所有参数放到一个数组中,ARGV[下标] 获取所有参数
[root@localhost ~]# awk  'NR==1 {print ARGV[0] }' awk.txt
awk
[root@localhost ~]# awk  'NR==1 {print ARGV[1] }' awk.txt
awk.txt

实践5-自定义变量

变量实践
[root@localhost ~]# awk -v name='shuji' 'BEGIN{print name}'
shuji
[root@localhost ~]# awk 'BEGIN{name="shuji";print name}'
shuji

定制格式化输出
[root@localhost ~]# awk -F: '{age=36; address="beijing";print $1,age,address}' passwd.txt
root 36 beijing

17.2 进阶知识

17.2.1 赋值运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk本质上属于一种编程语言,所以它具有编程语言的一般功能,表达式、流程控制等基本上都在awk中具有想当程度的使用。这一节我们学习awk进阶到流程控制的一个基础知识点 -- 表达式。

表达式分类

	awk的表达式包括很多种类,常见的表达式有:
	算术操作符:+ - * / ^ %
	赋值操作符:= += -= /= ++ -- %= ^=
	比较操作符:== != > >= < <= 
	模式匹配符:~ 左边是否与右边匹配包含,!~ 是否不匹配
	逻辑操作符:与&&、或||、非!

简单实践

实践1-字符串赋值

[root@localhost ~]# awk -v name='shuji' 'BEGIN{print name}'
shuji

[root@localhost ~]# awk 'BEGIN{school="xigongda";print school}'
xigongda

实践2-数据赋值

命令区域段内进行数据赋值操作
[root@localhost ~]# echo | awk '{i=10;print i+=1}'     
11
[root@localhost ~]# echo | awk '{i=10;print i++,i}'
10 11
[root@localhost ~]# echo | awk '{i=10;print ++i,i}'
11 11
[root@localhost ~]# echo | awk '{i=10;print --i,i}'
9 9
[root@localhost ~]# echo | awk '{i=10;print i--,i}'
10 9

在BEGIN段是可以的,由于END段主要是收尾的信息显示,所以基本不做计算层次的功能
[root@localhost ~]# awk 'BEGIN{i=0;print i++,i}'
0 1
[root@localhost ~]# awk 'BEGIN{i=0;print ++i,i}'
1 1

实践3-变量赋值

-v 设定变量进行赋值操作
[root@localhost ~]# awk -v n=0 'n++' awk.txt
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9
[root@localhost ~]# awk -v n=0 '!n++' awk.txt
nihao awk1 awk2 awk3
结果显示:
	当递增与!同时存在的时候,!优先生效

实践4-数组赋值

在awk中可以设定数组
[root@localhost ~]# awk 'BEGIN{array[0]=100;print array[0]}'
100

17.2.2 数学运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的数学运算,其实就是我们平常所说的二元运算,常见的运算符号有:
		+ - * / ^ %

简单实践

实践1-普通数学运算

[root@localhost ~]# awk 'BEGIN{print 100+3 }'
103
[root@localhost ~]# awk 'BEGIN{print 100-3 }'
97
[root@localhost ~]# awk 'BEGIN{print 100*3 }'
300
[root@localhost ~]# awk 'BEGIN{print 100/3 }'
33.3333
[root@localhost ~]# awk 'BEGIN{print 100**3 }'
1000000
[root@localhost ~]# awk 'BEGIN{print 100%3 }'
1

实践2-BEGIN和变量运算

[root@localhost ~]# awk -v 'count=0' 'BEGIN{count++;print count}'
1
[root@localhost ~]# awk -v 'count=0' 'BEGIN{count--;print count}'
-1

实践3-案例解读

在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
[root@localhost ~]# awk 'BEGIN{printf "---------------------------\n|%-3s|%2s|%2s|%2s|%2s|\n---------------------------\n","姓名","语文","数学","历史","总分";yu=0;shu=0;li=0;total} NR>=2 {yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4; printf "|%-3s|%4d|%4d|%4d|%4d|\n",$1,$2,$3,$4,$2+$3+$4} END{printf "---------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n学生总数总: %2d\n","合计",yu,shu,li,total,NR}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 268|
学生总数总:  4

17.2.3 逻辑运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的逻辑运算,其实指的就是 与或非的操作。基本语法格式如下:
		与&& - 并且关系
        或|| - 或者关系
        非! - 取反关系

简单实践

实践1-基本逻辑运算

与运算:真真为真,真假为假,假假为假
[root@localhost ~]# awk 'BEGIN{print 100>=2 && 100>=3 }'
1
[root@localhost ~]# awk 'BEGIN{print 100>=2 && 1>=100 }'
0

或运算:真真为真,真假为真,假假为假
[root@localhost ~]# awk 'BEGIN{print 100>=2 || 1>=100 }'
1
[root@localhost ~]# awk 'BEGIN{print 100>=200 || 1>=100 }'
0

实践2-文件逻辑运算

[root@localhost ~]# awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
root
python
[root@localhost ~]# awk -F: '$3==0 || $3>=1000 {print $1,$3}' /etc/passwd 
root 0
python 1000
[root@localhost ~]# awk -F: '$3==0 && $3>=1000 {print $1,$3}' /etc/passwd   
[root@localhost ~]# awk -F: '!($3<1000) {print $1,$3}' /etc/passwd           
python 1000
[root@localhost ~]# awk -F: 'NR>=1&&NR<=2{print NR,$0}' /etc/passwd 
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin

实践3-非关系

[root@localhost ~]# awk 'BEGIN{print i}'

[root@localhost ~]# awk 'BEGIN{print !i}'
1
[root@localhost ~]# awk -v i=10 'BEGIN{print !i}'
0
[root@localhost ~]# awk -v i=-3 'BEGIN{print !i}'
0
[root@localhost ~]# awk -v i=0 'BEGIN{print !i}'
1
[root@localhost ~]# awk -v i=abc 'BEGIN{print !i}'
0
[root@localhost ~]# awk -v i='' 'BEGIN{print !i}'
1

17.2.4 匹配运算

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的匹配运算,主要指的是关键字无法精确性的匹配相关信息了,但是我们可以结合一些关键字信息进行模糊的匹配。对于匹配运算来说,它有一些需要注意的事情,具体内容如下:
注意事项:
	如果没有指定,为空模式,匹配每一行
	如果指定”/匹配条件/”,则表示仅处理能够匹配到的内容
	如果指定关系表达式,只有结果为真的情况下,才会被处理
		真:结果为非0值,非空字符串,空格也是真
		假:结果为空字符串或0值,数值不用加””

简单实践

实践1-真假值匹配

假值匹配
[root@localhost ~]# awk '"" {print $1,$3}' awk.txt
[root@localhost ~]# awk '0 {print $1,$3}' awk.txt

非零真值匹配
[root@localhost ~]# awk '"aaa" {print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8
[root@localhost ~]# awk '9 {print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8
[root@localhost ~]# awk -v n=8 'n{print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8

实践2-内容匹配

内容匹配
[root@localhost ~]# awk -F ':' '$1 ~ "^ro" {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F ':' '$1 ~ "ftp" {print $0}' /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
[root@localhost ~]# awk -F ':' '$1 ~ "^[a-d].*" {print $0}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin

内容不匹配
[root@localhost ~]# awk -F ':' '$1 !~ "^ro" {print $0}' /etc/passwd
[root@localhost ~]# awk -F ':' '$1 !~ "^[a-r].*" {print $0}' /etc/passwd
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin

实践3-简单案例

准备不规则格式文件
[root@localhost ~]# cat zhengli.txt
  http://www.sswang.org/index.html
      http://www.sswang.org/1.html
    http://api.sswang.org/index.html
asdfasdfsd
  adfsdf

文件的规整
[root@localhost ~]# awk '/^[ \t]*/{print NR"--->"$1}' zhengli.txt
1--->http://www.sswang.org/index.html
2--->http://www.sswang.org/1.html
3--->http://api.sswang.org/index.html
4--->asdfasdfsd
5--->adfsdf

实践4-扩展实践

多值匹配打印
[root@localhost ~]# awk 'i=1;j=1{print $1,$3}' awk.txt
nihao awk1 awk2 awk3
nihao awk2
nihao awk4 awk5 awk6
nihao awk5
nihao awk7 awk8 awk9
nihao awk8
[root@localhost ~]# awk 'i=1{print $0};j=1{print $1,$3}' awk.txt
nihao awk1 awk2 awk3
nihao awk2
nihao awk4 awk5 awk6
nihao awk5
nihao awk7 awk8 awk9
nihao awk8

假值匹配
[root@localhost ~]# awk '0' awk.txt
真值匹配
[root@localhost ~]# awk '!0' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

非零真值匹配
[root@localhost ~]# awk '0-2' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9
零值匹配
[root@localhost ~]# awk '0-0' awk.txt

非零真值匹配
[root@localhost ~]# awk -v n=0 '++n' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

17.2.5 内置函数1

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

简介

	在awk内部预制了一些函数,借助于这些函数,我们可以实现相关场景的快速操作。这些内置函数的常见类型有:
数值类内置函数
	int(expr)     截断为整数:int(123.45)和int("123abc")都返回123,int("a123")返回0
    sqrt(expr)    返回平方根
    rand()        返回[0,1)之间的随机数,默认使用srand(1)作为种子值
    srand([expr]) 设置rand()种子值,省略参数时将取当前时间的epoch值(精确到秒的epoch)作为种子值
字符串类内置函数
	sprintf(format, expression1, ...):返回格式化后的字符串
	length():返回字符串字符数量、数组元素数量、或数值转换为字符串后的字符数量
	strtonum(str):将字符串转换为十进制数值
        如果str以0开头,则将其识别为8进制
        如果str以0x或0X开头,则将其识别为16进制
	tolower(str):转换为小写
	toupper(str):转换为大写
	index(str,substr):从str中搜索substr(子串),返回搜索到的索引位置,搜索不到则返回0
数据操作内置函数
	substr(string,start[,length]):从string中截取子串
	split(string, array [, fieldsep [, seps ] ]):将字符串分割后保存到数组array中
	match(string,reg[,arr]):使用reg正则规则匹配string信息,默认返回匹配的索引,可以将内容存到数组

简单实践

实践1-数据值函数实践

int数据取整
[root@localhost ~]# awk 'BEGIN{print int(123.45)}'
123
[root@localhost ~]# awk 'BEGIN{print int(123.565)}'
123

sqrt数据求平方根
[root@localhost ~]# awk 'BEGIN{print sqrt(9)}'
3

rand()求0-1的随机数
[root@localhost ~]# awk 'BEGIN{print rand()}'
0.237788
srand() 设定rand()的随机权重,权重固定,rand()值固定
[root@localhost ~]# awk 'BEGIN{srand();print rand()}'
0.858697
[root@localhost ~]# awk 'BEGIN{srand();print rand()}'
0.587366
[root@localhost ~]# awk 'BEGIN{srand(2);print rand()}'
0.610198
[root@localhost ~]# awk 'BEGIN{srand(2);print rand()}'
0.610198

结合srand()获取随机整数
[root@localhost ~]# awk 'BEGIN{srand();print int(22*rand())}'
10
[root@localhost ~]# awk 'BEGIN{srand();print int(22*rand())}'
7

实践2-字符串实践

sprintf设定数据基本样式
[root@localhost ~]# awk 'BEGIN{a=sprintf("%s-%d-%s","abc",23,"ert"); print a}'
abc-23-ert

length获取字符串长度
[root@localhost ~]# awk 'BEGIN{v="nsfadsafdsaf";print length(v)}'
12

tolower转换为小写
[root@localhost ~]# awk 'BEGIN{v="ADMIN";print tolower(v)}'
admin

toupper转换为大写
[root@localhost ~]# awk 'BEGIN{v="nsfad";print toupper(v)}'
NSFAD

index查找子字符串的位置
[root@localhost ~]# awk 'BEGIN{str="nsfad";print index(str, "fa")}'
3
[root@localhost ~]# awk 'BEGIN{str="nsfad";print index(str, "ns")}'
1
[root@localhost ~]# awk 'BEGIN{str="nsfad";print index(str, "ad")}'
4	

实践3-其他操作

substr字符串截取
[root@localhost ~]# awk 'BEGIN{v="abcdefgh";print substr(v,3)}'
cdefgh
[root@localhost ~]# awk 'BEGIN{v="abcdefgh";print substr(v,3,3)}'
cde

split切割字符串
[root@localhost ~]# awk 'BEGIN{split("abc-def-gho-pq",arr,"-",seps); print length(arr), arr[3], seps[1]}'
4 gho -
[root@localhost ~]# awk 'BEGIN{split("abcde",arr,"-");print arr[1]}'
abcde
[root@localhost ~]# echo "12:34:56" | awk '{split($0,a,":");print a[1],a[2],a[3]}'
12 34 56

match字符串匹配
[root@localhost ~]# awk 'BEGIN{str="safdsajfkdsajlfjdsl";print match(str,"j.*s")}'
7
[root@localhost ~]# awk 'BEGIN{str="safdsajfkdsajlfjdsl";match(str,"j.*s",arry);print arry[0]}'
jfkdsajlfjds

小结

17.2.6 内置函数2

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk还内置了一些关于时间和数据类型的内置函数,具体信息如下
时间类内置函数
	mktime("YYYY MM DD HH mm SS [DST]"):构建一个时间,构建失败则返回-1
	systime():返回当前系统时间点,返回的是秒级epoch值
	strftime([format [, timestamp [, utc-flag] ] ]):将时间格式转换为字符串
数据类型相关内置函数:
    isarray(var):测试var是否是数组,返回1(是数组)或0(不是数组)
    typeof(var):返回var的数据类型,有以下可能的值:
        “array”:是一个数组
        “regexp”:是一个真正表达式类型,强正则字面量才算是正则类型,如@/a.*ef/
        “number”:是一个number
        “string”:是一个string
        “strnum”:是一个strnum,参考strnum类型
        “unassigned”:曾引用过,但未赋值,例如”print f;print typeof(f)”
        “untyped”:从未引用过,也从未赋值过

简单实践

实践1-时间函数实践

systime返回当前系统时间点
[root@localhost ~]# awk 'BEGIN{print systime()}'
1655618798

mktime设置一个时间
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 11 32 19")}'
1966822339

strftime获取格式日期
[root@localhost ~]# awk 'BEGIN{print strftime()}'619 14:11:35 CST 2022
结合xargs设定时间格式
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 11 32 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 11:32:19

# 设定2032-04-29 11:00:19基础上减1分钟
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 11 -1 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 10:59:19

# 设定2032-04-29 00:23:19基础上加1小时
[root@localhost ~]# awk 'BEGIN{print mktime("2032 4 29 +1 23 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 01:23:19

# 设定时间格式
[root@localhost ~]# awk 'BEGIN{print strftime("%F %T %z", mktime("2032 4 29 11 32 19"))}'
2032-04-29 11:32:19 +0800

实践2-格式判断实践


isarray判断是否是一个数组
awk 'BEGIN{v="abcdefgh";print isarray(v)}'
awk 'BEGIN{v=("aa" "bb" 11);print isarray(v)}'

awk '
  BEGIN{
    for(idx in PROCINFO){
      if(typeof(PROCINFO[idx]) == "array"){
        continue
      }
      print idx " -> "PROCINFO[idx]
    }
  }'

17.3 逻辑控制

17.3.1 if条件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的条件判断,与我们之前学习的shell条件表达式基本效果一致,所以awk里面仍然会有if条件控制语句,只不过表现样式是shell的if语句的单命令格式而已。在shell中,条件控制主要有两类表现样式:
样式1:普通的非语句
	if(条件){执行语句;...}else {执行语句;...}
	if(条件1){执行语句1}else if(条件2){执行语句2}else{执行语句3}
样式2:三元表达式
	条件表达式?真值表达式:假值表达式
	可以类比为:
        if(条件){
            执行语句
        } else {
            执行语句
        }

简单实践

实践1-单if表达式

[root@localhost ~]# awk -F: '{if($0 ~ "/bin/bash")print$1}' /etc/passwd
root
python
[root@localhost ~]# awk -F: '{if($NF=="/bin/bash")print$1}' /etc/passwd
root
python
[root@localhost ~]# awk  -F' ' '{if(NR>9)print $1}' /etc/fstab
UUID=0f398cc3-6e97-4b25-9666-d848f4dd302b
统计只有四个字母的用户
[root@localhost ~]# awk -F":" 'length($1)==4{i++;print $1} END{print "count is "i}' /etc/passwdroot
sync
halt
mail
dbus
sshd
lisi
count is 7
[root@localhost ~]# awk -F":" '{if(length($1)==4){i++;print $1}} END{print "count is "i}' /etc/passwd
root
sync
halt
mail
dbus
sshd
lisi
count is 7

统计磁盘的使用量
[root@localhost ~]# df -h | awk '{if($(NF-1)>10){print $NF":"$(NF-1)}}'
挂载点:已用%
/:17%
/boot:15%

实践2-双分支if实践

判断内存使用量
[root@localhost ~]# free -m | awk '/^Mem/{if($3 / $2 *100 >= 10){print "内存使用量过高"} else{print "内存使用量正常"}}'
内存使用量正常

[root@localhost ~]# free -m | awk '/^Mem/{if($3 / $2 *100 >= 2){print "内存使用量过高"} else{print "内存使用量正常"}}'
内存使用量过高

实践3-多if表达式

标准格式:
[root@localhost ~]# awk '
  BEGIN{
    score = 999
    if (score >=0 && score < 60) {
      print "不及格"
    } else if (score >= 60 && score < 90) {
      print "优秀"
    } else if (score >= 90 && score <= 100) {
      print "你还是人么"
    } else {
      print "你觉得我信么?"
    }
  }
'
你觉得我信么?

简单格式:
[root@localhost ~]# awk -F: 'BEGIN{i=0;j=0}{if($3<=500){i++}else{j++}}END{print "uid小于500:"i,"uid大于500:"j}' /etc/passwd          
uid小于500:20 uid大于500:3
[root@localhost ~]# echo "nan" | awk '{if ($1=="nan") print "你是男性"; else if ($1=="nv") print "你是男性"; else print "你的性别未知"}'
你是男性

实践4-三元表达式

判断用户类型
[root@localhost ~]# awk -F":" '{$3>=1000?usertype="普通用户":usertype="系统用户";printf "%-6s:%6s\n",$1,usertype}' /etc/passwd | head -n 4
root  :  系统用户
bin   :  系统用户
daemon:  系统用户
adm   :  系统用户

判断磁盘的容量
[root@localhost ~]# df -h | awk '/^\/dev\/sd/{$(NF-1)>10?disk="full":disk="OK";print $1,$(NF-1),disk}'
/dev/sda2 14% full
/dev/sda1 15% full
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
姓名  语文	数据  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89

学生基本信息统计
[root@localhost ~]# awk -v total=0 'NR>=2, $2+$3+$4>=240?type="优秀":type="良好" {total=$2+$3+$4; printf "姓名: %s, 总分: %4d, 状态: %s\n",$1,$2+$3+$4,type}' course_scores.txt
姓名: 张三, 总分:  255, 状态: 优秀
姓名: 李四, 总分:  247, 状态: 优秀
姓名: 王五, 总分:  195, 状态: 良好
姓名: 赵六, 总分:  268, 状态: 优秀

结合BEGIN和END对每个学生的总分进行统计判断后格式化输出
[root@localhost ~]# awk 'BEGIN{printf "--------------------------------\n|%-3s|%2s|%2s|%2s|%2s|%2s|\n--------------------------------\n","姓名","语文","数学","历史","总分","状态";yu=0;shu=0;li=0;total} NR>=2, $2+$3+$4>=240?type="优秀":type="良好" {yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4; printf "|%-3s|%4d|%4d|%4d|%4d|%-2s|\n",$1,$2,$3,$4,$2+$3+$4,type} END{printf "--------------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n学生总数总: %2d\n","合计",yu,shu,li,total,NR}' course_scores.txt
--------------------------------
|姓名 |语文|数学|历史|总分|状态|
--------------------------------
|张三 | 100|  56|  99| 255|优秀|
|李四 |  90|  68|  89| 247|优秀|
|王五 |  50|  78|  67| 195|良好|
|赵六 |  80|  99|  89| 268|优秀|
--------------------------------
|合计 | 320| 301| 344| 268|
学生总数总:  4

17.3.2 switch条件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中有一种简单的条件控制语法switch,相较于if来说,switch分支语句功能较弱,只能进行等值比较或正则匹配,一般结合case方式来使用。
语法格式
    switch (表达式) {
        case 值1|regex1 : 执行语句1;break
        case 值2|regex2 : 执行语句2;break
        case 值3|regex3 : 执行语句3;break
        ...
        [ default: 执行语句 ]
    }
注意:
	表达式的结果符合awk的逻辑运算,表达式成功返回0,表达式失败返回1
	因为switch本身外侧没有流程控制,所以,一般情况下,当外侧没有循环控制的时候,会结合break来使用
	一旦遇到break,代表退出当前循环

简单实践

实践1-简单实践

无break效果
[root@localhost ~]# seq 2 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
    case "0":
        print "偶数: ", $1
    default:
        print "嘿嘿"
}}'
奇数:  1
偶数:  1
嘿嘿
偶数:  2
嘿嘿
结果显示:
	没有break的时候,每一次都会走一个switch的完整循环
结合break的基本语法实践
[root@localhost ~]# seq 5 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
        break
    case "0":
        print "偶数: ", $1
        break
    default:
        print "嘿嘿"
        break
}}'
奇数:  1
偶数:  2
奇数:  3
偶数:  4
奇数:  5

结果显示:
	有了break效果好多了

实践2-结合文本来进行实践

[root@localhost ~]# awk '{switch (NR % 2) {
    case "1":
        print "奇数行: ", $0
        break
    case "0":
        print "偶数行: ", $0
        break
}}' awk.txt
奇数行:  nihao awk1 awk2 awk3
偶数行:  nihao awk4 awk5 awk6
奇数行:  nihao awk7 awk8 awk9
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
姓名  语文	数据  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89
[root@localhost ~]# awk -v total=0 '
BEGIN{
    printf "\t学生成绩信息统计\n"
    printf "-----------------------------------\n"
}
NR>=2 {switch ($2+$3+$4 >= 240) {
    case 0:
        type="优秀";break
    case 1:
        type="良好";break
}
total=$2+$3+$4
printf "姓名: %-3s 总分: %4d,状态: %-2s\n",$1,$2+$3+$4,type
}' course_scores.txt
        学生成绩信息统计
-----------------------------------
姓名: 张三  总分:  255,状态: 良好
姓名: 李四  总分:  247,状态: 良好
姓名: 王五  总分:  195,状态: 优秀
姓名: 赵六  总分:  268,状态: 良好

17.3.3 for循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中,支持一些逻辑循环的功能,比如 for、while等。实际的过程中,我们往往会结合数组元素进行信息的统计
for语句标准格式
	for (三元表达式) {
        执行语句
    }

    for (变量 in 列表) {
        执行语句
    }
    
for语句简写格式
	for(表达式) {执行语句;...}
	
注意:
	for语句可以结合if语句进行操作

简单实践

实践1-for循环语法

普通for循环实践
[root@localhost ~]# for((i=1,sum=0;i<=100;i++));do let sum+=i;done;echo $sum
5050

awk的for标准语法
[root@localhost ~]# awk 'BEGIN {
    sum=0
    for (i=1;i<=100;i++) {
        sum+=i
    }
    print sum
}'
5050

awk for循环单行实践
[root@localhost ~]# awk 'BEGIN{ sum=0; for(i=1;i<=100;i++) {sum+=i}; print sum}'
5050

实践2-文本实践

文本信息的基本统计
[root@localhost ~]# seq 10 | paste -s | tr -s "\t" " " > num.txt
[root@localhost ~]# cat num.txt
1 2 3 4 5 6 7 8 9 10
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){sum+=$i};print sum}' num.txt
55
学生信息统计
[root@localhost ~]# awk 'NR>=2 {
    total=0
    for (i=2;i<=NF;i++) {
        total+=$i
    }
    print "学生姓名: "$1", 课程总分: "total
}' course_scores.txt
学生姓名: 张三, 课程总分: 255
学生姓名: 李四, 课程总分: 247
学生姓名: 王五, 课程总分: 195
学生姓名: 赵六, 课程总分: 268

17.3.4 while循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk支持while相关的循环处理,它主要有两种表现样式
样式1
    while(条件){
        执行语句
    }

样式2
    do {
        执行语句
    } while(条件)
    
简写样式
	while(condition){执行语句;...}
	do {条件;...} while(条件)

简单实践

实践1-while实践

while标准格式
[root@localhost ~]# awk 'BEGIN {
    i=1;sum=0
    while(i<=100) {
        sum+=i
        i++
    }
    print sum
}'
5050

求和1~100
[root@localhost ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print "sum="sum}'
sum=5050

指定数据进行求和
[root@localhost ~]# read -p "请输入一个数字 : " NUM;awk -v num=$NUM 'BEGIN{i=1;sum=0;while(i<=num){sum+=i;i++};print "1~$NUM的和为="sum}'
请输入一个数字 :34
1~$NUM的和为=595

最大值和最小值
[root@localhost ~]# echo '0 234 252 3246 2245 2345 4536 3754 32 345 323 234 3 1' > num.txt
[root@localhost ~]# awk '{min=$1;max=$1;while(i<=NF){if(max<$i)max=$i;if(min>$i)min=$i;i++};print "max:"max,"min:"min}' num.txt
max:4536 min:0

标准求和
[root@localhost ~]# awk '{
sum=0
i=1
while (i<=NF) {
   sum+=$i
   i++
}
print sum
}'  num.txt
17550

实践2-dowhile实践

基本格式
[root@localhost ~]# awk 'BEGIN{i=0;do {print i;i++} while(i<5)}'
0
1
2
3
4

求和1~100
[root@localhost ~]# awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print total}'
5050

17.3.5 流程控制

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk提供了很多的功能实践,尤其是与逻辑控制相关的,其实awk为了更好的进行这些流程的控制,它也提供了很多的控制语法,这些语法如下:
	continue 	中断本次循环
	break 		中断整个循环
	next 		可以提前结束对匹配行处理而直接进入下一行处理
	nextfile 	进阶版的next,可以提前结束对匹配行处理,直接读取下一个文件进行循环处理
	exit 		退出awk程序
		END代码段属于exit一部分,可以在BEGIN或main段中执行exit操作--执行END语句块。

简单实践

实践1-continue实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5)continue
    print(i)
  }
}'
0
1
2
3
4
6
7
8
9
continue 求奇|偶数和
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2!=0)continue;sum+=i}print sum}'
2550

实践2-break实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5){
      break
    }
    print(i)
  }
}'
0
1
2
3
4

循环求和,当加值为66的时候,停止运算
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)break;sum+=i}print sum}'
2145

实践3-next实践

查看文件内容
[root@localhost ~]# cat awk.txt -n
     1  nihao awk1 awk2 awk3
     2  nihao awk4 awk5 awk6
     3  nihao awk7 awk8 awk9
     
输出指定行外的其他行   
[root@localhost ~]# awk 'NR==3{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
[root@localhost ~]# awk 'NR==2{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk7 awk8 awk9

实践4-nextfile实践

nextfile代表在
[root@localhost ~]# awk 'FNR==3{nextfile}{print}' awk.txt awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6

实践5-exit实践

处理一个文件就退出awk程序
[root@localhost ~]# awk '{print $2}' awk.txt
awk1
awk4
awk7
[root@localhost ~]# awk '{print $2; exit}' awk.txt
awk1

只处理三行匹配的内容,后续结束
[root@localhost ~]# awk '/nologin/{i++;if(i<=3){print $0}else{exit;}}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
多个阶段实践exit
[root@localhost ~]# awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
1
[root@localhost ~]# awk 'BEGIN{exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
2
[root@localhost ~]# echo | awk 'BEGIN{}{exit 111}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
111
[root@localhost ~]# echo | awk 'BEGIN{}{}END{if(flag){exit}}'
[root@localhost ~]# echo $?
0

小结


17.3.6 数组实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在使用awk的时候,其实在一些业务的数据分析场景下,使用频率最多的应该是数组,而且数据往往与前面学习的逻辑流程控制组合在一起使用,数组一般很少单独使用。
	其基本语法格式如下:
定义数组:
	array[index表达式]
	index表达式:
        可使用任意字符串;字符串要使用双引号括起来
        如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为空串
        若要判断数组中是否存在某元素,要使用index in array格式进行遍历

遍历数组中的元素,要使用for循环
	for(var in array){for-body}
	注意:
		var会遍历array的每个索引,var不能用关键字index

简单实践

实践1-数组简单实践

数组的简单定义和调用
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;print array["yuwen"],array["shuxue"],array["lishi"]}'
78 89 99

数组的遍历操作
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;for(i in array){print array[i]}}'
78
89
99

内容自动遍历
[root@localhost ~]# cat passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){arrar[i]=i} print $7}' passwd.txt
/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){array[i]=$i};for(j in array){print array[j]}}' passwd.txt
0
root
/root
root
x
0

实践2-数组数据去重

文件内容准备
[root@localhost ~]# echo -e "a\nb\na\ncc" > array.txt
[root@localhost ~]# cat array.txt
a
b
a
cc

数组去重的逻辑
[root@localhost ~]# awk 'array[$0]++' array.txt
a
[root@localhost ~]# awk '!array[$0]++' array.txt
a
b
cc
语法解读
	array[$0]++  第一次执行array[$0],由于是array[a]不为空,所以为真,然后array[a]"++"计数后变成1,并打印数组元素a
	!array[$0]++ 第一次效果与上面一样,执行到第三行的时候,因为存在array[a]已存在,为真,然后!array[a]为假,不再输出打印当前内容,从而达到去重的效果

实践3-统计计数

统计计数
[root@localhost ~]# echo "a.b.c,c.d" |awk -F'[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1

17.3.7 自定义函数

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

需求

	虽然awk提供了内置的函数来实现相应的内置函数,但是有些功能场景,还是需要我们自己来设定,这就用到了awk的自定义函数功能了。
	awk的函数目的,与shell的函数目的一致,都是提高代码的复用能力和功能灵活性

函数

语法格式
    function 函数名(参数1, 参数2, ...)
    {
        函数体代码
    }
注意:
	函数名不能用awk的关键字信息

简单实践

实践1-简单函数实践

[root@localhost ~]# awk '
function add_func(num1, num2)
{
  return num1 + num2
}
function sub_func(num1, num2)
{
  if (num1 > num2)
    return num1 - num2
  return num2 - num1
}
BEGIN {
  sum_result=add_func(10, 20)
  print "两值之和为: "sum_result
  sub_result=sub_func(10, 20)
  print "两值之差为: "sub_result
}'
两值之和为: 30
两值之差为: 10

实践2-数据统计计算

在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
[root@localhost ~]# awk '
function head_func() {
  printf "---------------------------\n|%-3s|%2s|%2s|%2s|%2s|\n---------------------------\n","姓名","语文","数学","历史","总分"
}
function body_func(arg1, arg2, arg3, arg4,arg5){
  printf "|%-3s|%4d|%4d|%4d|%4d|\n",arg1,arg2,arg3,arg4,arg5
}
function tail_func(arg1, arg2, arg3, arg4,arg5){
  printf "---------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n---------------------------\n学生总数总: %2d\n","合计",arg1,arg2,arg3,arg4,arg5
}
BEGIN {
  head_func()
  yu=0;shu=0;li=0;total
}{
  yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4
  body_func($1,$2,$3,$4,$2+$3+$4)
}END{
  tail_func(yu,shu,li,total,NR)
}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 268|
---------------------------
学生总数总:  4

小结


17.3.8 综合实践

这一节,我们从 网络实践、文件实践、小结 三个方面来学习

网络实践

简介

	所谓的网络实践,主要是借助于awk的数组功能,进行站点的信息统计操作。

准备网络环境

安装软件
yum install nignx -y

重启nginx
[root@localhost ~]# systemctl restart nginx.service

重置网站首页
[root@localhost /etc/nginx]# echo 'hello nginx' > /usr/share/nginx/html/index.html
[root@localhost /etc/nginx]# curl localhost
hello nginx
[root@localhost /etc/nginx]# curl localhost/nihao -I -s | head -1
HTTP/1.1 404 Not Found

模拟外网访问
[root@localhost ~]# curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: 2.2.2.2" | head -1
HTTP/1.1 200 OK
[root@localhost ~]# tail -n1 /var/log/nginx/access.log
10.0.0.12 - - [19/Jun/2022:18:04:20 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "2.2.2.2"

准备ip地址文件
[root@localhost ~]# cat ip.txt
112.64.233.130
114.101.40.170
123.15.24.200
125.46.0.62
223.243.252.155
122.228.19.92
218.2.226.42
124.205.143.213
218.60.8.99
125.123.120.130
123.139.56.238
218.60.8.83
222.240.184.126
222.90.110.194
1.196.160.46
222.217.125.153
163.125.156.249
27.50.142.132
61.145.182.27
222.249.238.138
218.64.69.79
103.10.86.203
14.155.112.17
27.191.234.69
60.211.218.78
124.237.83.14
59.44.247.194
114.249.119.45
125.123.65.177
14.115.106.222
准备站点访问测试脚本
[root@localhost /etc/nginx]# cat curl_web_site.sh
#!/bin/bash
# 功能:模拟外网访问网站
while true
do
  cat ip.txt | while read ip
  do
   NUM=$(echo $ip | cut -d"." -f 4)
   for i in $(seq $NUM)
   do
     curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: $ip" >> /dev/null
     curl http://10.0.0.12/$NUM/ -s >> /dev/null
   done
   sleep 1
  done
done
脚本测试效果
[root@localhost ~]# /bin/bash curl_web_site.sh
...

实践1-基本信息统计

查看当前系统的链接状态数量
[root@localhost ~]# ss  -ant
State       Recv-Q Send-Q  Local Address:Port   Peer Address:Port
LISTEN      0      128                 *:22                *:*
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60856
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60857
ESTAB       0      64          10.0.0.12:22         10.0.0.1:64059
ESTAB       0      0           10.0.0.12:22         10.0.0.1:64061
LISTEN      0      32               [::]:21             [::]:*
LISTEN      0      128              [::]:22             [::]:*
统计当前主机的连接状态信息
[root@localhost ~]# ss -tan|awk '!/State/{state[$1]++}END{for(i in state){print i,state[i]}}'
LISTEN 5
ESTAB 4
TIME-WAIT 3960
发现异常ip地址,进行杜绝恶意ip地址访问
[root@localhost ~]# ss -nt | awk -F'[ :]+' '!/State/{ip[$(NF-2)]++}END{for(i in ip){print i,ip[i]}}' | while read line; do ip=$(echo $line | awk '{if($2>1)print $1}');[ -z "$ip" ] || echo "iptables -A INPUT -s $ip -j REJECT"; done
iptables -A INPUT -s 10.0.0.1 -j REJECT
注意:
	这里为了演示成功,故意将恶意ip的频率降低了
	如果不小心真的添加了防火墙策略,则执行下面的命令实现功能恢复
	iptables -vnL INPUT
	iptables -D INPUT 1

实践2-web访问信息统计

获取客户端ip地址信息
[root@localhost ~]# awk -F '"' 'NR==403 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170
统计访问网站的地址信息
[root@localhost ~]# awk -F '"' '{ip[$(NF-1)]++}END{for(i in ip){print i,ip[i]}}' /var/log/nginx/access.log
60.211.218.78 624
222.217.125.153 1377
124.205.143.213 1917
14.115.106.222 1776
14.155.112.17 153
...
统计站点的访问页面信息
[root@localhost ~]# awk '{a[$7]++}END{for(v in a)print v,a[v]|"sort -k1 -nr|head -n10"}' /var/log/nginx/access.log
/nihao 3
/img/html-background.png 1
/img/header-background.png 1
/img/centos-logo.png 1
/favicon.ico 1
/99/ 396
/92/ 368
/83/ 332
/79/ 316
/78/ 312

实践3-脚本信息统计

查看脚本内容
[root@localhost ~]# cat net.sh
#!/bin/bash
# 功能: 脚本统计主机网络信息

# TCP连接数量
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
# UDP连接数量
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
# Listen监听状态的TCP端口数量
Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print count}')
# ESTABLlSHED状态的TCP连接数量
Estab_Total=$(ss -antpH | awk 'BEGIN{count=0}/^ESTAB/{count++}END{print count}')
# TIME-WAIT状态的TCP连接数量
TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0}/^TIME-WAIT/{count++}END{print count}')

#显示主机连接相关信息
echo "TCP连接总数:$TCP_Total"
echo "UDP连接总数:$UDP_Total"
echo "LISTEN状态的TCP端口数量:$Listen_Toatl"
echo "ESTAB状态的TCP连接数量:$Estab_Toatl"
echo "TIME-WAIT状态的TCP连接数量:$TIME_WAIT_Total"

文件实践

简介

	所谓的文件实践,主要是借助于awk的数组功能,实现文件的合并格式化等工作.

查看日志的样式

默认日志格式
	10.0.0.12 - - [19/Jun/2022:18:13:51 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "114.101.40.170"
	
期望统计信息
--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|

准备工作

获取ip地址
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170

获取访问页面
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-13)}' /var/log/nginx/access.log
/170/

输出统计信息

[root@localhost ~]# awk -F '("| )' '
  BEGIN{
    printf "--------------------------------------------\n|%-14s|%-4s|%-4s|%-4s|\n--------------------------------------------\n","     ip地址","访问次数","访问url","访问次数"
  }
  {a[$(NF-1)][$(NF-13)]++}
  END{
    # 遍历数组,统计每个ip的访问总数
    for(ip in a){
      for(uri in a[ip]){
        b[ip] += a[ip][uri]
      }
    }
    # 再次遍历
    for(ip in a){
      for(uri in a[ip]){
        printf "|%16s|%8d|%7s|%8d|\n", ip, b[ip], uri, a[ip][uri]
      }
    }
    printf "--------------------------------------------\n"
  }
' /var/log/nginx/access.log

--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|
| 124.205.143.213|    3408|      /|    3408|
|  14.115.106.222|    3330|      /|    3330|
|   14.155.112.17|     272|      /|     272|
--------------------------------------------

18 变量进阶

18.1 变量实践

18.1.1 高级赋值

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的高级赋值,是另外的一种变量值获取方法,这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分的内容主要还是在字符串的基础上,如何更精细的获取特定的信息内容:主要涉及到的内容样式如下:
字符串截取按分隔符截取: # 右  % 左
    ${file#/}	   	删除匹配结果,保留第一个/右边的字符串
    ${file##/}		删除匹配结果,保留最后一个/右边的字符串
    ${file%/}		删除匹配结果,保留第一个/左边的字符串
    ${file%%/}		删除匹配结果,保留最后一个/左边的字符串
    注意:
        匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
    ${file/dir/path}	把第一个dir替换成path:/path1/dir2/dir3/n
    ${file//dir/path}	把所有dir替换成path:/path1/path2/path3/n
    ${file/#dir/path} 	将从左侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}${file/%dir/path} 	将从右侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
    注意:
		如果匹配内容使用的是正则符号,应该注意正则符号的写法
字符串转换
    ${file^^}		把file中的所有小写字母转换为大写
    ${file,,}		把file中的所有大写字母转换为小写

简单实践

实践1-字符串截取

字符串截取示例
[root@localhost ~]# string=abc12342341
[root@localhost ~]# echo ${string#a*3}
42341
[root@localhost ~]# echo ${string#c*3}
abc12342341
[root@localhost ~]# echo ${string#*c1*3}
42341
[root@localhost ~]# echo ${string##a*3}
41
[root@localhost ~]# echo ${string%3*1}
abc12342
[root@localhost ~]# echo ${string%%3*1}
abc12
字符串截取赋值
[root@localhost ~]# file=/var/log/nginx/access.log
[root@localhost ~]# filename=${file##*/}
[root@localhost ~]# echo $filename
access.log
[root@localhost ~]# filedir=${file%/*}
[root@localhost ~]# echo $filedir
/var/log/nginx

实践2-字符串替换

字符串替换示例
[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# echo ${str/apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str//apple/APPLE}
APPLE, tree, APPLE tree, APPLE
[root@localhost ~]# echo ${str/#apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str/%apple/APPLE}
apple, tree, apple tree, APPLE

使用正则的情况下,代表尽可能多的匹配
[root@localhost ~]# file=dir1@dir2@dir3@n.txt
[root@localhost ~]# echo ${file/#d*r/DIR}
DIR3@n.txt
[root@localhost ~]# echo ${file/%3*/DIR}
dir1@dir2@dirDIR

实践3-字符串转换

[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# upper_str=${str^^}
[root@localhost ~]# echo ${upper_str}
APPLE, TREE, APPLE TREE, APPLE
[root@localhost ~]# lower_str=${upper_str,,}
[root@localhost ~]# echo ${lower_str}
apple, tree, apple tree, apple

18.1.2 嵌套变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

场景现象

场景1:我们知道,命令变量的的表现样式:
	ver=$(命令)
	-- 执行原理是,当`` 或者 $() 范围中存在能够正常解析的命令的话,会先执行命令,然后将命令执行的结果交个一个变量名。
场景2:它还有另外一种样式 -- 普通变量的第三种样式双引号 
	ming=shuji; name="wang-$ming"
	-- 解析原理:双引号会首先查看变量值范围内是否有可以解析的变量名,如果有的话,将解析后的结果放到变量值范围内,组合成一个新的变量值,然后交给变量名。
	上面的两种场景的特点就在于,一个命令行中,借助于$() 或者 "" 发起一次隐藏的命令执行,但是有些场景下,表面上的一个命令需要发起更深一层的命令执行才可以实现指定的功能。在这种场景下,无论是$() 还是 ""都无法满足要求了

场景示例

循环遍历演示
[root@localhost ~]# for i in {1..10}; do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读:这里出现一层隐藏命令执行
	1 {1..10} 会自动进行命令解析
		[root@localhost ~]# echo {1..10}
  	  	1 2 3 4 5 6 7 8 9 10
  	然后执行for命令
  		for i in 1 2 3 4 5 6 7 8 9 10
双层隐藏命令解读
[root@localhost ~]# n=10
[root@localhost ~]# for i in {1..$n}; do  echo "$i "; done
{1..10}

示例解读:
	在for语句中,其实我们的目的与上面的演示一样,但是区别在于这里有两层隐藏的命令执行
	1 $n 需要解析成 10
	2 {1..10} 需要解析成 1 2 3 4 5 6 7 8 9 10
	最后执行 for命令 for 1 2 3 4 5 6 7 8 9 10
问题:
	linux命令行,默认情况下是无法执行两层隐藏命令的执行的,在有些场景中,我们可以通过$() 来实现多层命令的解读,示例如下:
	[root@localhost ~]# cmd=who
    [root@localhost ~]# echo $(${cmd}ami)
    root
	但是,这里我们无法实现,因为 {1..10} 不是命令。
	[root@localhost ~]# for i in $({1..$n}); do  echo "$i "; done
	-bash: {1..10}: 未找到命令

解决方法

	在shell中,它提供了一个专属的命令,可以实现多层隐藏命令的解析,不仅仅能够解析,还能够将相关环境的属性重现,从而实现多层隐藏命令的顺利执行。这个命令就是 eval。
eval原理
	1 eval命令将会首先扫描命令行整体
	2 发现解析则解析,发现执行则预先执行,实现所有隐藏命令的成功执行
	3 将隐藏命令执行的最终结果进行置换
	4 最后执行命令行表面的命令。

简单实践

实践1-eval简单实践

for循环演示
[root@localhost ~]# n=10
[root@localhost ~]# for i in $(eval echo {1..$n}); do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读
	1 命令改造$(eval echo {1..$n})
		1-1 $n先解析为10,命令替换为 {1..10}
		1-2 通过 eval 带入 echo 命令环境
		1-3 $() 执行 echo {1..10} 输出为 1 2 3 4 5 6 7 8 9 10
	2 整体置换命令结果
		for i in 1 2 3 4 5 6 7 8 9 10

实践2-eval的命令扩展演示

查看文件内容
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world

脚本内容演示
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world
[root@localhost ~]# cmd="cat infile.txt"
[root@localhost ~]# echo $(${cmd})
hello-in-world
[root@localhost ~]# echo ${cmd}				
cat infile.txt

不是我们想要的,我们可以借助于eval 和 $() 方式来实现隐藏命令的解读
[root@localhost ~]# eval ${cmd}
hello-in-world
[root@localhost ~]# echo $(${cmd})
hello-in-world

实践3-eval变量名的预制解析

定制嵌套的环境变量
[root@localhost ~]# str=a
[root@localhost ~]# num=1
[root@localhost ~]# $str$num=hello
-bash: a1=hello: 未找到命令

借助于eval命令来实现
[root@localhost ~]# eval $str$num=hello
[root@localhost ~]# echo $a1
hello

借助于eval实现变量名的嵌套
[root@localhost ~]# eval $str=$a1
[root@localhost ~]# echo $a
hello
解读:
	$str 就是 a,$a1就是hello,
	eval执行的命令就是  a=hello

18.1.3 综合案例

这一节,我们从 免密认证、脚本实践、小结 三个方面来学习

免密认证

案例需求

	A 以主机免密码认证 连接到 远程主机B
我们要做主机间免密码认证需要做三个动作
    1、本机生成密钥对
    2、对端机器使用公钥文件认证
    3、验证

手工演示

本地主机生成秘钥对
[root@localhost ~]# ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Ncra/fPpaVs+M18l9Kn7CQq33zmWQSoJ/ujuugCkNjM root@localhost
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|   .      o   .  |
|  o    . + . . o.|
| E .    S . . +.o|
|. + .  o o o ..o.|
|     .. ..+..o  =|
|      .  .oo+ =%+|
|       o*+ ooBO*O|
+----[SHA256]-----+
将公钥信息传递给远程主机的指定用户
[root@localhost ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
本地主机测试验证效果
[root@localhost ~]# ssh root@10.0.0.12 "ifconfig eth0 | grep netmas"
        inet 10.0.0.12  netmask 255.255.255.0  broadcast 10.0.0.255

简单实践

remotehost_sshkey_auth.sh
#!/bin/bash
# 功能:设置ssh跨主机免密码认证
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
user_dir="/root"
login_uesr='root'
login_pass='123456'

# 定制数组变量
target_type=(部署 免密 退出)

# 定制安装软件的函数
expect_install(){
    yum install expect -y >> /dev/null
    echo "软件安装完毕"
}

# 定制ssh秘钥对的生成
sshkey_create(){
    # 清理历史秘钥
    [ -d ${user_dir}/.ssh ] && rm -rf ${user_dir}/.ssh
    # 生成新的秘钥
    ssh-keygen -t rsa -P "" -f ${user_dir}/.ssh/id_rsa >> /dev/null
    echo "秘钥生成完毕"
}

# 定制expect的认证逻辑
expect_process(){
    # 注意:这里不要乱用$1,可以参考函数和脚本间的数组传参
    command="$@"
    expect -c "
        spawn ${command}
        expect {
            \"*yes/no*\" {send \"yes\r\"; exp_continue}
            \"*password*\" {send \"${login_pass}\r\"; exp_continue}
            \"*Password*\" {send \"${login_pass}\r\";}
        }"
}

# 跨主机密码认证
sshkey_auth(){
    local host_list="$1"
    for i in ${host_list}
    do
        command="/usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub"
        remote="${login_uesr}@$i"
        expect_process ${command} ${remote}
    done
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台操作界面---------------"
    echo -e " 1: 秘钥准备  2: 免密认证  3: 退出操作"
    echo -e "-------------------------------------------\033[0m"
}

# 定制脚本帮助信息
Usage(){
    echo "请输入有效的操作标识!!!"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始部署秘钥环境..."
        expect_install
        sshkey_create
    elif [ ${target_type[$target_id-1]} == "免密" ];then
        read -p "> 请输入免密10.0.0网段主机的范围,示例{12..19}: " num_list
        # eval的隐藏命令解析
        ip_list=$(eval echo 10.0.0.${num_list})
        sshkey_auth ${ip_list}
    elif [ ${target_type[$target_id-1]} == "退出" ];then
        echo "准备退出管理操作界面..."
        exit
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash remotehost_sshkey_auth.sh
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署秘钥环境...
软件安装完毕
秘钥生成完毕
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入免密10.0.0网段主机的范围,示例{12..19}: {12..13}
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.

---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 3
准备退出管理操作界面...

小结


19 awk实践

19.1 逻辑控制

19.1.1 switch条件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中有一种简单的条件控制语法switch,相较于if来说,switch分支语句功能较弱,只能进行等值比较或正则匹配,一般结合case方式来使用。
语法格式
    switch (表达式) {
        case 值1|regex1 : 执行语句1;break
        case 值2|regex2 : 执行语句2;break
        case 值3|regex3 : 执行语句3;break
        ...
        [ default: 执行语句 ]
    }
注意:
	表达式的结果符合awk的逻辑运算,表达式成功返回0,表达式失败返回1
	因为switch本身外侧没有流程控制,所以,一般情况下,当外侧没有循环控制的时候,会结合break来使用
	一旦遇到break,代表退出当前循环

简单实践

实践1-简单实践

无break效果
[root@localhost ~]# seq 2 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
    case "0":
        print "偶数: ", $1
    default:
        print "嘿嘿"
}}'
奇数:  1
偶数:  1
嘿嘿
偶数:  2
嘿嘿
结果显示:
	没有break的时候,每一次都会走一个switch的完整循环
结合break的基本语法实践
[root@localhost ~]# seq 5 | awk '{switch ($1 % 2) {
    case "1":
        print "奇数: ", $1
        break
    case "0":
        print "偶数: ", $1
        break
    default:
        print "嘿嘿"
        break
}}'
奇数:  1
偶数:  2
奇数:  3
偶数:  4
奇数:  5

结果显示:
	有了break效果好多了

实践2-结合文本来进行实践

[root@localhost ~]# awk '{switch (NR % 2) {
    case "1":
        print "奇数行: ", $0
        break
    case "0":
        print "偶数行: ", $0
        break
}}' awk.txt
奇数行:  nihao awk1 awk2 awk3
偶数行:  nihao awk4 awk5 awk6
奇数行:  nihao awk7 awk8 awk9
定制配置考试成果表
[root@localhost ~]# cat course_scores.txt
姓名  语文	数据  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89
[root@localhost ~]# awk -v total=0 '
BEGIN{
    printf "\t学生成绩信息统计\n"
    printf "-----------------------------------\n"
}
NR>=2 {switch ($2+$3+$4 >= 240) {
    case 0:
        type="优秀";break
    case 1:
        type="良好";break
}
total=$2+$3+$4
printf "姓名: %-3s 总分: %4d,状态: %-2s\n",$1,$2+$3+$4,type
}' course_scores.txt
        学生成绩信息统计
-----------------------------------
姓名: 张三  总分:  255,状态: 良好
姓名: 李四  总分:  247,状态: 良好
姓名: 王五  总分:  195,状态: 优秀
姓名: 赵六  总分:  268,状态: 良好

19.1.2 for循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在awk中,支持一些逻辑循环的功能,比如 for、while等。实际的过程中,我们往往会结合数组元素进行信息的统计
for语句标准格式
	for (三元表达式) {
        执行语句
    }

    for (变量 in 列表) {
        执行语句
    }
    
for语句简写格式
	for(表达式) {执行语句;...}
	
注意:
	for语句可以结合if语句进行操作

简单实践

实践1-for循环语法

普通for循环实践
[root@localhost ~]# for((i=1,sum=0;i<=100;i++));do let sum+=i;done;echo $sum
5050

awk的for标准语法
[root@localhost ~]# awk 'BEGIN {
    sum=0
    for (i=1;i<=100;i++) {
        sum+=i
    }
    print sum
}'
5050

awk for循环单行实践
[root@localhost ~]# awk 'BEGIN{ sum=0; for(i=1;i<=100;i++) {sum+=i}; print sum}'
5050

实践2-文本实践

文本信息的基本统计
[root@localhost ~]# seq 10 | paste -s | tr -s "\t" " " > num.txt
[root@localhost ~]# cat num.txt
1 2 3 4 5 6 7 8 9 10
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){sum+=$i};print sum}' num.txt
55
学生信息统计
[root@localhost ~]# awk 'NR>=2 {
    total=0
    for (i=2;i<=NF;i++) {
        total+=$i
    }
    print "学生姓名: "$1", 课程总分: "total
}' course_scores.txt
学生姓名: 张三, 课程总分: 255
学生姓名: 李四, 课程总分: 247
学生姓名: 王五, 课程总分: 195
学生姓名: 赵六, 课程总分: 268

19.1.3 while循环

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk支持while相关的循环处理,它主要有两种表现样式
样式1
    while(条件){
        执行语句
    }

样式2
    do {
        执行语句
    } while(条件)
    
简写样式
	while(condition){执行语句;...}
	do {条件;...} while(条件)

简单实践

实践1-while实践

while标准格式
[root@localhost ~]# awk 'BEGIN {
    i=1;sum=0
    while(i<=100) {
        sum+=i
        i++
    }
    print sum
}'
5050

求和1~100
[root@localhost ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print "sum="sum}'
sum=5050

指定数据进行求和
[root@localhost ~]# read -p "请输入一个数字 : " NUM;awk -v num=$NUM 'BEGIN{i=1;sum=0;while(i<=num){sum+=i;i++};print "1~$NUM的和为="sum}'
请输入一个数字 :34
1~$NUM的和为=595

最大值和最小值
[root@localhost ~]# echo '0 234 252 3246 2245 2345 4536 3754 32 345 323 234 3 1' > num.txt
[root@localhost ~]# awk '{min=$1;max=$1;while(i<=NF){if(max<$i)max=$i;if(min>$i)min=$i;i++};print "max:"max,"min:"min}' num.txt
max:4536 min:0

标准求和
[root@localhost ~]# awk '{
sum=0
i=1
while (i<=NF) {
   sum+=$i
   i++
}
print sum
}'  num.txt
17550

实践2-dowhile实践

基本格式
[root@localhost ~]# awk 'BEGIN{i=0;do {print i;i++} while(i<5)}'
0
1
2
3
4

求和1~100
[root@localhost ~]# awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print total}'
5050

19.1.4 流程控制

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	awk提供了很多的功能实践,尤其是与逻辑控制相关的,其实awk为了更好的进行这些流程的控制,它也提供了很多的控制语法,这些语法如下:
	continue 	中断本次循环
	break 		中断整个循环
	next 		可以提前结束对匹配行处理而直接进入下一行处理
	nextfile 	进阶版的next,可以提前结束对匹配行处理,直接读取下一个文件进行循环处理
	exit 		退出awk程序
		END代码段属于exit一部分,可以在BEGIN或main段中执行exit操作--执行END语句块。

简单实践

实践1-continue实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5)continue
    print(i)
  }
}'
0
1
2
3
4
6
7
8
9
continue 求奇|偶数和
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2!=0)continue;sum+=i}print sum}'
2550

实践2-break实践

continue输出满足条件的数据
[root@localhost ~]# awk '
BEGIN{
  for(i=0;i<10;i++){
    if(i==5){
      break
    }
    print(i)
  }
}'
0
1
2
3
4

循环求和,当加值为66的时候,停止运算
[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)break;sum+=i}print sum}'
2145

实践3-next实践

查看文件内容
[root@localhost ~]# cat awk.txt -n
     1  nihao awk1 awk2 awk3
     2  nihao awk4 awk5 awk6
     3  nihao awk7 awk8 awk9
     
输出指定行外的其他行   
[root@localhost ~]# awk 'NR==3{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
[root@localhost ~]# awk 'NR==2{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk7 awk8 awk9

实践4-nextfile实践

nextfile代表在
[root@localhost ~]# awk 'FNR==3{nextfile}{print}' awk.txt awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6

实践5-exit实践

处理一个文件就退出awk程序
[root@localhost ~]# awk '{print $2}' awk.txt
awk1
awk4
awk7
[root@localhost ~]# awk '{print $2; exit}' awk.txt
awk1

只处理三行匹配的内容,后续结束
[root@localhost ~]# awk '/nologin/{i++;if(i<=3){print $0}else{exit;}}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
多个阶段实践exit
[root@localhost ~]# awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
1
[root@localhost ~]# awk 'BEGIN{exit 2}{}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
2
[root@localhost ~]# echo | awk 'BEGIN{}{exit 111}END{if(flag){exit 1}}'
[root@localhost ~]# echo $?
111
[root@localhost ~]# echo | awk 'BEGIN{}{}END{if(flag){exit}}'
[root@localhost ~]# echo $?
0

小结


19.1.5 数组实践

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	在使用awk的时候,其实在一些业务的数据分析场景下,使用频率最多的应该是数组,而且数据往往与前面学习的逻辑流程控制组合在一起使用,数组一般很少单独使用。
	其基本语法格式如下:
定义数组:
	array[index表达式]
	index表达式:
        可使用任意字符串;字符串要使用双引号括起来
        如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为空串
        若要判断数组中是否存在某元素,要使用index in array格式进行遍历

遍历数组中的元素,要使用for循环
	for(var in array){for-body}
	注意:
		var会遍历array的每个索引,var不能用关键字index

简单实践

实践1-数组简单实践

数组的简单定义和调用
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;print array["yuwen"],array["shuxue"],array["lishi"]}'
78 89 99

数组的遍历操作
[root@localhost ~]# awk 'BEGIN{array["yuwen"]=78;array["shuxue"]=89;array["lishi"]=99;for(i in array){print array[i]}}'
78
89
99

内容自动遍历
[root@localhost ~]# cat passwd.txt
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){arrar[i]=i} print $7}' passwd.txt
/bin/bash
[root@localhost ~]# awk -F: '{for(i=1;i<NF;i++){array[i]=$i};for(j in array){print array[j]}}' passwd.txt
0
root
/root
root
x
0

实践2-数组数据去重

文件内容准备
[root@localhost ~]# echo -e "a\nb\na\ncc" > array.txt
[root@localhost ~]# cat array.txt
a
b
a
cc

数组去重的逻辑
[root@localhost ~]# awk 'array[$0]++' array.txt
a
[root@localhost ~]# awk '!array[$0]++' array.txt
a
b
cc
语法解读
	array[$0]++  第一次执行array[$0],由于是array[a]不为空,所以为真,然后array[a]"++"计数后变成1,并打印数组元素a
	!array[$0]++ 第一次效果与上面一样,执行到第三行的时候,因为存在array[a]已存在,为真,然后!array[a]为假,不再输出打印当前内容,从而达到去重的效果

实践3-统计计数

统计计数
[root@localhost ~]# echo "a.b.c,c.d" |awk -F'[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1

19.1.6 自定义函数

这一节,我们从 基础知识、简单实践、小结 三个方面来学习。

基础知识

需求

	虽然awk提供了内置的函数来实现相应的内置函数,但是有些功能场景,还是需要我们自己来设定,这就用到了awk的自定义函数功能了。
	awk的函数目的,与shell的函数目的一致,都是提高代码的复用能力和功能灵活性

函数

语法格式
    function 函数名(参数1, 参数2, ...)
    {
        函数体代码
    }
注意:
	函数名不能用awk的关键字信息

简单实践

实践1-简单函数实践

[root@localhost ~]# awk '
function add_func(num1, num2)
{
  return num1 + num2
}
function sub_func(num1, num2)
{
  if (num1 > num2)
    return num1 - num2
  return num2 - num1
}
BEGIN {
  sum_result=add_func(10, 20)
  print "两值之和为: "sum_result
  sub_result=sub_func(10, 20)
  print "两值之差为: "sub_result
}'
两值之和为: 30
两值之差为: 10

实践2-数据统计计算

在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
[root@localhost ~]# awk '
function head_func() {
  printf "---------------------------\n|%-3s|%2s|%2s|%2s|%2s|\n---------------------------\n","姓名","语文","数学","历史","总分"
}
function body_func(arg1, arg2, arg3, arg4,arg5){
  printf "|%-3s|%4d|%4d|%4d|%4d|\n",arg1,arg2,arg3,arg4,arg5
}
function tail_func(arg1, arg2, arg3, arg4,arg5){
  printf "---------------------------\n|%-3s|%4d|%4d|%4d|%4d|\n---------------------------\n学生总数总: %2d\n","合计",arg1,arg2,arg3,arg4,arg5
}
BEGIN {
  head_func()
  yu=0;shu=0;li=0;total
}{
  yu=$2+yu;shu=$3+shu;li=$4+li;total=$2+$3+$4
  body_func($1,$2,$3,$4,$2+$3+$4)
}END{
  tail_func(yu,shu,li,total,NR)
}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 268|
---------------------------
学生总数总:  4

小结


19.1.7 综合实践

这一节,我们从 网络实践、文件实践、小结 三个方面来学习

网络实践

简介

	所谓的网络实践,主要是借助于awk的数组功能,进行站点的信息统计操作。

准备网络环境

安装软件
yum install nignx -y

重启nginx
[root@localhost ~]# systemctl restart nginx.service

重置网站首页
[root@localhost /etc/nginx]# echo 'hello nginx' > /usr/share/nginx/html/index.html
[root@localhost /etc/nginx]# curl localhost
hello nginx
[root@localhost /etc/nginx]# curl localhost/nihao -I -s | head -1
HTTP/1.1 404 Not Found

模拟外网访问
[root@localhost ~]# curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: 2.2.2.2" | head -1
HTTP/1.1 200 OK
[root@localhost ~]# tail -n1 /var/log/nginx/access.log
10.0.0.12 - - [19/Jun/2022:18:04:20 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "2.2.2.2"

准备ip地址文件
[root@localhost ~]# cat ip.txt
112.64.233.130
114.101.40.170
123.15.24.200
125.46.0.62
223.243.252.155
122.228.19.92
218.2.226.42
124.205.143.213
218.60.8.99
125.123.120.130
123.139.56.238
218.60.8.83
222.240.184.126
222.90.110.194
1.196.160.46
222.217.125.153
163.125.156.249
27.50.142.132
61.145.182.27
222.249.238.138
218.64.69.79
103.10.86.203
14.155.112.17
27.191.234.69
60.211.218.78
124.237.83.14
59.44.247.194
114.249.119.45
125.123.65.177
14.115.106.222
准备站点访问测试脚本
[root@localhost /etc/nginx]# cat curl_web_site.sh
#!/bin/bash
# 功能:模拟外网访问网站
while true
do
  cat ip.txt | while read ip
  do
   NUM=$(echo $ip | cut -d"." -f 4)
   for i in $(seq $NUM)
   do
     curl http://10.0.0.12/ -s -I -H "X-Forwarded-For: $ip" >> /dev/null
     curl http://10.0.0.12/$NUM/ -s >> /dev/null
   done
   sleep 1
  done
done
脚本测试效果
[root@localhost ~]# /bin/bash curl_web_site.sh
...

实践1-基本信息统计

查看当前系统的链接状态数量
[root@localhost ~]# ss  -ant
State       Recv-Q Send-Q  Local Address:Port   Peer Address:Port
LISTEN      0      128                 *:22                *:*
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60856
ESTAB       0      0           10.0.0.12:22         10.0.0.1:60857
ESTAB       0      64          10.0.0.12:22         10.0.0.1:64059
ESTAB       0      0           10.0.0.12:22         10.0.0.1:64061
LISTEN      0      32               [::]:21             [::]:*
LISTEN      0      128              [::]:22             [::]:*
统计当前主机的连接状态信息
[root@localhost ~]# ss -tan|awk '!/State/{state[$1]++}END{for(i in state){print i,state[i]}}'
LISTEN 5
ESTAB 4
TIME-WAIT 3960
发现异常ip地址,进行杜绝恶意ip地址访问
[root@localhost ~]# ss -nt | awk -F'[ :]+' '!/State/{ip[$(NF-2)]++}END{for(i in ip){print i,ip[i]}}' | while read line; do ip=$(echo $line | awk '{if($2>1)print $1}');[ -z "$ip" ] || echo "iptables -A INPUT -s $ip -j REJECT"; done
iptables -A INPUT -s 10.0.0.1 -j REJECT
注意:
	这里为了演示成功,故意将恶意ip的频率降低了
	如果不小心真的添加了防火墙策略,则执行下面的命令实现功能恢复
	iptables -vnL INPUT
	iptables -D INPUT 1

实践2-web访问信息统计

获取客户端ip地址信息
[root@localhost ~]# awk -F '"' 'NR==403 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170
统计访问网站的地址信息
[root@localhost ~]# awk -F '"' '{ip[$(NF-1)]++}END{for(i in ip){print i,ip[i]}}' /var/log/nginx/access.log
60.211.218.78 624
222.217.125.153 1377
124.205.143.213 1917
14.115.106.222 1776
14.155.112.17 153
...
统计站点的访问页面信息
[root@localhost ~]# awk '{a[$7]++}END{for(v in a)print v,a[v]|"sort -k1 -nr|head -n10"}' /var/log/nginx/access.log
/nihao 3
/img/html-background.png 1
/img/header-background.png 1
/img/centos-logo.png 1
/favicon.ico 1
/99/ 396
/92/ 368
/83/ 332
/79/ 316
/78/ 312

实践3-脚本信息统计

查看脚本内容
[root@localhost ~]# cat net.sh
#!/bin/bash
# 功能: 脚本统计主机网络信息

# TCP连接数量
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
# UDP连接数量
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
# Listen监听状态的TCP端口数量
Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print count}')
# ESTABLlSHED状态的TCP连接数量
Estab_Total=$(ss -antpH | awk 'BEGIN{count=0}/^ESTAB/{count++}END{print count}')
# TIME-WAIT状态的TCP连接数量
TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0}/^TIME-WAIT/{count++}END{print count}')

#显示主机连接相关信息
echo "TCP连接总数:$TCP_Total"
echo "UDP连接总数:$UDP_Total"
echo "LISTEN状态的TCP端口数量:$Listen_Toatl"
echo "ESTAB状态的TCP连接数量:$Estab_Toatl"
echo "TIME-WAIT状态的TCP连接数量:$TIME_WAIT_Total"

文件实践

简介

	所谓的文件实践,主要是借助于awk的数组功能,实现文件的合并格式化等工作.

查看日志的样式

默认日志格式
	10.0.0.12 - - [19/Jun/2022:18:13:51 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "114.101.40.170"
	
期望统计信息
--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|

准备工作

获取ip地址
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170

获取访问页面
[root@localhost ~]# awk -F '("| )' 'NR==404 {print $(NF-13)}' /var/log/nginx/access.log
/170/

输出统计信息

[root@localhost ~]# awk -F '("| )' '
  BEGIN{
    printf "--------------------------------------------\n|%-14s|%-4s|%-4s|%-4s|\n--------------------------------------------\n","     ip地址","访问次数","访问url","访问次数"
  }
  {a[$(NF-1)][$(NF-13)]++}
  END{
    # 遍历数组,统计每个ip的访问总数
    for(ip in a){
      for(uri in a[ip]){
        b[ip] += a[ip][uri]
      }
    }
    # 再次遍历
    for(ip in a){
      for(uri in a[ip]){
        printf "|%16s|%8d|%7s|%8d|\n", ip, b[ip], uri, a[ip][uri]
      }
    }
    printf "--------------------------------------------\n"
  }
' /var/log/nginx/access.log

--------------------------------------------
|     ip地址     |访问次数|访问url|访问次数|
--------------------------------------------
|   60.211.218.78|    1248|      /|    1248|
| 222.217.125.153|    2448|      /|    2448|
| 124.205.143.213|    3408|      /|    3408|
|  14.115.106.222|    3330|      /|    3330|
|   14.155.112.17|     272|      /|     272|
--------------------------------------------

20 变量进阶

20.1 变量实践

20.1.1 高级赋值

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	所谓的高级赋值,是另外的一种变量值获取方法,这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分的内容主要还是在字符串的基础上,如何更精细的获取特定的信息内容:主要涉及到的内容样式如下:
字符串截取按分隔符截取: # 右  % 左
    ${file#/}	   	删除匹配结果,保留第一个/右边的字符串
    ${file##/}		删除匹配结果,保留最后一个/右边的字符串
    ${file%/}		删除匹配结果,保留第一个/左边的字符串
    ${file%%/}		删除匹配结果,保留最后一个/左边的字符串
    注意:
        匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
    ${file/dir/path}	把第一个dir替换成path:/path1/dir2/dir3/n
    ${file//dir/path}	把所有dir替换成path:/path1/path2/path3/n
    ${file/#dir/path} 	将从左侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}${file/%dir/path} 	将从右侧能匹配到的dir,则替换成 path 然后返回;否则直接返回 ${var}。
    注意:
		如果匹配内容使用的是正则符号,应该注意正则符号的写法
字符串转换
    ${file^^}		把file中的所有小写字母转换为大写
    ${file,,}		把file中的所有大写字母转换为小写

简单实践

实践1-字符串截取

字符串截取示例
[root@localhost ~]# string=abc12342341
[root@localhost ~]# echo ${string#a*3}
42341
[root@localhost ~]# echo ${string#c*3}
abc12342341
[root@localhost ~]# echo ${string#*c1*3}
42341
[root@localhost ~]# echo ${string##a*3}
41
[root@localhost ~]# echo ${string%3*1}
abc12342
[root@localhost ~]# echo ${string%%3*1}
abc12
字符串截取赋值
[root@localhost ~]# file=/var/log/nginx/access.log
[root@localhost ~]# filename=${file##*/}
[root@localhost ~]# echo $filename
access.log
[root@localhost ~]# filedir=${file%/*}
[root@localhost ~]# echo $filedir
/var/log/nginx

实践2-字符串替换

字符串替换示例
[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# echo ${str/apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str//apple/APPLE}
APPLE, tree, APPLE tree, APPLE
[root@localhost ~]# echo ${str/#apple/APPLE}
APPLE, tree, apple tree, apple
[root@localhost ~]# echo ${str/%apple/APPLE}
apple, tree, apple tree, APPLE

使用正则的情况下,代表尽可能多的匹配
[root@localhost ~]# file=dir1@dir2@dir3@n.txt
[root@localhost ~]# echo ${file/#d*r/DIR}
DIR3@n.txt
[root@localhost ~]# echo ${file/%3*/DIR}
dir1@dir2@dirDIR

实践3-字符串转换

[root@localhost ~]# str="apple, tree, apple tree, apple"
[root@localhost ~]# upper_str=${str^^}
[root@localhost ~]# echo ${upper_str}
APPLE, TREE, APPLE TREE, APPLE
[root@localhost ~]# lower_str=${upper_str,,}
[root@localhost ~]# echo ${lower_str}
apple, tree, apple tree, apple

20.1.2 嵌套变量

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

场景现象

场景1:我们知道,命令变量的的表现样式:
	ver=$(命令)
	-- 执行原理是,当`` 或者 $() 范围中存在能够正常解析的命令的话,会先执行命令,然后将命令执行的结果交个一个变量名。
场景2:它还有另外一种样式 -- 普通变量的第三种样式双引号 
	ming=shuji; name="wang-$ming"
	-- 解析原理:双引号会首先查看变量值范围内是否有可以解析的变量名,如果有的话,将解析后的结果放到变量值范围内,组合成一个新的变量值,然后交给变量名。
	上面的两种场景的特点就在于,一个命令行中,借助于$() 或者 "" 发起一次隐藏的命令执行,但是有些场景下,表面上的一个命令需要发起更深一层的命令执行才可以实现指定的功能。在这种场景下,无论是$() 还是 ""都无法满足要求了

场景示例

循环遍历演示
[root@localhost ~]# for i in {1..10}; do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读:这里出现一层隐藏命令执行
	1 {1..10} 会自动进行命令解析
		[root@localhost ~]# echo {1..10}
  	  	1 2 3 4 5 6 7 8 9 10
  	然后执行for命令
  		for i in 1 2 3 4 5 6 7 8 9 10
双层隐藏命令解读
[root@localhost ~]# n=10
[root@localhost ~]# for i in {1..$n}; do  echo "$i "; done
{1..10}

示例解读:
	在for语句中,其实我们的目的与上面的演示一样,但是区别在于这里有两层隐藏的命令执行
	1 $n 需要解析成 10
	2 {1..10} 需要解析成 1 2 3 4 5 6 7 8 9 10
	最后执行 for命令 for 1 2 3 4 5 6 7 8 9 10
问题:
	linux命令行,默认情况下是无法执行两层隐藏命令的执行的,在有些场景中,我们可以通过$() 来实现多层命令的解读,示例如下:
	[root@localhost ~]# cmd=who
    [root@localhost ~]# echo $(${cmd}ami)
    root
	但是,这里我们无法实现,因为 {1..10} 不是命令。
	[root@localhost ~]# for i in $({1..$n}); do  echo "$i "; done
	-bash: {1..10}: 未找到命令

解决方法

	在shell中,它提供了一个专属的命令,可以实现多层隐藏命令的解析,不仅仅能够解析,还能够将相关环境的属性重现,从而实现多层隐藏命令的顺利执行。这个命令就是 eval。
eval原理
	1 eval命令将会首先扫描命令行整体
	2 发现解析则解析,发现执行则预先执行,实现所有隐藏命令的成功执行
	3 将隐藏命令执行的最终结果进行置换
	4 最后执行命令行表面的命令。

简单实践

实践1-eval简单实践

for循环演示
[root@localhost ~]# n=10
[root@localhost ~]# for i in $(eval echo {1..$n}); do  echo "$i "; done
1
2
3
4
5
6
7
8
9
10
示例解读
	1 命令改造$(eval echo {1..$n})
		1-1 $n先解析为10,命令替换为 {1..10}
		1-2 通过 eval 带入 echo 命令环境
		1-3 $() 执行 echo {1..10} 输出为 1 2 3 4 5 6 7 8 9 10
	2 整体置换命令结果
		for i in 1 2 3 4 5 6 7 8 9 10

实践2-eval的命令扩展演示

查看文件内容
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world

脚本内容演示
[root@localhost ~]# echo 'hello-in-world' > infile.txt
[root@localhost ~]# cat infile.txt
hello-in-world
[root@localhost ~]# cmd="cat infile.txt"
[root@localhost ~]# echo $(${cmd})
hello-in-world
[root@localhost ~]# echo ${cmd}				
cat infile.txt

不是我们想要的,我们可以借助于eval 和 $() 方式来实现隐藏命令的解读
[root@localhost ~]# eval ${cmd}
hello-in-world
[root@localhost ~]# echo $(${cmd})
hello-in-world

实践3-eval变量名的预制解析

定制嵌套的环境变量
[root@localhost ~]# str=a
[root@localhost ~]# num=1
[root@localhost ~]# $str$num=hello
-bash: a1=hello: 未找到命令

借助于eval命令来实现
[root@localhost ~]# eval $str$num=hello
[root@localhost ~]# echo $a1
hello

借助于eval实现变量名的嵌套
[root@localhost ~]# eval $str=$a1
[root@localhost ~]# echo $a
hello
解读:
	$str 就是 a,$a1就是hello,
	eval执行的命令就是  a=hello

20.1.3 综合案例

这一节,我们从 免密认证、脚本实践、小结 三个方面来学习

免密认证

案例需求

	A 以主机免密码认证 连接到 远程主机B
我们要做主机间免密码认证需要做三个动作
    1、本机生成密钥对
    2、对端机器使用公钥文件认证
    3、验证

手工演示

本地主机生成秘钥对
[root@localhost ~]# ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Ncra/fPpaVs+M18l9Kn7CQq33zmWQSoJ/ujuugCkNjM root@localhost
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|   .      o   .  |
|  o    . + . . o.|
| E .    S . . +.o|
|. + .  o o o ..o.|
|     .. ..+..o  =|
|      .  .oo+ =%+|
|       o*+ ooBO*O|
+----[SHA256]-----+
将公钥信息传递给远程主机的指定用户
[root@localhost ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '10.0.0.12 (10.0.0.12)' can't be established.
ECDSA key fingerprint is SHA256:XUJsgk4cTORxdcswxIKBGFgrrqFQzpHmKnRRV6ABMk4.
ECDSA key fingerprint is MD5:71:74:46:50:3f:40:4e:af:ad:d3:0c:de:2c:fc:30:c0.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.
本地主机测试验证效果
[root@localhost ~]# ssh root@10.0.0.12 "ifconfig eth0 | grep netmas"
        inet 10.0.0.12  netmask 255.255.255.0  broadcast 10.0.0.255

简单实践

remotehost_sshkey_auth.sh
#!/bin/bash
# 功能:设置ssh跨主机免密码认证
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制普通变量
user_dir="/root"
login_uesr='root'
login_pass='123456'

# 定制数组变量
target_type=(部署 免密 退出)

# 定制安装软件的函数
expect_install(){
    yum install expect -y >> /dev/null
    echo "软件安装完毕"
}

# 定制ssh秘钥对的生成
sshkey_create(){
    # 清理历史秘钥
    [ -d ${user_dir}/.ssh ] && rm -rf ${user_dir}/.ssh
    # 生成新的秘钥
    ssh-keygen -t rsa -P "" -f ${user_dir}/.ssh/id_rsa >> /dev/null
    echo "秘钥生成完毕"
}

# 定制expect的认证逻辑
expect_process(){
    # 注意:这里不要乱用$1,可以参考函数和脚本间的数组传参
    command="$@"
    expect -c "
        spawn ${command}
        expect {
            \"*yes/no*\" {send \"yes\r\"; exp_continue}
            \"*password*\" {send \"${login_pass}\r\"; exp_continue}
            \"*Password*\" {send \"${login_pass}\r\";}
        }"
}

# 跨主机密码认证
sshkey_auth(){
    local host_list="$1"
    for i in ${host_list}
    do
        command="/usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub"
        remote="${login_uesr}@$i"
        expect_process ${command} ${remote}
    done
}

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台操作界面---------------"
    echo -e " 1: 秘钥准备  2: 免密认证  3: 退出操作"
    echo -e "-------------------------------------------\033[0m"
}

# 定制脚本帮助信息
Usage(){
    echo "请输入有效的操作标识!!!"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始部署秘钥环境..."
        expect_install
        sshkey_create
    elif [ ${target_type[$target_id-1]} == "免密" ];then
        read -p "> 请输入免密10.0.0网段主机的范围,示例{12..19}: " num_list
        # eval的隐藏命令解析
        ip_list=$(eval echo 10.0.0.${num_list})
        sshkey_auth ${ip_list}
    elif [ ${target_type[$target_id-1]} == "退出" ];then
        echo "准备退出管理操作界面..."
        exit
    else
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash remotehost_sshkey_auth.sh
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署秘钥环境...
软件安装完毕
秘钥生成完毕
---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 2
> 请输入免密10.0.0网段主机的范围,示例{12..19}: {12..13}
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.0.0.12
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.12's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@10.0.0.12'"
and check to make sure that only the key(s) you wanted were added.

---------------管理平台操作界面---------------
 1: 秘钥准备  2: 免密认证  3: 退出操作
-------------------------------------------
> 请输入要操作的目标类型: 3
准备退出管理操作界面...

小结


21 项目发布

21.1 基础知识

21.1.1 项目交付

这一节,我们从 基础知识、代码发布、小结 三个方面来学习

基础知识

简介

	项目交付是一个涉及到多团队共同协作的事情,它包括 产品团队设计产品、研发团队开发产品、测试团队测试代码、运维团队发布代码和维护站点等工作。
项目交付的过程中,每个团队都有自己特有的一些工作特殊
一般情况下,项目交付在软件工程人员人员眼中的基本逻辑

代码发布

简介

	所谓的代码发布,其实就是一句话:将我们的代码放到一台公司的互联网服务器上。那么我们应该怎么来理解这句话呢?我们从三个方面来理解他。
发布什么?
	代码		经过测试,功能完善,没有问题的代码
发布到哪里?
	服务器		所有人都能访问的到的一台服务器(有公网IP)
				idc机房、阿里云、亚马逊、腾讯云、华为云、....
发布的效果?
	web网页	对外展示

发布方式

常见的代码发布方式有两种:手工方式和脚本方式。
样式1:手工发布代码
	慢、干扰因素多、不安全
样式2:脚本发布代码
	快、干扰因素少、安全

21.1.2 发布解读

这一节,我们从 基本流程、流程解读、小结 三个方面来学习

基本流程

简介

	这里面的代码发布过程是侧重于手工级别的代码发布流程,在其他的一些基础设施环境中,虽然有一些区别,但是整个的流程脉络是一样的。
功能解读:
	获取代码 - 为什么? - 代码在远端服务器上
	打包代码 - 为什么? - 代码量太多,不好传输
	传输代码 - 为什么? - 本地主机没有代码
	关闭应用 - 为什么? - 防止用户访问出错
	解压代码 - 为什么? - 拉过来的是压缩包
	更新代码 - 为什么? - 要发布项目
	开启应用 - 为什么? - 刚才关闭了
	检查效果 - 为什么? - 万一失败了呢?
	对外开放 - 为什么? - 发布过程,没有让网上用户看到

流程详解

获取代码

	一般情况下,软件项目的网站代码都存放在一个稳定的代码服务器上,这个网站服务器叫代码仓库。
仓库分类
	公有仓库 - 使用互联网的代码服务器
	私有仓库 - 内部服务器或者公网服务器
	
实现方式
	集中式:svn的几乎所有操作命令,受本地主机和代码仓库的网络连接状态。
	分布式:git的几乎所有操作命令,不受代码仓库的网络连接状态限制。

打包代码

	所谓的打包代码,其实就是将获取的项目源代码进行打包操作
目的:
	减少传输文件数量
	减小传输文件大小
	增强传输速率
		
常见打包方式:
	windows:	zip、rar...
	linux:	   tar、zip...

传输代码

	在一个临时的服务器环境上,进行项目代码的部署测试,如果没有问题的话,可以将该代码传输到生产服务器上.
	常见的传输方式如下:
有网情况下
	多种方式: git、ftp、scp、共享挂载 cp、rsync
没有网情况下
	物理方式: U盘或者硬盘

关闭应用

	发布代码的时候,肯定会对生产服务器上运行的项目产生影响,为了避免对互联网用户产生不必要的干扰,我们会将应用关闭。	
问题1:关闭什么应用
	代码所在的服务用到了什么应用,就关闭什么应用
问题2:应用相互依赖的情况下,如何确定关闭的顺序
	站在用户访问的角度,先关闭离客户近的,后关闭离客户远的。

解压代码

	这一步不是必须的,有些场景下,需要代码压缩包解压,有些不用

更新代码

	为了避免我们在放置代码过程中,对老文件造成影响,所以我们放置代码一般分为两步:备份老文件和放置新文件。
备份老文件:防止更新失败,导致旧有的成功运行的代码被覆盖。
放置新文件:将新代码放置到旧代码的位置

注意:
	两个文件的名称是一样的,只是内容不同
	对整个应用项目来说,两个文件没有区别

开启应用

	在代码放置完毕后,将刚才关闭了应用,开启就可以了
开启的顺序:
	先开启离客户远的,后开启离客户近的

检查效果

	为了防止发布后的效果不成功,我们手工方式或者其他方式检测网站的功能是否可以正常的访问

对外开放

	项目内部人员检查这次发布没有问题后,开放应用的流量入口,让用户使用新版本的服务

21.1.3 技术要点

这一节,我们从 解压缩、传输、备份、小结 三个方面来学习

解压缩

简介

文件的压缩
	压缩格式:tar zcvf 压缩后的文件名  将要压缩的文件
文件的解压
	解压格式:tar xf 压缩后的文件名
查看压缩文件内容
	查看格式:zcat 压缩文件
命令参数详解
    z	指定压缩文件的格式为 tar.gz
    c	表示压缩
    v	显示详细过程
    f	指定压缩文件
    x	解压
    C   制定解压位置

解压缩实践

压缩实践
[root@localhost ~]# mkdir tar_dir
[root@localhost ~]# echo nihao > tar_dir/nihao.txt
[root@localhost ~]# tar -zcvf nihao.tar.gz tar_dir/
tar_dir/
tar_dir/nihao.txt

查看压缩文件
[root@localhost ~]# zcat nihao.tar.gz
tar_dir/0000...6011213 5ustar rootroottar_dir/nihao.txt 00...4253652266013046 0ustar  rootrootnihao

解压文件
[root@localhost ~]# tar xf nihao.tar.gz -C /tmp/
[root@localhost ~]# cat /tmp/tar_dir/nihao.txt
nihao

传输

简介

scp传输工具:
	命令格式:scp  要传输的文件 要放置的位置
	
远程目标样式:
	远端主机文件放置位置的表示形式:
		远程连接的用户@远程主机:远程主机的目录路径
	远端主机文件位置的表示形式:
		远程连接的用户@远程主机:远程主机的文件路径
传输示例:
	将本地文件推送到远程主机
		scp file.tar.gz root@10.0.0.12:/root/
	将远程主机的文件拉取到本地
		scp root@10.0.0.12:/root/file.tar.gz ./

传输文件实践

本地文件传输远程
[root@localhost ~]# scp nihao.tar.gz root@10.0.0.13:/tmp/
Warning: Permanently added '10.0.0.13' (ECDSA) to the list of known hosts.
root@10.0.0.13's password:
nihao.tar.gz          100%  156   122.3KB/s   00:00

远程文件拉到本地
[root@localhost ~]# rm -f nihao.tar.gz
[root@localhost ~]# scp root@10.0.0.13:/tmp/nihao.tar.gz ./
root@10.0.0.13's password:
nihao.tar.gz        100%  156   112.3KB/s   00:00
[root@localhost ~]# zcat nihao.tar.gz
tar_dir/00007...213 5ustar  rootroottar_dir/nihao.txt0...046 0ustar  rootrootnihao

备份

实践

	文件的备份要有一定的标志符号,我们就使用目前通用的时间戳的形式来表示,关于时间戳,我们可以借助于 date命令来进行获取

date命令详解:
    命令格式:date [option]
    常见参数:
        %F		显示当前日期格式,%Y-%m-%d
        %T		显示当前时间格式,%H:%M:%S
备份命令效果格式:
    方式一:复制备份-源文件不动
    	cp nihao nihao-$(date +%Y%m%d%H%M%S)
    方式二:移动备份-源文件没了
   		mv nihao nihao-$(date +%Y%m%d%H%M%S)	

备份实践

复制备份
[root@localhost ~]# cp nihao.tar.gz nihao.tar.gz-$(date +%Y%m%d%H%M%S)
[root@localhost ~]# cp nihao.tar.gz nihao.tar.gz-$(date +%Y%m%d%H%M%S)

移动备份
[root@localhost ~]# mv nihao.tar.gz nihao.tar.gz-$(date +%Y%m%d%H%M%S)
[root@localhost ~]# ls nihao.tar.gz-*
nihao.tar.gz-20420620010300  nihao.tar.gz-20420620010304  nihao.tar.gz-20420620010311

21.2 手工发布

21.2.1 方案解读

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

案例需求

	实现一套业务环境的项目发布流程,基本的网站架构效果如下:
架构解读:
	负载均衡采用Nginx服务,基于请求内容进行后端跳转
	动态应用使用的nginx 或者 uwsgi服务
	后端服务应用有多套编程语言研发的web项目。
		有基于java语言的sprintCloud框架开发的
		有基于python语言的django框架开发的
	我们要发布的是django后端服务代码

方案分析

项目部署分析

分析:
		2、python环境		--->  3、python虚拟环境
		1、django环境部署
			4、django软件安装
			5、项目基本操作
			6、应用基本操作
			7、view和url配置
				8、问题:只有本机能访问
					9、方案代理---- 10、nginx
		11、nginx实现代理
			12、nginx软件安装
			13、nginx基本操作
			14、nginx代理的配置
				15、目录结构
				16、查看配置文件
				17、找到对应的代理配置项
		18、启动django
		29、启动nginx
		20、整个项目调试

环境部署方案

环境部署方案
一、django环境部署
	1.1 python虚拟环境
	1.2 django环境部署
		1.2.1 django软件安装
		1.2.2 项目基本操作
		1.2.3 应用基本操作
		1.2.4 view和url配置
二、nginx代理django
	2.1 nginx软件安装
		2.1.1 nginx软件安装
		2.1.2 nginx基本操作
	2.2 nginx代理配置
		2.2.1 目录结构查看
		2.2.2 配置文件查看
		2.2.3 编辑代理配置项
三、项目调试
	3.1 启动软件
		3.1.1 启动django
		3.1.2 启动nginx
		3.2 整个项目调试
1、施工方案的分析原理:基于需求关键点-查依赖-查流程
2、施工方案编写的流程:基于需求分析的流程,按结点输出方案

21.2.2 环境部署

这一节,我们从 基础环境、代码环境、web环境、小结 三个方面来学习

基础环境

python软件部署

python软件部署
[root@localhost ~]# yum install python3 -y
[root@localhost ~]# python --version
Python 2.7.5
pip环境配置
(venv) [root@localhost ~]# mkdir ~/.pip
(venv) [root@localhost ~]#  cat ~/.pip/pip.conf
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host = pypi.tuna.tsinghua.edu.cn
(venv) [root@localhost ~]# python -m pip install --upgrade pip

python虚拟环境

安装虚拟环境软件
[root@localhost ~]# yum install python-virtualenv -y
准备虚拟环境目录
[root@localhost ~]# mkdir /data/virtual -p
[root@localhost ~]# cd /data/virtual
创建虚拟环境
[root@localhost /data/virtual]# virtualenv -p /usr/bin/python3.6 venv
Running virtualenv with interpreter /usr/bin/python3.6
Using base prefix '/usr'
New python executable in /data/virtual/venv/bin/python3.6
Also creating executable in /data/virtual/venv/bin/python
Installing setuptools, pip, wheel...done.
[root@localhost /data/virtual]# ll
总用量 0
drwxr-xr-x 5 root root 56 74 00:32 venv
[root@localhost /data/virtual]# ls -a
.  ..  venv
[root@localhost /data/virtual]# source venv/bin/activate
(venv) [root@localhost /data/virtual]#
(venv) [root@localhost /data/virtual]# python --version
Python 3.6.8

其他相关命令

退出虚拟环境
(venv) [root@localhost /data/virtual]# deactivate
[root@localhost /data/virtual]#

删除虚拟环境
[root@localhost /data/virtual]# ls
venv
[root@localhost /data/virtual]# rm -rf venv/
[root@localhost /data/virtual]# ls
[root@localhost /data/virtual]#

代码环境

准备sqlite环境

查看默认版本
(venv) [root@localhost /data/softs]# /usr/bin/sqlite3 -version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668
更新sqlite版本为3.9+版本
(venv) [root@localhost ~]# cd /data/softs
(venv) [root@localhost /data/softs]# wget https://www.sqlite.org/2022/sqlite-autoconf-3390000.tar.gz
(venv) [root@localhost /data/softs]# tar xf sqlite-autoconf-3390000.tar.gz
(venv) [root@localhost /data/softs]# cd sqlite-autoconf-3390000
(venv) [root@localhost /data/softs]# ./configure
(venv) [root@localhost /data/softs]# make && make install
(venv) [root@localhost /data/softs/sqlite-autoconf-3390000]# sqlite3 --version
3.39.0 2022-06-25 14:57:57 14e166f40dbfa6e055543f8301525f2ca2e96a02a57269818b9e69e162e98918

echo 'export LD_LIBRARY_PATH="/usr/local/lib"' >> ~/.bashrc
source ~/.bashrc
系统环境变量设置
(venv) [root@localhost ~]# echo 'export LD_LIBRARY_PATH="/usr/local/lib"' >> ~/.bashrc
(venv) [root@localhost ~]# source ~/.bashrc

django环境

进入python虚拟环境部署django环境
[root@localhost ~]# source /data/virtual/venv/bin/activate
(venv) [root@localhost ~]# pip install Django==3.2.0

查看软件安装效果
(venv) [root@localhost ~]# pip list
Package           Version
----------------- -------
asgiref           3.4.1
Django            3.2.0
pip               21.3.1
pytz              2022.1
setuptools        28.8.0
sqlparse          0.4.2
typing_extensions 4.1.1
wheel             0.29.0
创建项目
(venv) [root@localhost ~]# mkdir /data/server -p
(venv) [root@localhost ~]# cd /data/server/
(venv) [root@localhost /data/server]# django-admin startproject web_site
(venv) [root@localhost /data/server]# ls
web_site
(venv) [root@localhost /data/server]# tree web_site/
web_site/
├── manage.py
└── web_site
    ├── asgi.py
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

1 directory, 6 files
创建应用
(venv) [root@localhost /data/server]# cd web_site/
(venv) [root@localhost /data/server/web_site]# python manage.py startapp app1
(venv) [root@localhost /data/server/web_site]# tree
.
├── app1
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── web_site
    ├── asgi.py
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-36.pyc
    │   └── settings.cpython-36.pyc
    ├── settings.py
    ├── urls.py
    └── wsgi.py

4 directories, 15 files

注册应用
(venv) [root@localhost /data/server/web_site]# sed -i "/staticfiles/a\    'app1'," web_site/settings.py

定制访问逻辑

定制访问页面的逻辑
(venv) [root@localhost /data/server/web_site]# cat app1/views.py
from django.shortcuts import render
from django.http import HttpResponse

def hello(resquest):
   return HttpResponse("web_site V1.0\n")
   
定制访问页面的路由
(venv) [root@localhost /data/server/web_site]# sed -i '/t path/a\from app1.views import *' web_site/urls.py
(venv) [root@localhost /data/server/web_site]# sed -i "/admin.site/a\    path('hello/', hello)," web_site/urls.py
启动djang应用
(venv) [root@localhost /data/server/web_site]# python  manage.py runserver>> /dev/null 2>&1 &
[1] 4585

检查效果
(venv) [root@localhost /data/server/web_site]# curl localhost:8000/hello/
web_site V1.0

web环境

ngix环境

安装nginx
[root@localhost ~]# yum install nginx -y

启动nginx
[root@localhost ~]# systemctl start nginx

查看状态
[root@localhost ~]# netstat -tnulp | grep nginx
tcp        0      0 0.0.0.0:80    0.0.0.0:*      LISTEN  4725/nginx: master
tcp6       0      0 :::80         :::*           LISTEN 4725/nginx: master

nginx配置

创建配置文件
[root@localhost ~]# cat /etc/nginx/default.d/django.conf
  location /hello/ {
    proxy_pass http://localhost:8000;
  }

测试配置文件
[root@localhost ~]# /usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

重载nginx服务
[root@localhost ~]# systemctl reload nginx
测试访问效果
[root@localhost ~]# echo 'hello nginx' > /usr/share/nginx/html/index.html
[root@localhost ~]# curl localhost
hello nginx
[root@localhost ~]# curl localhost/hello/
web_site V1.0

定制访问域名
[root@localhost ~]# echo '10.0.0.12 django.superopsmsb.com' >> /etc/hosts
[root@localhost ~]# curl django.superopsmsb.com
hello nginx
[root@localhost ~]# curl django.superopsmsb.com/hello/
web_site V1.0

21.2.3 手工发布

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

	为了合理的演示生产环境的项目代码发布,同时又兼顾实际实验环境的资源,我们这里将 B主机和C主机 用一台VM主机来实现,A主机单独实现。这两台主机的ip地址相关内容如下:
	A主机:10.0.0.12   B主机: 10.0.0.13
	为了体现整个实践操作的标准化,在这里进行所有目录的统一规划:
		代码存储目录: /data/codes/django
		打包文件目录: /data/codes
		脚本相关目录: /data/scripts
		备份文件目录: /data/backup/django
		项目代码目录: /data/server/web_site

简单实践

代码准备-10.0.0.13

准备待发布代码
[root@localhost ~]# mkdir /data/codes
[root@localhost ~]# cd /data/codes/
[root@localhost /data/codes]# mkdir django
[root@localhost /data/codes]# scp root@10.0.0.12:/data/server/web_site/app1/views.py django/
[root@localhost /data/codes]# cat django/views.py
from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
# 定制后端业务逻辑处理函数
def hello(request):
    return HttpResponse("web_site v0.1\n")

代码获取-10.0.0.13

我们借助于sed的方式模拟代码获取修改后的代码
[root@localhost /data/codes]# sed -i 's#0.1#0.2#' django/views.py
[root@localhost /data/codes]# grep web_site django/views.py
    return HttpResponse("web_site v0.2\n")

打包代码-10.0.0.13

打包文件
[root@localhost /data/codes]# tar zcf django.tar.gz django/
[root@localhost /data/codes]# ls
django  django.tar.gz

确认文件信息
[root@localhost /data/codes]# zcat  django.tar.gz
django/0000...026 5ustar  rootrootdjango/views.py000...12531 0ustar  rootrootfrom django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
# 定制后端业务逻辑处理函数
def hello(request):
    return HttpResponse("web_site v0.2\n")

传输代码-10.0.0.12

在制定的目录下获取远程代码目录
[root@localhost ~]# mkdir /data/codes
[root@localhost ~]# cd /data/codes/
[root@localhost ~]# scp root@10.0.0.13:/data/codes/django.tar.gz ./

关闭应用-10.0.0.12

前端准备数据迁移配置
[root@localhost ~]# mkdir /etc/nginx/conf.d
[root@localhost ~]# cat /etc/nginx/conf.d/update.conf
server {
  listen 6666;
  location / {
    index index.html;
    root /usr/share/nginx/update/;
  }
}

准备数据迁移文件
[root@localhost ~]# mkdir /usr/share/nginx/update -p
[root@localhost ~]# echo '数据迁移中,请耐心等待,抱歉!!!' >> /usr/share/nginx/update/index.html
使用数据迁移配置
[root@localhost ~]# cat /etc/nginx/default.d/django.conf
location /hello/ {
  # proxy_pass http://localhost:8000;
  proxy_pass http://10.0.0.12:6666/;
}

重启nginx服务
[root@localhost ~]# /usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost ~]# systemctl restart nginx
检查效果
[root@localhost ~]# netstat -tnulp |grep nginx
tcp        0      0 0.0.0.0:6666   0.0.0.0:*   LISTEN      92562/nginx: master
tcp        0      0 0.0.0.0:80     0.0.0.0:*   LISTEN      92562/nginx: master
tcp6       0      0 :::80          :::*        LISTEN      92562/nginx: master
[root@localhost ~]# curl www.superopsmsb.com/hello/
数据迁移中,请耐心等待,抱歉!!!
关闭后端的django服务
[root@localhost ~]# kill $(lsof -Pti :8000)
[root@localhost ~]# netstat -tnulp | grep 8000
[root@localhost ~]#

解压代码-10.0.0.12

[root@localhost ~]# cd /data/codes
[root@localhost /data/codes]# tar xf django.tar.gz

备份文件-10.0.0.12

备份老文件
[root@localhost /data/codes]# mkdir /data/backup/django -p
[root@localhost /data/codes]# mv /data/server/web_site/app1/views.py /data/backup/django/views.py-$(date +%Y%m%d%H%M%S)
[root@localhost /data/codes]# ls /data/server/web_site/app1/views.*
ls: 无法访问/data/server/web_site/app1/views.*: 没有那个文件或目录

放置新文件
[root@localhost /data/codes]# mv /data/codes/django/views.py /data/server/web_site/app1/
[root@localhost /data/codes]# ls /data/server/web_site/app1/views.*
/data/server/web_site/app1/views.py
[root@localhost /data/codes]# grep web_site /data/server/web_site/app1/views.py
    return HttpResponse("web_site v0.2\n")

开启应用-10.0.0.12

开启后端django服务
[root@localhost /data/codes]# source /data/virtual/venv/bin/activate
(venv) [root@localhost /data/codes]# cd /data/server/web_site/
(venv) [root@localhost /data/server/web_site]# python manage.py runserver >> /dev/null 2>&1 &
[1] 92774
(venv) [root@localhost /data/server/web_site]# deactivate
[root@localhost /data/server/web_site]#

检查效果
[root@localhost ~]# netstat -tnulp | grep 8000
tcp        0      0 127.0.0.1:8000  0.0.0.0:*    LISTEN      92776/python
修改前端nginx服务入口
[root@localhost ~]# cat /etc/nginx/default.d/django.conf
location /hello/ {
  proxy_pass http://localhost:8000;
  # proxy_pass http://10.0.0.12:6666/;
}

重启nginx服务
[root@localhost /data/server/web_site]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost /data/server/web_site]# systemctl restart nginx

内部检查-10.0.0.12

检查效果
[root@localhost /data/server/web_site]# curl www.superopsmsb.com
hello nginx
[root@localhost /data/server/web_site]# curl www.superopsmsb.com/hello/
web_site v0.2

对外开放

接收外部用户流量即可

22 脚本发布

22.1 简单脚本

22.1.1 命令罗列

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

目的:
	实现代码仓库主机上的操作命令功能即可

简单实践

实践

查看脚本内容
#!/bin/bash
# 功能:打包代码
# 版本: v0.1
# 作者: 书记
# 联系: superopsmsb.com

cd /data/codes
[ -f django.tar.gz ] && rm -f django.tar.gz
tar zcf django.tar.gz django	

脚本实践

脚本编写完成后,进行测试:
sed -i 's#1.1#1.2#' /data/server/web_site/views.py
bash /data/scripts/tar_code.sh
查看压缩文件内容
zcat django.tar.gz 

22.1.2 变量转化

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

问题:
	脚本里面的手写的固定的内容太多了,更改时候费劲
	所以通过变量的方式实现信息的固化

简单实践

实践

查看脚本内容
#!/bin/bash
# 功能:打包代码
# 版本: v0.2
# 作者: 书记
# 联系: superopsmsb.com

FILE='django.tar.gz'
CODE_DIR='/data/codes'
CODE_PRO='django'

cd "${CODE_DIR}"
[ -f "${FILE}" ] && rm -f "${FILE}"
tar zcf "${FILE}" "${CODE_PRO}"	

脚本实践

脚本编写完成后,进行测试:
sed -i 's#1.2#1.3#' /data/server/web_site/views.py
bash /data/scripts/tar_code.sh
查看压缩文件内容
zcat django.tar.gz

22.1.3 功能函数

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	三条命令其实是一个组合,实现的是一个功能

简单实践

实践

查看脚本内容
#!/bin/bash
# 功能:打包代码
# 版本: v0.3
# 作者: 书记
# 联系: superopsmsb.com

FILE='django.tar.gz'
CODE_DIR='/data/codes'
CODE_PRO='django'

code_tar(){
	cd "${CODE_DIR}"
	[ -f "${FILE}" ] && rm -f "${FILE}"
	tar zcf "${FILE}" "${CODE_PRO}"	
}
code_tar

脚本实践

脚本编写完成后,进行测试:
sed -i 's#1.3#1.4#' /data/server/web_site/views.py
bash /data/scripts/tar_code.sh
查看压缩文件内容
zcat django.tar.gz

22.1.3 远程执行

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

有时候,我们需要通过远程方式到另外一台主机进行脚本的执行
格式:
	ssh 远程主机登录用户名@远程主机ip地址 "执行命令"

效果

[root@localhost ~]# ssh root@10.0.0.13 "ifconfig eth0"
root@10.0.0.13's password:
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.13  netmask 255.255.255.0  broadcast 10.0.0.255
        ...

简单实践

实践

远程更新文件内容
ssh root@10.0.0.13 "sed -i /'s#1.4#1.5#' /data/server/web_site/views.py"
远程查看脚本
ssh root@10.0.0.13 "ls /data/scripts"
远程执行脚本
ssh root@10.0.0.13 "/bin/bash /data/scripts/tar_code.sh"
远程检查更新效果
ssh root@10.0.0.13 "zcat /data/server/web_site.tar.gz"

22.2 大型脚本

22.2.1 功能框架

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

问题:为什么不按照简单脚本的思路进行编写

为什么?
    1、命令多
    2、功能多
    3、不好组合
解决方案:
    一句话:化整为零,各个击破

脚本框架

编写大型脚本有一个流程:
    一、脚本框架
    二、命令填充
    三、完善功能
        增加日志功能
        增加锁文件功能
        增加主函数逻辑
        增加参数安全措施

需求

完成代码发布流程框架,一个流程(步骤)即一个功能
	- 用函数来实现

简单实践

实践

脚本内容
#!/bin/bash
# 功能:打包代码	
# 版本: v0.1
# 作者: 书记
# 联系: superopsmsb.com

# 获取代码
get_code(){
  echo "获取代码"
}

# 打包代码
tar_code(){
  echo "打包代码"
}

# 传输代码
scp_code(){
  echo "传输代码"
}

# 关闭应用
stop_serv(){
  echo "关闭应用"
  echo "关闭nginx应用"
  echo "关闭django应用"
}

# 解压代码
untar_code(){
  echo "解压代码"
}

# 放置代码
fangzhi_code(){
  echo "放置代码"
  echo "备份老文件"
  echo "放置新文件"
}

# 开启应用
start_serv(){
  echo "开启应用"
  echo "开启django应用"
  echo "开启nginx应用"
}

# 检查
check(){
  echo "检查项目"
}

# 部署函数
deploy_pro(){
  get_code
  tar_code
  scp_code
  stop_serv
  untar_code
  fangzhi_code
  start_serv
  check
}

# 主函数
main(){
  deploy_pro
}

# 执行主函数
main

22.2.2 命令填充

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	在流程跑通的情况下,执行完整的代码部署过程
方案:
	在脚本框架中,填写执行成功的命令

简单实践

实践

2#!/bin/bash
# 功能:打包代码	
# 版本: v0.2
# 作者: 书记
# 联系: superopsmsb.com

# 获取代码
get_code(){
  echo "获取代码"
}

# 打包代码
tar_code(){
  echo "打包代码"
  ssh root@10.0.0.13 "/bin/bash /data/scripts/tar_code.sh"
}

# 传输代码
scp_code(){
  echo "传输代码"
  cd /data/codes
  [ -f django.tar.gz ] && rm -f django.tar.gz
  scp root@10.0.0.13:/data/server/web_site.tar.gz ./
}

# 关闭应用
stop_serv(){
  echo "关闭应用"
  echo "关闭nginx应用"
  /data/server/nginx/sbin/nginx -s stop
  echo "关闭django应用"
  kill $(lsof -Pti :8000)
}

# 解压代码
untar_code(){
  echo "解压代码"
  cd /data/codes
  tar xf django.tar.gz
}

# 放置代码
fangzhi_code(){
  echo "放置代码"
  echo "备份老文件"
  mv /data/server/web_site/app1/views.py /data/backup/views.py-$(date +%Y%m%d%H%M%S)
  echo "放置新文件"
  mv /data/server/web_site/views.py /data/server/web_site/app1/
}

# 开启应用
start_serv(){
  echo "开启应用"
  echo "开启django应用"
  source /data/virtual/venv/bin/activate
  cd /data/server/web_site/
  python manage.py runserver >> /dev/null 2>&1 &
  deactivate
  echo "开启nginx应用"
  /data/server/nginx/sbin/nginx
}
# 检查
check(){
  echo "检查项目"
  netstat -tnulp | grep ':80'
}

...

22.2.3 日志功能

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	1、追踪记录
	2、数据说话
方案:
	增加日志功能
	1、日志文件
		/data/logs/deploy.log
	2、日志格式
		日期	时间	脚本名称	步骤
知识点:
	文件内容追加: >>
	日期:date +%F
	时间:date +%T
	脚本:$0

简单实践

实践

#!/bin/bash
...
LOG_FILE='/data/logs/deploy.log'
# 增加日志功能
write_log(){
  DATE=$(date +%F)
  TIME=$(date +%T)
  buzhou="$1"
  echo "${DATE} ${TIME} $0 : ${buzhou}" >> "${LOG_FILE}"
}

# 获取代码
get_code(){
  ...
  write_log "获取代码"
}

# 打包代码
tar_code(){
  ...
  write_log "打包代码"
}

# 传输代码
scp_code(){
  ...
  write_log "传输代码"
}

# 关闭应用
stop_serv(){
  ...
  write_log "关闭应用"
  ...
  write_log "关闭nginx应用"
  ...
  write_log "关闭django应用"
}

# 解压代码
untar_code(){
  ...
  write_log "解压代码"
}

# 放置代码
fangzhi_code(){
  ...
  write_log "放置代码"
  ...
  write_log "备份老文件"
  ...
  write_log "放置新文件"
}

# 开启应用
start_serv(){
  ...
  write_log "开启应用"
  ...
  write_log "开启django应用"
  ...
  write_log "开启nginx应用"
}
# 检查
check(){
  ...
  write_log "检查项目"
}

...

22.2.4 锁文件

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	同一时间段内,只允许有一个用户来执行这个脚本
	如果脚本执行的时候,有人在执行,那么输出报错:
	脚本 deploy.sh 正在运行,请稍候...
设计:
	1、锁文件	/tmp/deploy.pid
	2、存在锁文件时候,输出报错信息
	3、脚本执行的时候,需要创建锁文件
	4、脚本执行结束的时候,需要删除锁文件
知识点:
    条件和结果: 双分支if语句
    文件表达式: -f  file_name
    验证表达式: [ 表达式 ]
    创建和删除命令:touch、rm -f

简单实践

实践

#!/bin/bash
...
PID_FILE='/tmp/deploy.pid'
...
# 增加锁文件功能
add_lock(){
  echo "增加锁文件"
  touch "${PID_FILE}"
  write_log "增加锁文件"
}

# 删除锁文件功能
del_lock(){
  echo "删除锁文件"
  rm -f "${PID_FILE}"
  write_log "删除锁文件"
}

# 部署函数
deploy_pro(){
  add_lock
  ...
  del_lock
}

# 脚本报错信息
err_msg(){
  echo "脚本 $0 正在运行,请稍候..."
}

# 主函数
main(){
if [ -f "${PID_FILE}" ]
then
      err_msg
else
      deploy_pro
fi
}

# 执行主函数
main

22.2.5 流程控制

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	如果我给脚本输入的参数是deploy,那么脚本才执行,否则的话,提示该脚本的使用帮助信息,然后退出
提示信息:脚本 deploy.sh 的使用方式: deploy.sh [ deploy ]
分析:
    1、脚本传参,就需要在脚本内部进行调用参数
    2、脚本的帮助信息
    3、脚本内容就需要对传参的内容进行判断
知识点:
    1、shell内置变量:$n
    2、帮助信息: 简单函数定义和调用
    3、内容判断: 多if语句或者case语句
方案:
	1、脚本的传参
        脚本执行:bash deploy.sh deploy
        位置参数的调用: $1
	2、脚本的帮助信息
        定义一个usage函数,然后调用。
        提示信息格式:
        脚本 deploy.sh 的使用方式: deploy.sh [ deploy ]
	3、内容判断
        main函数体调用函数传参: $1
        在main函数中,结合case语句,对传入的参数进行匹配
            如果传入参数内容是"deploy",那么就执行代码部署流程
            如果传入参数内容不是"deploy",那么输出脚本的帮助信息
        if语句和case语句的结合
        	case语句在外,if语句在内

简单实践

实践

#!/bin/bash
...

# 脚本帮助信息
usage(){
  echo "脚本 $0 的使用方式: $0 [deploy]"
  exit
}

# 主函数
main(){
  case "$1" in 
    "deploy")
      if [ -f "${PID_FILE}" ]
      then
         err_msg
      else
        deploy_pro
      fi
    ;;
    *)
      usage
    ;;
  esac
}

# 执行主函数
main $1

22.2.6 参数安全

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

需求:
	对脚本传入的参数的数量进行判断,如果参数数量不对,提示脚本的使用方式,然后退出
分析:
    1、脚本参数数量判断
    2、条件判断
        数量对,那么执行主函数
        数量不对,那么调用脚本帮助信息
知识点:
    1、脚本参数数量判断
        shell内置变量: 	$#
        条件表达式:	   [ $# -eq 1 ]
    2、条件判断:
    	双分支if语句    
方案:
	1、双分支if语句 + main函数调用

简单实践

实践

#!/bin/bash
...

# 执行主函数
if [ $# -eq 1 ] 
then
  main $1
else
  usage
fi

22.2.7 脚本调试

这一节,我们从 基础知识、简单实践、小结 三个方面来学习

基础知识

简介

我们介绍脚本调试的时候呢,主要分三种方式来介绍:

    -n	检查脚本中的语法错误
    -v	先显示脚本所有内容,然后执行脚本,结果输出,如果执行遇到错误,将错误输出。
    -x	将执行的每一条命令和执行结果都打印出来

简单实践

实践


22.3 脚本技巧

22.3.1 技巧解读

这一节,我们从 简单脚本、复杂脚本、注意事项、小结 四个方面来学习

简单脚本

简介

	1、手工执行的命令一定要可执行
	2、命令简单罗列
	3、固定的内容变量化
	4、功能函数化

复杂脚本

实践

	1、手工执行的命令一定要可执行
	2、根据发布流程编写脚本的框架
	3、将手工执行的命令填充到对应的框架函数内部
	4、增加日志功能,方便跟踪脚本历史执行记录
	5、主函数中逻辑流程控制好
	6、设计安全的方面:
		增加锁文件,保证代码发布的过程中不受干扰,
		输入参数数量
		输入参数匹配
		脚本帮助信息
	7、调试脚本

注意事项

	1、命令一定要保证能正常执行
	2、成对的符号,要成对写,避免丢失
	3、函数调用,
		写好函数后,一定要在主函数中进行调用
	4、避免符号出现中文
	5、命令变量的写法一定要规范
	6、固定的内容一定要变量实现,方便以后更改
	7、日志的输出
	8、脚本的传参和函数的传参要区别对待
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值