shell

  • ~45.80K 字
  1. 1. Shell编程
    1. 1.1. 一. Shell编程初识
      1. 1.1.1. Shell概览
      2. 1.1.2. Shell的特性及特点
        1. 1.1.2.1. 一、shell? 命令解释器
        2. 1.1.2.2. 二、GNU/bash shell特点
      3. 1.1.3. 脚本编写
      4. 1.1.4. bash脚本调试 [了解]
      5. 1.1.5. 变量☆
        1. 1.1.5.1. 一、shell 变量
        2. 1.1.5.2. 二、变量的类型
          1. 1.1.5.2.1. 1. 自定义变量
          2. 1.1.5.2.2. 2. 环境变量
          3. 1.1.5.2.3. 3. 位置变量
          4. 1.1.5.2.4. 4. 预定义变量
          5. 1.1.5.2.5. 5. 变量”内容”的删除和替换(扩展)
          6. 1.1.5.2.6. 6. i++ 和 ++i (了解)
    2. 1.2. 二. Shell流程控制
      1. 1.2.1. Shell条件测试
        1. 1.2.1.1. 1.文件测试 [ 操作符 文件或目录 ]
        2. 1.2.1.2. 2.数值比较 [ 整数1 操作符 整数2 ]
      2. 1.2.2. Shell条件结构-if判断
        1. 1.2.2.1. 随堂作业
      3. 1.2.3. case匹配模式
      4. 1.2.4. Shell循环结构-for
        1. 1.2.4.1. 随堂练习
      5. 1.2.5. Shell循环结构-while
        1. 1.2.5.1. 随堂练习
      6. 1.2.6. 循环控制
    3. 1.3. 三. printf格式化打印
    4. 1.4. 四. echo打印颜色
      1. 1.4.1. 实现上下键选择菜单执行指定功能
    5. 1.5. 五. Shell函数
      1. 1.5.0.1. 获取函数参数的个数
      2. 1.5.0.2. 随堂练习
  2. 1.6. 六. Shell数组
    1. 1.6.0.1. 索引数组
    2. 1.6.0.2. 关联数组
    3. 1.6.0.3. 数组扩展
    4. 1.6.0.4. 随堂练习
  • 1.7. 七. Shell正则表达式
    1. 1.7.1. 正则表达式元字符
    2. 1.7.2. 其他元字符
    3. 1.7.3. POSIX字符类(扩展)
    4. 1.7.4. 随堂作业
  • 1.8. 八. 文本处理三剑客
    1. 1.8.1. sed
      1. 1.8.1.1. 随堂练习
      2. 1.8.1.2. 模式空间和保留空间(扩展)
    2. 1.8.2. awk
      1. 1.8.2.1. 1. awk的语法
      2. 1.8.2.2. 2. 简单使用
      3. 1.8.2.3. 3. awk的模式匹配
      4. 1.8.2.4. 4. 基本操作
      5. 1.8.2.5. 5. awk的变量
        1. 1.8.2.5.1. 1)内置变量
        2. 1.8.2.5.2. 2)自定义变量
      6. 1.8.2.6. 6. 赋值运算
      7. 1.8.2.7. 7. awk的特殊模式
      8. 1.8.2.8. 8. 扩展: awk 内嵌 if for
      9. 1.8.2.9. 9. 扩展:awk数组
      10. 1.8.2.10. 随堂练习
    3. 1.8.3. grep
  • 1.9. 九. expect 免交互
    1. 1.9.1. 非交互生成密钥对及传递公钥

  • Shell编程

    语言:自然语言,计算机语言(低级语言:0 1 汇编 C C++ 高级语言:java php python oc swift shell(bash))

    • 编译型:C C++ java

    • 解释型:php python shell(bash)

    编程:让计算机根据自己定义的路线去执行命令

    编译型:

    1
    2
    3
    4
    5
    (1)只须编译一次就可以把源代码编译成机器语言,后面的执行无须重新编译,直接使用之前的编译结果就可以;因此其执行的效率比较高;

    (2)编译性语言代表:C、C++、Pascal/Object Pascal(Delphi);

    (3)程序执行效率比较高,但比较依赖编译器,因此跨平台性差一些;

    解释型:

    1
    2
    3
    4
    5
    6
    7
    (1)源代码不能直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行;

    (2)程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次;

    (3)解释性语言代表:Python、JavaScript、Shell、Ruby、MATLAB等;

    (4)运行效率一般相对比较低,依赖解释器,跨平台性好;

    一. Shell编程初识

    Shell概览

    Shell 能做什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    1. 自动化批量系统初始化程序 (update,软件安装,时区设置,安全策略...)

    2. 自动化批量软件部署程序 (LAMP,LNMP,Tomcat,LVS,Nginx)

    3. 应用管理程序 (KVM,集群管理扩容,MySQL,DELLR720批量RAID)

    4. 日志分析处理程序(PV, UV, 200, !200, top 100, grep/awk)

    5. 自动化备份恢复程序(MySQL完全备份/增量 + Crond)

    6. 自动化管理程序(批量远程修改密码,软件升级,配置更新)

    7. 自动化信息采集及监控程序(收集系统/应用状态信息,CPU,Mem,Disk,Net,TCP Status,Apache,MySQL)

    8. 配合Zabbix信息采集(收集系统/应用状态信息,CPU,Mem,Disk,Net,TCP Status,Apache,MySQL)

    9. 自动化扩容(增加云主机——>业务上线)
    zabbix监控CPU 80%+|-50% Python API AWS/EC2(增加/删除云主机) + Shell Script(业务上线)

    --------------

    10. 俄罗斯方块,打印三角形,打印圣诞树,打印五角星,运行小火车,坦克大战,排序算法实现

    11. Shell可以做任何事(一切取决于业务需求)

    shell的定义

    1
    2
    3
    4
    Shell 是命令解释器

    Shell 也是一种程序设计语言,他有变量,关键字,各种控制语句,有自己的语法结构,利用shell程序设计语言可以编写功能很强、代码简短的程序。

    shell的分类和更改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     # cat /etc/shells  查看系统支持那些shell
    /bin/sh
    /bin/bash
    /sbin/nologin
    /usr/bin/sh
    /usr/bin/bash
    /usr/sbin/nologin
    /bin/tcsh
    /bin/csh

    # chsh –l 查看所有的shell

    其他

    1、linux命令的分类:

    内嵌命令 外部命令 别名 函数

    1
    2
    3
    4
    5
    6
    7
    别名: alias

    查询别名: # alias

    设置别名: # alias 命令名称='动作'

    取消别名: # unalias 别名名称

    2、历史命令

    history, 默认只记录1000条

    清除历史命令: history -c

    如要每行历史命令前面加上时间

    1
    2
    3
    # echo 'export HISTTIMEFORMAT="%F %T "' >> ~/.bashrc

    # source ~/.bashrc

    Shell的特性及特点

    一、shell? 命令解释器

    shell命令 cp ls date

    Linux支持的shell

    1
    2
    3
    # cat /etc/shells

    # chsh -l
    二、GNU/bash shell特点
    1. 命令和文件自动补齐 - Tab
    2. 命令历史记忆功能 上下键、!number(命令的序号)、!$
    3. 快捷键
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Ctrl+a 切换到命令行开始(跟home一样,但是home在某些unix环境下无法使用)

    Ctrl+e 切换到命令行末尾

    Ctrl+u 清除剪切光标之前的内容

    Ctrl+k 清除剪切光标之后的内容[包括光标本身]

    ctrl+y 粘贴刚才所删除的字符

    ctrl+左右键 快速移动光标

    Ctrl+r 在历史命令中查找,输入关键字调出之前的命令
    1. 前后台作业控制 &、nohup、ctrl + C、ctrl + Z(停止),bg %1(将一个在后台暂停的命令,变成继续执行)、fg (将后台中的命令调至前台继续运行)%1、kill %3、screen

    案例:bg fg tailf /var/log/messages ^Z sleep

    1. 输入输出重定向 0,1,2 > >> 2> 2>> 2>&1 &>

      cat < /etc/hosts

      cat <<EOF

      cat >file1 <<EOF

    2. 管道 | tee

    1
    2
    3
    4
    5
    # ip addr |grep 'inet ' |grep eth0

    # ip addr |grep 'inet ' |tee test |grep eth0 覆盖

    # ip addr |grep 'inet ' |tee -a test |grep eth0 -a 追加

    1. 命令排序

    ​ && || ; 具备逻辑判断,连接命令用的

    1
    2
    3
    4
    5
    ;   		无论前面是否执行成功,分号后的命令都会继续执行	

    && 逻辑与;前面执行成功,后面的才继续执行

    || 逻辑或;前面命令不成功,后面的命令才会继续

    ​ 注意:

    ​ command & 后台执行

    ​ command &>/dev/null 混合重定向(标准输出1,错误输出2)

    ​ command1 && command2 命令排序,逻辑判断

    1. shell通配符(元字符)表示的不是本意

      基本元字符

    * 匹配任意多个字符 ls in* rm -rf * rm -rf .pdf find / -iname “-eth0” yum -y install epel*

    ? 匹配任意一个字符 touch love loove live l7ve; ll l?ve

    . 匹配任意一个字符

    [] 匹配括号中任意一个字符 [abc] [a-z] [0-9] [a-zA-Z0-9]

    () 在子shell中执行(cd /boot;ls) (umask 077; touch file1000)

    ​ 注意:如何查看当前子shell ps | grep $$ $$:当前shell的pid

    {} 集合 touch file{1..9}

    ​ # mkdir /home/{111,222} mkdir -pv /home/{333/{aaa,bbb},444}

    ​ # cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.old

    ​ # cp -rv /etc/sysconfig/network-scripts/{ifcfg-eth0,ifcfg-eth0.old}

    ​ # cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0{,.old}

    \ 转义符,让元字符回归本意

    ​ # echo *

    ​ # echo *

    ​ # touch xing\ dian

    备注:

    ​ mysql % 所有字符 (like 模糊查询) _ 单个字符 (like 模糊查询)

    1. 计算器

    ​ 计算符号:

    取余 **
    + - * / % 次方

    ​ 计算整数

    ​ # echo $[1+2] //计算整数1+2 ~~ echo $((1+2))

    ​ # expr 1 + 1 // + 号两边一定要有空格

    ​ 计算小数 bc

    ​ # echo 1.5+1.5 | bc

    脚本编写

    创建bash脚本: 一般以.sh结尾的文件 .py结尾的文件是python的脚本

    ​ 1. 创建脚本文件

    ​ #!/bin/bash #!/usr/bin/env bash

    ​ 指定命令解释器:第一行的专门解释命令解释器

    ​ 注释 :以#开头的都不生效

    ​ 编写bash指令集合

    ​ 2. 修改权限(改不改路径都可以执行)

    1
    2
    3
    4
    5
    #!/bin/bash
    #注释
    #author:blackmed
    #version:0.1
    #功能

    执行shell脚本

    1. 直接执行,在命令行写脚本的路径 开启子shell执行,将执行的输出返回到父shell中,前提,脚本要有可执行权限

    2. 调用解释器执行

    3. source 脚本的绝对路径,点执行 . 脚本的绝对路径 直接在当前shell下执行,会影响当前的shell环境

    练习1:

    编写脚本,一键部署青蛙吃苍蝇小游戏???

    1
    2
    3
    4
    1. 安装httpd服务
    2. 解压项目包
    2. 项目包的内容copy到 /var/www/html/
    4. 启动httpd服务

    练习2:

    编写脚本,一键配置腾讯云的yum源,Base epel

    1. 现存的源备份掉(删除)
    2. 建立新的源!!
    	cat > /ect/yum.repos.d/tencent_base.repo <<EOF
    	....
    	EOF
    3. yum repolist 
    

    bash脚本调试 [了解]

    ​ •sh –x script

    ​ 这将执行该脚本并显示所有变量的值

    ​ •sh –n script

    ​ 不执行脚本只是检查语法模式,将返回所有错误语法

    ​ •sh –v script

    ​ 执行脚本前把脚本内容显示在屏幕上

    变量☆

    一、shell 变量

    什么是shell变量?

    变量的类型

    变量的定义方式

    变量的运算

    变量”内容”的删除和替换[了解]

    shell 变量? 用一个固定的字符串去表示不固定的内容

    如何去赋值变量??

    1. 显式赋值

    ​ 变量名=变量值

    ​ 示例:

    1
    2
    3
    4
    ip1=192.168.1.251
    school="BeiJing 1000phone"
    today1=`date +%F`
    today2=$(date +%F)

    注意事项:

    ​ 1. = 两边不要有空格

    ​ 2. 变量名不能以数字或符号开头,例如 1a @a

    ​ 3. 变量名可以由大写+小写+数字+下划线组成例如 a1 A1 a_1 A_2

    1. read 从键盘读入变量值

    ​ read 变量名

    1
    2
    3
    4
    5
    6
    7
    read -p "提示信息: "  变量名
    read -t 5 -p "提示信息: " 变量名

    选项:
    -t 后面跟秒数,定义输入字符的等待时间
    -n 后跟一个数字,定义输入文本的长度,很实用。
    -s 键盘输入不显示
    二、变量的类型
    1. 自定义变量

    ​ 定义变量: 变量名=变量值 变量名必须以字母或下划线开头,区分大小写 ip1=192.168.2.115

    ​ 引用变量: $变量名 或 ${变量名}

    ​ 查看变量: echo $变量名 set(所有变量:包括自定义变量和环境变量)

    ​ 取消变量: unset 变量名

    ​ 作用范围: 仅在当前shell中有效

    2. 环境变量

    ​ 定义环境变量: 方法一 export back_dir2=/home/backup

    ​ 方法二 export back_dir1 将自定义变量转换成环境变量

    ​ 引用环境变量: $变量名 或 ${变量名}

    ​ 查看环境变量: echo $变量名

    ​ 取消环境变量: unset 变量名

    ​ 变量作用范围: 在当前shell和子shell有效

    环境变量拥有可继承性:export之后就拥有继承性

    ​ 永久生效:写到四个登录脚本,/etc/profile ~/.baserc ~/.bash_profile /etc/bashrc source ~/.bashrc

    举例:

    ​ vim /etc/profile

    ​ JAVA_HOME=/usr/local/java

    ​ PATH=$JAVA_HOME/bin:$PATH

    ​ export JAVA_HOME PATH

    ​ vim ~/.bash_profile

    ​ :/usr/local/mycat/bin

    ​ =====================================================

    如何设置环境变量

    ssh登入或su 登入就会执行加载以下文件

    1)/etc/profile

    2)/etc/bashrc

    3)/root/.bash_profile //配置roo用户的环境变量

    4)~/.bashrc

    5)/home/tom/.bashrc ///配置tom用户的环境变量

    所有用户登录都会加载这四个配置文件

    /etc下两个为全局环境配置文件,对所有用户生效

    家目录下两个为用户自己的环境配置文件,只对用户本人生效

    /etc/profile

    这是系统最主要的shell设置文件,也是用户登陆时系统最先检查的文件,有关重要的环境变量都定义在此,其中包括PATH,USER,LOGNAME,MAIL,HOSTNAME,HISTSIZE,INPUTRC等。而在文件的最后,它会检查并执行/etc/profile.d/*.sh的脚本。

    ~/.bash_profile

    这个文件是每位用户的bash环境设置文件,它存在与于用户的主目录中,当系统执行/etc/profile 后,就会接着读取此文件内的设置值。在此文件中会定义USERNAME,BASH_ENV和PATH等环境变量,但是此处的PATH除了包含系统的$PATH变量外加入用户的“bin”目录路径.

    ~/.bashrc

    接下来系统会检查/.bashrc文件,这个文件和前两个文件(/etc/profile 和.bash_profile)最大的不同是,每次执行bash时,.bashrc都会被再次读取,也就是变量会再次地设置,而/etc/profile,./bash_profile只有在登陆时才读取。就是因为要经常的读取,所以~/.bashrc文件只定义一些终端机设置以及shell提示符号等功能,而不是定义环境变量。

    ~/.bash_login

    如果/.bash_profile文件不存在,则系统会转而读取/.bash_login这个文件内容。这是用户的登陆文件,在每次用户登陆系统时,bash都会读此内容,所以通常都会将登陆后必须执行的命令放在这个文件中。

    .profile

    如果./bash_profile ~./bash_login两个文件都不存在,则会使用这个文件的设置内容,其实它的功能与/.bash_profile相同。

    .bash_logout

    如果想在关机前(退出登入前)执行一些工作,都可以在此文件中设置。

    例如:

    #vi ~.bash_logout

    clear

    仅执行一个clear命令在你注销的时候

    ~/.bash_history

    这个文件会记录用户先前使用的历史命令。

    注意:在/etc/profile.d建立独立的环境变量配置文件

    ​ 常用环境变量: USER 当前登入的用户

    ​ UID 当前登入用户的UID

    ​ HOME 当前登入用户的家目录

    ​ HOSTNAME 当前登入的主机名

    ​ PWD 当前目录的绝对路径

    ​ PS1 当前的终端提示符的格式

    ​ PATH

    ​ PATH:这个变量存放的是所有命令所在的路径 修改:PATH=$PATH:+目录

    ​ 意义:让所有命令在执行的时候不必输入路径

    练习:

    编写一个脚本,执行脚本会输出以下内容 //任何用户都可以执行

    # msg

    2024年 09月 10日 星期一 16:08:20 CST

    当前登入的用户名:xx

    当前登入的用户ID:xx

    当前用户的家目录:xx

    当前所在的目录: xx

    3. 位置变量

    ​ $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10}

    4. 预定义变量

    ​ $0 脚本名

    ​ $* 所有的参数

    ​ $@ 所有的参数

    ​ $# 参数的个数

    ​ $$ 当前进程的PID

    ​ $! 上一个后台进程的PID

    ​ $? 上一个命令的返回值 0表示成功 ,1-254 表示不成功 【重点】

    ​ 示例1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    # vim test.sh
    echo "第2个位置参数是$2"

    echo "第1个位置参数是$1"

    echo "第4个位置参数是$4"

    echo "所有参数是: $*"

    echo "所有参数是: $@"

    echo "参数的个数是: $#"

    echo "当前进程的PID是: $$"

    echo '$1='$1

    echo '$2='$2

    echo '$3='$3

    echo '$*='$*

    echo '$@='$@

    echo '$#='$#

    echo '$$='$$

    ​ 示例2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # vim ping.sh

    #!/bin/bash
    ping -c2 $1 &>/dev/null
    if [ $? = 0 ];then
    echo "host $1 is ok"
    else

    echo "host $1 is fail"
    fi

    # chmod a+x ping.sh

    # ./ping.sh 192.168.2.25

    ​ 示例3:

    1
    2
    3
    4
    5
    6
    7
    8
    # vim first.sh
    #!/bin/bash

    back_dir1=/var/backup
    read -p "请输入你的备份目录: " back_dir2
    echo $back_dir1
    echo $back_dir2
    # sh first.sh

    ​ 示例4:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # vim ping2.sh

    #!/bin/bash
    read -p "Input IP: " ip
    ping -c2 $ip &>/dev/null
    if [ $? = 0 ];then
    echo "host $ip is ok"
    else
    echo "host $ip is fail"
    fi

    # chmod a+x ping2.sh

    # ./ping.sh

    ​ 定义或引用变量时注意事项:

    ​ “ “ 弱引用 可以实现变量和命令的替换

    ​ ‘ ‘ 强引用 不完成变量替换

    ​ [root@qiangge ~]# school=1000phone

    ​ [root@qiangge ~]# echo “${school} is good”

    ​ 1000phone is good

    ​ [root@qiangge ~]# echo ‘${school} is good’

    ​ ${school} is good

    命令替换 等价于 $() 反引号中的shell命令会被先执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@qiangge ~]# touch `date +%F`_file1.txt  

    [root@qiangge ~]# touch $(date +%F)_file2.txt


    [root@qiangge ~]# disk_free3="df -Ph |grep '/$' |awk '{print $4}'" 错误

    [root@qiangge ~]# disk_free4=$(df -Ph |grep '/$' |awk '{print $4}')

    [root@qiangge ~]# disk_free5=`df -Ph |grep '/$' |awk '{print $4}'

    练习:

    1、写一个计算年龄的脚本,用户输入出生年份,程序输出 今年多少 xx 岁?

    5. 变量”内容”的删除和替换(扩展)

    ===”内容”的删除===

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    [root@qiangge ~]# url=www.sina.com.cn

    [root@qiangge ~]# echo ${#url} 获取变量值的长度
    15

    [root@qiangge ~]# echo ${url} 标准查看
    www.sina.com.cn

    [root@qiangge ~]# echo ${url#*.} 从前往后,最短匹配
    sina.com.cn

    [root@qiangge ~]# echo ${url##*.} 从前往后,最长匹配 贪婪匹配
    cn


    [root@qiangge ~]# url=www.sina.com.cn

    [root@qiangge ~]# echo ${url}
    www.sina.com.cn

    [root@qiangge ~]# echo ${url%.*} 从后往前,最短匹配
    www.sina.com

    [root@qiangge ~]# echo ${url%%.*} 从后往前,最长匹配 贪婪匹配
    www


    [root@qiangge ~]# url=www.sina.com.cn

    [root@qiangge ~]# echo ${url#a.}
    www.sina.com.cn

    [root@qiangge ~]# echo ${url#*sina.}
    com.cn


    [root@qiangge ~]# echo $HOSTNAME
    qiangge.1000phone.com

    [root@qiangge ~]# echo ${HOSTNAME%%.*}
    qiangge

    ===索引及切片===

    1
    2
    3
    4
    5
    6
    7
    8
    [root@qiangge ~]# echo ${url:0:5}
    0:从头开始
    5:往后切5个字符

    [root@qiangge ~]# echo ${url:5:5} //从第6个开始算,切到往后的第5个字符 (6-10 的5个字符)

    [root@qiangge ~]# echo ${url:5} //把变量前面的5个字符,切掉,只留下后面的

    ===”内容”的替换===

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@qiangge ~]# url=www.sina.com.cn

    [root@qiangge ~]# echo ${url/sina/baidu}
    www.baidu.com.cn

    [root@qiangge ~]# url=www.sina.com.cn

    [root@qiangge ~]# echo ${url/n/N}
    www.siNa.com.cn

    [root@qiangge ~]# echo ${url//n/N} 贪婪匹配.将变量内容里所有的n都替换为N
    www.siNa.com.cN

    ===变量的替代===

    条件:

    ​ 1. 如果没有该变量,就会被替换

    ​ 2. 如果变量有值或者值为空,就不会被替换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    [root@qiangge ~]# unset var1

    [root@qiangge ~]#

    [root@qiangge ~]# echo ${var1}

    [root@qiangge ~]# echo ${var1-aaaaa}
    aaaaa

    [root@qiangge ~]# var2=111

    [root@qiangge ~]# echo ${var2-bbbbb}
    111

    [root@qiangge ~]#

    [root@qiangge ~]# var3=

    [root@qiangge ~]# echo ${var3-ccccc} //var3变量已经定义,但是没有值,也不会被替代


    ${变量名-新的变量值}
    变量没有被赋值(没有变量):会使用“新的变量值“ 替代



    ${变量名+新的变量值}
    变量有被赋值(有变量):会使用“新的变量值“ 替代


    6. i++ 和 ++i (了解)

    i++是自增运算,规则是先运算在let i++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    [root@qiangge ~]# echo $i自增,每次自增的步距为1
    对变量的值的影响:

    [root@qiangge ~]# i=1

    [root@qiangge ~]# let i++

    [root@qiangge ~]# echo $i
    2

    [root@qiangge ~]# j=1

    [root@qiangge ~]# let ++j

    [root@qiangge ~]# echo $j
    2


    对表达式的值的影响:
    [root@qiangge ~]# unset i

    [root@qiangge ~]# unset j

    [root@qiangge ~]#

    [root@qiangge ~]# i=1

    [root@qiangge ~]# j=1

    [root@qiangge ~]#

    [root@qiangge ~]# let x=i++ //先赋值,再运算

    [root@qiangge ~]# let y=++j //先运算,再赋值

    [root@qiangge ~]#

    [root@qiangge ~]# echo $i
    2

    [root@qiangge ~]# echo $j
    2

    [root@qiangge ~]#

    [root@qiangge ~]# echo $x
    1

    [root@qiangge ~]# echo $y
    2

    企业案例(作业)

    1.编写一个shell脚本,用于搜集其执行主机的信息,打印结果如下:

    当前时间为:

    当前用户为:

    当前用户的家目录为:

    用户的标识为:

    主机名称为:

    mem的使用率:

    当前主机IP地址:

    cpu使用率:

    磁盘剩余量:

    二. Shell流程控制

    主要知识点:

    1. 条件测试

    2. if判断语句

    3. case菜单

    4. for循环语句

    5. while循环

    Shell条件测试

    1
    2
    3
    4
    5
    文件测试

    数值[整数]比较

    字符串比较

    Shell 条件测试

    1
    2
    3
    4
    5
    格式1: test 条件表达式

    格式2: [ 条件表达式 ]

    格式3: [[ 条件表达式 ]] //支持扩展正则,写两个以上的条件( && || )
    1.文件测试 [ 操作符 文件或目录 ]

    案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@localhost ~]# test -d /home

    [root@localhost ~]# echo $?
    0

    [root@localhost ~]# test -d /home11111

    [root@localhost ~]# echo $?
    1

    [root@localhost ~]# [ -d /home ]

    [ -e dir或file ] //单独判断是否有文件 -e 后面可以接文件也可以接目录

    [ -d dir ]

    [ -f file ] 是否存在,而且是文件

    [ -r file ] 当前用户对该文件是否有读权限

    [ -x file ]

    [ -w file ]

    ——————– 判断最好加上绝对路径—————

    1
    2
    3
    [root@localhost ~]# [ ! -d /ccc ]  && mkdir /ccc

    [root@localhost ~]# [ -d /ccc ] || mkdir /ccc

    test命令或者返回0(真) 或者返回1(假).

    test可理解的表达式类型分为四类:

    1)判断表达式

    if (表达式为真)

    if ! 表达式为假

    [ 表达式1 -a 表达式2 ] 或者 [ 表达式1 ] && [ 表达式2 ] //两个表达式都为真,满足两个条件

    [ 表达式1 -o 表达式2 ] 或者 [ 表达式1 ] || [ 表达式2 ] //两个表达式有一个为真,满足其中一个条件即可

    2)判断字符串(一般用于判断变量) 加双引号

    test –n “字符串” 字符串的长度非零,则为真; 变量有值 (变量为空格也为真,定义变量为空则为假)

    test –z “字符串” 字符串的长度为零,则为真;变量没有值

    test “字符串1” == “字符串2” 字符串相等,则为真

    test “字符串1” != “字符串2” 字符串不等,则为真

    [[ “123” =~ ^[0-9]+$ ]] 字符串约等于,正则能匹配

    3)判断整数

    test 整数1 -eq 整数2 整数相等

    test 整数1 -ge 整数2 整数1大于等于整数2

    test 整数1 -gt 整数 2 整数1大于整数2

    test 整数1 -le 整数 2 整数1小于等于整数2

    test 整数1 -lt 整数 2 整数1小于整数2

    test 整数1 -ne 整数 2 整数1不等于整数2

    4)判断文件

    test File1 –ef File2 两个文件具有同样的设备号和i结点号

    test File1 –nt File2 文件1比文件2 新

    test File1 –ot File2 文件1比文件2 旧

    test –b File 文件存在并且是块设备文件

    test –c File 文件存在并且是字符设备文件

    test –d File 文件存在并且是目录

    test –e File 文件存在 exist

    test –f File 文件存在并且是普通文件

    test –g File 文件存在并且是设置了组ID

    test –G File 文件存在并且属于有效组ID

    test –h File 文件存在并且是一个符号链接(同-L)

    test –k File 文件存在并且设置了sticky位

    test –b File 文件存在并且是块设备文件

    test –L File 文件存在并且是一个符号链接(同-h)

    test –o File 文件存在并且属于有效用户ID

    test –p File 文件存在并且是一个命名管道

    test –r File 文件存在并且可读

    test –s File 文件存在并且是一个套接字

    test –t FD 文件描述符是在一个终端打开的

    test –u File 文件存在并且设置了它的suid位

    test –w File 文件存在并且可写

    test –x File 文件存在并且可执行

    注意:在使用[]简写test时,左中括号后面的空格和右括号前面的空格是必需的,如果没有空格,Shell不可能辨别表达式何时开始何时结束.

    2.数值比较 [ 整数1 操作符 整数2 ]

    [ 1 -gt 10 ] 大于

    [ 1 -lt 10 ] 小于

    [ 1 -eq 10 ] 等于

    [ 1 -ne 10 ] 不等于

    [ 1 -ge 10 ] 大于等于

    [ 1 -le 10 ] 小于等于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [root@localhost ~]# disk_use=$(df -P |grep '/$' |awk '{print $5}' |awk -F% '{print $1}')

    [root@localhost ~]# [ $disk_use -gt 90 ] && echo "war......"

    [root@localhost ~]# [ $disk_use -gt 60 ] && echo "war......"
    war......
    wall "内容"

    [root@localhost ~]# id -u
    0

    [root@localhost ~]# [ $(id -u) -eq 0 ] && echo "当前是超级用户"
    当前是超级用户

    [alice@localhost ~]$ [ $UID -eq 0 ] && echo "当前是超级用户" || echo "you不是超级用户"
    you不是超级用户

    C语言风格的数值比较(了解)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [root@localhost ~]# ((1<2));echo $?
    0

    [root@localhost ~]# ((1==2));echo $?
    1

    [root@localhost ~]# ((1>2));echo $?
    1

    [root@localhost ~]# ((1>=2));echo $?
    1

    [root@localhost ~]# ((1<=2));echo $?
    0

    [root@localhost ~]# ((1!=2));echo $?
    0

    [root@localhost ~]# ((`id -u`>0));echo $?
    1

    [root@localhost ~]# (($UID==0));echo $?
    0

    3.字符串比较

    提示:使用双引号

    1
    2
    3
    4
    5
    [root@localhost ~]# [ "$USER" == "root" ];echo $?
    0

    [tom@localhost ~]# [ "$USER" == "root" ];echo $?
    1

    注意:

    “”:弱引用,可以实现变量和命令的替换

    1
    2
    3
    4
    [root@localhost ~]# x=*

    [root@localhost ~]# echo "$x"
    *

    ‘’:强引用,不完成变量替换

    1
    2
    3
    4
    [root@localhost ~]# x=*

    [root@localhost ~]# echo '$x'
    $x

    扩展:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    [root@localhost ~]# var1=111

    [root@localhost ~]# var2=

    [root@localhost ~]# //var3变量没有定义

    [root@localhost ~]# echo ${#var1} #显示变量中字符串的个数
    3

    [root@localhost ~]# echo ${#var2}
    0

    [root@localhost ~]# echo ${#var3}
    0

    [root@localhost ~]# [ -z "$var1" ];echo $? //-z 变量字符串为空
    1

    [root@localhost ~]# [ -z "$var2" ];echo $?
    0

    [root@localhost ~]# [ -z "$var3" ];echo $?
    0

    [root@localhost ~]# [ -n "$var1" ];echo $? //-n 变量字符串不为空
    0

    [root@localhost ~]# [ -n "$var2" ];echo $?
    1

    [root@localhost ~]# [ -n "$var3" ];echo $?
    1

    小结:变量为空 或 未定义: 长度都为0

    练习:

    [root@localhost ~]# [ "$USER" = "root" ];echo $?
    0

    [root@localhost ~]# [ "$USER" = "alice" ];echo $?
    1

    [root@localhost ~]# [ "$USER" != "alice" ];echo $?
    0

    [root@localhost ~]# [ 1 -lt 2 -a 5 -gt 10 ];echo $?
    1

    [root@localhost ~]# [ 1 -lt 2 -o 5 -gt 10 ];echo $?
    0

    [root@localhost ~]# [[ 1 -lt 2 && 5 -gt 10 ]];echo $?
    1

    [root@localhost ~]# [[ 1 -lt 2 || 5 -gt 10 ]];echo $?
    0

    [root@localhost ~]# [ "$USER" = "root" ];echo $?
    0

    Shell条件结构-if判断

    if判断的语法结构

    注意:if和fi要成对出现,脚本里有几个if就得有几个fi

    1)最简单的语法-单分支

    1
    2
    3
    4
    if 条件表达式或者命令
    then
    命令;
    fi

    2)分支的if结构语法-两个分支

    1
    2
    3
    4
    5
    6
    if 条件表达式或命令
    then
    命令;
    else
    命令;
    fi

    例子:

    如果使用该脚本的用户是root,则执行以下操作,否则退出并提示使用root用户执行

    判断文件/tmp/a.txt是否存在且为普通文件,若存在则提示文件已存在,否则创建文件a.txt并提示文件成功创建.

    1
    2


    3)多分支if结构语法结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if 条件表达式或者命令
    then
    命令;
    elif 条件表达式或命令 //elif可以有多个
    then
    命令;
    else
    命令;
    fi

    多重条件判断

    条件表达式1 选项 条件表达式2

    选项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -a:并且 
    -o :或者
    ! 条件表达式 :取反,非

    if [ $username = "root" -a $password = "redhat" ]

    if [ $username = "root" ] && [ $password = "redhat" ]

    if [[ $username = "root" && $password = "redhat" ]]

    练习:

    1、提示输入一个1-100以内的数字,

    ​ 1.当你输入的是81~100,提示优秀

    ​ 2.当你输入的是60~80,提示良好

    ​ 3.当你输入的是60以下,不含60,提示真菜

    ​ 思考: 4.如果输入的数字不在1~100范围以内,退出脚本,并且给他一jio

    ​ 5.要求输入的数字一定只能是正整数,如果不是则进行提示

    1
    2


    随堂作业

    1.提示用户输入想创建的用户名,判断若该用户名存在,则提示用户已存在,否则创建用户并提示用户成功创建.

    1
    2


    2.改写练习1,先判断执行脚本的用户是不是root,如果是就执行练习1的操作;否则就提示不是root并退出。

    1
    2


    3.写一个脚本,从标准输入读取一个IP地址,如果用户输入的IP不为空就测试该主机是否存活,活着输出“alive”,否则输出“not alive” ,如果为空,直接退出脚本

    1
    2


    case匹配模式

    模式匹配:case

    ========================================================

    case:多分支判断

    语法结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    case 引用变量 in
    值1)
    命令
    ;;
    值2)
    命令
    ;;
    ... ...
    ;;
    值n)
    命令
    ;;
    *) //*表示匹配除了以上各个值之外的所有值
    命令;
    ;;
    esac

    同时匹配两个变量的方法一:将两个变量看成一个整体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@srv254-200 scripts]# vim case2.sh
    #!/bin/bash
    read -p "Please input your name: " user_name
    read -s -p "Please input your password: " password

    case "$user_name $password" in
    "root redhat")
    echo "You are right."
    ;;
    "oracle ora")
    echo "You are right for oracle."
    ;;
    *)
    echo "You are wrong."
    ;;
    esac

    测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@srv254-200 scripts]# sh case2.sh 
    Please input your name: root
    Please input your password: redhat
    You are right.

    [root@srv254-200 scripts]# sh case2.sh
    Please input your name: haha
    Please input your password: good
    You are wrong.

    同时匹配两个变量的方法二:用逗号分隔多个变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@srv254-200 scripts]# vim case3.sh
    #!/bin/bash
    read -p "Please input your name: " user_name
    read -s -p "Please input your password: " password

    case $user_name,$password in
    root,redhat)
    echo "You are right."
    ;;
    oracle,ora)
    echo "You are right for oracle."
    ;;
    *)
    echo "You are wrong."
    ;;
    esac

    ========================================================

    案例1:简单的模式匹配

    确定要继续删除吗 yes/no: “ yes

    1
    2


    案例2:系统管理工具箱

    执行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@srv254-200 scripts]# bash tool.sh
    Command action
    h 显示命令帮助
    f 显示磁盘分区
    d 显示磁盘挂载
    m 查看内存使用
    u 查看系统负载
    q 退出程序

    Command (h for help): m

    total used free shared buffers cached
    Mem: 7628 840 6788 0 29 378
    Swap: 2047 0 2047

    思考额外条件?

    如果使用其中一个工具,不能退出,按Q/q 才能退出该脚本

    Shell循环结构-for

    Shell循环:for 循环次数是固定的

    语法结构

    1
    2
    3
    4
    for 变量 in 变量的列表
    do
    命令块
    done

    在命令行直接运行for命令

    1
    # for 变量 in 变量列表; do 命令块; done

    变量列表决定循环次数

    注意:如果是以文件行数为单位进行循环,行内有空格会影响循环范围

    IFS=$’\n’ //约束,只能以行为单位进行循环

    1
    2
    3
    4
    5
    6
    #for i in 1 2 3 4 5
    #for i in `seq 5`
    for i in {1..5}
    do
    echo $i
    done

    练习1:

    打印1~100之间的偶数

    ​ 假设打印 1~100的奇数呢?

    1

    练习2:

    计算1~100的和

    1

    -——————————————

    C写法:

    1
    2
    3
    4
    for ((i=1;i<=10;i++))
    do
    动作块
    done

    ==================================================

    插曲 —— seq

    1
    2
    3
    4
    5
    # seq 1 2 10 //打印奇数 起始位置 步长 结束位置 

    # seq 5 10 //打印5到10 起始位置 结束位置 ,步长为1

    # seq -w 10 //等宽显示 :不够位数在前面补0
    随堂练习
    1. 用for循环,打印/etc/passwd的第一个字段

      如果要彻底删除uid大于1000的用户

    1
    2


    1. 批量添加10个用户user01-user10,并设置密码,密码分别为pass01-pass10,
    1
    2


    ​ 2.1 如果不创建user05用户那该怎么做?

    1
    2


    1. 用for循环在/haowan目录下批量创建10个文件,文件名为up-1,up-2,…,up-10
    1

    1. 用for循环将第三题文件名中的up全部改成down
    1
    2


    1. 求1到100所有正整数之和
    1

    1. 求1到100所有偶数之和
    1

    1. 打印9*9乘法表
    1
    2


    1. 输出随机的10个手机号码

    手机号: 1xxxxxxxxxx

    1
    2


    Shell循环结构-while

    while语法: 无限循环 (也可以有限循环)

    1
    2
    3
    4
    5
    while 条件表达式或命令
    do
    命令
    循环体 //注意:循环体部分一定要有变量的更新,否则死循环
    done

    在命令行直接运行while命令

    1
    i=1 ; while [ $i -le 10 ]; do echo $i; let i++; done

    案例

    1
    2
    3
    4
    5
    6
    7
    8
    [root@srv254-200 scripts]# cat while.sh 
    #!/bin/bash
    i=1
    while [ $i -le 5 ]
    do
    echo $i
    let i++ //变量的更新
    done

    -———————————–

    i++:先用,后加1

    ++i:先加1,后用

    -———————————–

    扩展:

    添加用户脚本,要求用户的信息来自于文本文件

    1
    2
    3
    4
    5
    [root@srv254-200 scripts]# cat user_list 
    tom:passtom:2014
    jack:passjack:2018
    mary:passmary:2016
    kite:passkite:2000
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@srv254-200 scripts]# cat while1.sh 
    #!/bin/bash
    while read line
    do
    username=`echo $line | cut -d: -f1`
    password=`echo $line | cut -d: -f2`
    uid=`echo $line | cut -d: -f3`
    if id $username &> /dev/null
    then
    echo "exists"
    else
    useradd -u $uid $username && echo $password | passwd --stdin $username &> /dev/null
    fi
    done < /scripts/user_list
    随堂练习
    1. 使用while循环,添加test1-test10,分别设置密码为pass1-pass10,不添加test5
    1
    2


    1. 使用while循环,将上题中的9个用户删除干净,删除成功给出如下提示“user xxx delete successfully.”
    1

    1. 使用while循环,机器里有test1~10个用户,其中有一个用户已经被删除,请找出该用户,并且删除其他用户?
    1

    1. 使用while循环计算1+2+3+…+100的和?
    1
    2


    1. 现在有一个文件,内容如下
    1
    2


    要求进行发送邮件给tom jerry jack 三个用户,标题分别为hello abc 123,内容接着来

    1
    2


    echo “邮件内容” | mail -s 标题 收件人

    循环控制

    break 跳出整个循环

    sleep 10 //等待10秒,再继续下一操作

    continue 跳出当前这一次循环

    exit 直接退出,直接退出整个脚本

    { 命令1

    命令2

    }& 把多个命令同时放到后台执行

    使用wait等待所有子任务结束

    1
    2
    3
    4
     #!/bin/bash  
    sleep 10 &
    sleep 5 &
    wait //wait后没有指定进程号,所以要等待所有进程执行结束后才退出,这里是等待10秒后退出

    三. printf格式化打印

    printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。使用 printf 的脚本比使用 echo 移植性好。

    语法格式:printf format-string [arguments…]

    • format-string: 为格式控制字符串
    • arguments: 为参数列表。

    命令案例:

    1
    2
    3
    4
    5
    6
    7
    8
    # echo "Hello, Shell"
    Hello, Shell

    # printf "Hello, Shell" //默认不换行

    # printf "Hello, Shell\n"
    Hello, Shell

    脚本案例:

    1
    2
    3
    4
    5
    6
    7
    8
    # vim printf.sh

    #!/bin/bash
    printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
    printf "%-10s %-8s %-4.2f\n" 张三 男 66.1234
    printf "%-10s %-8s %-4.2f\n" 李四 男 48.6543
    printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876

    %s %d %f //都是格式替代符

    字符串 整数 小数

    %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。

    %-4.2f 指格式化为小数,其中.2指保留2位小数。

    click me click me
    \f 换页(formfeed)
    \n 换行
    \r 回车(Carriage return)

    字符串:%s

    小数:%f

    数字:%d

    -——————————————————————

    练习:

    现在有一个名单,name.txt

    内容如下

    1
    2
    3
    4
    5
    6
    7
    tom:男:牛
    jerry:女:虎
    jack:男:马
    Aqiangge:男:龙
    Bge:男:狗
    kunge:男:鸡
    oldsix:男:狗

    按要求修改其格式写入到name.txt.bak内并进行输出,格式如下

    # cat name.txt.bak

    1
    2
    3
    4
    5
    姓名        性别        生肖
    tom 男 牛
    jerry 女 虎
    ...
    oldsix 男 狗

    案例:

    1
    2


    四. echo打印颜色

    shell脚本中echo显示内容带颜色显示,需要使用到-e参数

    格式1:echo -e “\033[背景颜色;字体颜色m 要输出的字符 \033[0m”

    格式2:echo -e “\e[背景颜色;字体颜色m 要输出的字符 \e[0m”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    echo -e "\033[30m 黑色字 \033[0m"

    echo -e "\033[31m 红色字 \033[0m"

    echo -e "\033[32m 绿色字 \033[0m"

    echo -e "\033[33m 黄色字 \033[0m"

    echo -e "\033[34m 蓝色字 \033[0m"

    echo -e "\033[35m 紫色字 \033[0m"

    echo -e "\033[36m 天蓝字 \033[0m"

    echo -e "\033[37m 白色字 \033[0m"

    echo -e "\033[40;37m 黑底白字 \033[0m"

    echo -e "\033[41;37m 红底白字 \033[0m"

    echo -e "\033[42;37m 绿底白字 \033[0m"

    echo -e "\033[43;37m 黄底白字 \033[0m"

    echo -e "\033[44;37m 蓝底白字 \033[0m"

    echo -e "\033[45;37m 紫底白字 \033[0m"

    echo -e "\033[46;37m 天蓝底白字 \033[0m"

    echo -e "\033[47;30m 白底黑字 \033[0m"


    控制选项:
    \033[0m:关闭所有属性

    \033[1m:高亮显示,加粗

    \033[5m:闪烁 //案例 echo -e "\033[42;5;37m 绿底白字 \033[0m

    //42 -- 背景色
    5 -- 闪烁
    37m -- 白字

    实现上下键选择菜单执行指定功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #!/bin/bash
    #清除屏幕
    clear

    # 定义选项数组
    options=("centos7" "win10" "winserver2012" "ubuntu20" "退出")

    # 初始化选中索引
    selected=0

    # 显示菜单
    show_menu() {
    clear
    echo -e "\033[33m请选择一个使用的镜像 (使用上下箭头选择,回车确认)\033[33m"
    for ((i=0; i<${#options[@]}; i++)); do
    if [ $i -eq $selected ]; then
    echo -e "\e[1;32m> ${options[$i]}\e[0m"
    else
    echo " ${options[$i]}"
    fi
    done
    }

    # 读取用户输入
    while true; do
    show_menu
    read -rsn1 input
    case "$input" in
    A) # 上箭头
    if [ $selected -gt 0 ]; then
    ((selected--))
    fi
    ;;
    B) # 下箭头
    if [ $selected -lt $(( ${#options[@]} - 1 )) ]; then
    ((selected++))
    fi
    ;;
    "") # 回车键
    break
    ;;
    esac
    done

    # 处理用户选择的选项
    selected_option="${options[$selected]}"
    if [ "$selected_option" == "退出" ]; then
    #install_space
    echo -e "\033[33m你选择了退出。\033[0m"
    exit
    else
    #install_space
    echo -e "\033[33m你选择的系统为:\033[31m$selected_option\033[0m"
    fi

    五. Shell函数

    函数function

    1
    2
    3
    4
    定义函数
    调用函数
    取消函数
    函数传参

    函数变量的适用范围:

    1
    2
    3
    默认,函数里的变量会在函数外面生效

    local 变量名称 //只允许在函数内部使用

    myfunc() //函数定义

    {

    echo “This is my first shell function”

    }

    myfunc //函数调用

    unset myfunc //取消函数

    myfunc(){

    echo “This is a new function“

    }

    myfunc

    例:

    1
    2
    3
    4
    lsl(){
    ls –l
    }
    cd "$1" && lsl

    -—————————-

    return 函数返回值 // 提前先定义好 $? 的返回值

    使用$1,$2传参

    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
    hello(){
    for i in {1..3}
    do
    echo $[$i*2]
    done
    }
    hello
    获取函数参数的个数

    在函数内部,用户也是可以通过位置变量来接受函数的值

    案例:

    1.如何通过$#来获取函数参数的个数

    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
    func(){
    echo "the function has $# parameters"

    }
    func a b c d e f
    func 12 "hello word"
    func

    注意:函数在使用位置变量的过程中,使用空格隔开,如果参数中有空格就用引号印起来。

    1. 如何通过 $1 $2 来获取函数的位置变量的值
    随堂练习

    编写系统管理工具箱

    1. 查看内存的使用率 查看磁盘的使用率和剩余容量 查看系统的负载 只能按q键退出程序
    1
    2


    1. 编写函数,实现打印绿色字体OK和红色字体FAILED 判断是否有位置参数$#,存在为Ok,不存在为FAILED
    1
    2


    六. Shell数组

    数组(array):

    ​ 把多个元素按一定顺序排列的集合,就是把有限个元素用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标[index]。

    ​ 数组是可以保存一组值的变量

    索引数组

    索引数组—–只支持正整数下标

    ​ 1. 定义数组

    1
    2
    3
    # nums=(2 5 8)           #下标从0开始,依次递增

    # nums[100]=hello

    ​ 2. 数组的调用 ${name[index]} #name数组的名称,index下标值

    1
    2
    3
    4
    5
    # echo ${nums[1]}
    5

    # echo ${nums[*]} #查看数组所有的值 * 整体 @ 个体 //效果一样
    2 5 8 hello

    ​ 3. 查看数组的下标

    1
    2
    # echo ${!nums[*]}       #按顺序列出所有值的下标
    0 1 2 100

    ​ 4. 数组的遍历 //遍历: 把所有的元素打印出来

    ​ 1).遍历值

    1
    # echo ${数组[*]}

    ​ 2).遍历下标

    1
    # echo ${!数组[*]}

    ​ 5. 数组的长度 #元素的个数

    1
    2
    # echo ${#nums[@]}     
    4

    ​ 6. 取消数组

    1
    2
    3
    4
    5
    # unset nums[100]          #取消数组中的一个元素

    # echo ${nums[@]}
    2 5 8
    # unset nums #取消整个数组
    关联数组

    关联数组—–支持字符串下标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # declare -A tom              #定义一个关联数组

    # tom[age]=20

    # tom[sex]=male

    # tom[addr]="guangzhou tianhe"

    # echo ${tom[addr]}
    guangzhou tianhe
    数组扩展

    案例1:for循环遍历数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/bash
    #定义数组
    array=(Mon Tue Wed Thur Fir Sat Sun)

    #数组遍历
    for day in ${array[*]} //以数组所有元素的值为循环列表
    do
    echo $day
    done

    案例2: for创建数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    IFS=$'\n'
    j=0
    for line in `cat /etc/hosts` //赋值
    do

    hosts[++j]=$line

    done


    1
    2
    3
    4
    5
    6
    7
    for  i in ${!hosts[*]}           //遍历 ; 以数组的下标为循环列表

    do

    echo "$i : ${hosts[i]}"

    done

    案例3:通过while 赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    while read line
    do
    hosts[++i]=$line

    #数组 索引付值 读取的内容

    #数组名 下标(索引) 值

    done < /etc/hosts
    echo "hosts first: ${hosts[1]}"
    echo


    -----------------------------------
    #遍历
    for i in ${!hosts[@]}
    do
    echo "$i : ${hosts[i]}"
    done

    定义换行符,定义某一段

    OLD_IFS=$IFS

    实战案例

    ​ 现有一个成绩名单,内容如下

    1
    2
    3
    4
    5
    6
    7
    # cat name.txt
    tom 男 85
    jerry 男 79
    jack 男 90
    alice 女 84
    natasha 女 59
    bob 男 65

    请编写脚本进行输出;男女个数分别是多少?

    ​ 及格与不及格人数分别是多少?

    ​ 打印出成绩最好的是?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #!/usr/bin/bash
    declare -A sex
    while read line
    do
    type=`echo $line | awk '{print $2}'`
    let sex[$type]++
    done < name.txt

    #男 作为数组的索引(下标)

    for i in ${!sex[@]}
    do
    echo "$i : ${sex[$i]}"
    done
    随堂练习

    1、编写脚本,生成100组 以1开头的电话号码

    ​ //要求必须使用数组

    1

    2、 编写脚本,输入出生年份,程序告知生肖

    1
    2


    3、编写一个双色球程序

    红球 33个 01~33

    篮球 16个 01~16

    条件 1. 红球不能重复,并且不能取到空球

    # bash ssq.sh

    01 08 33 30 15 17 + 08

    1
    2


    七. Shell正则表达式

    正则表达式 (Regular Expression)

    =======================================================

    正则表达式是一种字符特征的描述方式,用来在文本中匹配用户想要的内容。

    正则表达式与通配符

    正则表达式一般用于处理输入数据,常用命令有grep、sed、awk、vim等

    通配符一般用于匹配文件名,常用命令有find、ls、cp、rm等

    什么地方使用正则表达式

    vim grep sed awk nginx apache mail perl java python 等等都使用正则

    构成

    - 元字符(基本元字符、扩展元字符):拥有特殊含义

    - 普通字符:仅表示他字面意思

    正则表达式的匹配过程

    正则表达式是按照从最左端第一个字符开始,左到右依次一个一个字符进行匹配.

    特征贪婪

    基本元字符与扩展元字符

    基本元字符grep、sed、awk都支持

    扩展元符:

    ​ egrep 或 grep -E

    ​ sed -r

    ​ awk

    正则表达式元字符

    - 以下没有特殊注明的均为基本元字符

    字符匹配

    . 任意单个字符

    [] []内的任意单个字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    转义符 正则表达式 

    . [^\n\r] # 除了换行和回车之外的任意字符
    \d [0-9] # 数字字符
    \D [^0-9] # 非数字字符
    \s [ \t\n\x0B\f\r] # 空白字符
    \S [^ \t\n\x0B\f\r] # 非空白字符
    \w [a-zA-Z_0-9] # 单词字符(所有的字母/数字/下划线)
    \W [^a-zA-Z_0-9] # 非单词字符

    以上转义字符不支持三剑客,但可以在其他地方使用(了解)

    次数匹配 - 不能单独存在

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    *           # 前面的字符重复 0 次到多次

    注: 以下为扩展正则表达式
    ######################
    + # 前面的字符至少重复 1 次
    ? # 前面的字符重复 0 次或 1 次 (单个字符)
    {} # 次数匹配
    {N} # 前面的字符重复 N 次
    {N,M} # 前面的字符重复 N 到 M 次
    {N,} # 前面的字符至少重复 N 次 >= N 次以上

    位置匹配

    1
    2
    3
    4
    5
    ^           # 行首
    $ # 行尾
    \< # 词首
    \> # 词尾
    \b # 单词边界(词首或词尾)

    其他元字符

    1
    2
    3
    4
    5
    6
    7
    8
    注: 以下为扩展正则表达式
    ######################
    | # 或
    () # 分组
    \数字 # 引用分组的内容 1-9
    # 前向引用: 在正则中引用
    # 后向引用: 在其他地方引用, \0表示引用模式匹配到的所有内容 \0 ⇔ &
    & 前面匹配所有内容

    编写正则表达式的步骤

    1. 知道要匹配的内容以及它如何出现在文本中
    2. 编写正则表达式
    3. 测试它匹配的内容,不能出现错匹配,漏匹配或多匹配

    匹配 ipv4 地址,手机号码,时间

    1
    2
    3
    4
    5
    ip地址:1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[1-9][0-9]?)\.((1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[0-9][0-9]?)\.){2}(1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[1-9][0-9]?)

    时间:([01]?[0-9]|2[0-3])(:[0-5]?[0-9]){2}

    手机号码:1[3-9][0-9]{9}

    POSIX字符类(扩展)

    点击这里 点击这里
    [:digit:] 任何数字 [0-9]
    [:xdigit:] 任何十六进制数字 [0-9a-fA-F]
    [:alpha:] 任何字母 [a-zA-Z]
    [:lower:] 任何小写字母 [a-z]
    [:upper:] 任何大写字母 [A-Z]
    [:alnum:] 任何字母或数字 [a-zA-Z0-9]
    [:cntrl:] ASCII控制字符(ASCII 0~31 和 ASCII127)
    [:punct:] 不属于[:alnum:]和[:cntrl:]的任何字符
    [:blank:] 空格或制表符([\t ])
    [:space:] 任何空白字符,包括空格([\f\n\r\t\v ])
    [:print:] 任何可打印字符
    [:graph:] 同[:print:],但不包括空格

    随堂作业

    练习:head /etc/passwd > /opt/pass

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    sed

    1. 删除每行的第一个字符


    2. 在每行行首插入hello


    3. 删除每行的第二个字符


    4. 把每个数字用()括起来 如:(1)(2)


    5. 把每个数值用()括起来 如:(12)

    5.1 只扩数值,如 (12) (123)

    6. 删除每行的倒数第二个字符

    7. 交换每行的第一个和最后一个字符

    8. 删除刚好三个字符的单词

    9. 把ro或da替换成A

    思考:
    1. 删除每行的第一个单词(纯字母)


    八. 文本处理三剑客

    sed

    文本的操作

    sed是一个“非交互式的”面向字符流的编辑器。

    awk是一种负责模式匹配的程序设计语言,的典型示例是将数据转换成格式化的报表。

    sed stream editor

    是一种文本编辑器,默认情况下是不会修改原文件的。

    也是一种非交互式的编辑器

    工作原理

    一行一行处理的

    当从文件中读取一行后,首先放到模式空间中对该行进行相应的处理,处理完将结果输出到屏幕上。然后继续读取下一行内容,直到所有行都读取完毕,sed结束。

    语法:

    1
    sed [选项] '模式 动作' 文件...

    选项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -n:静默输出,关闭模式空间的输出,不会输出未匹配到的行 一般与p命令结合使用(只显示处理过的行)

    -e:允许进行多项编辑,也就是说对同一行做多次处理,可以做多点编辑
    -e '动作1' -e '动作2'
    '动作1;动作2'
    -r:允许使用扩展正则(支持扩展元字符)

    -i:直接作用于原文件 没有输出 在使用-i之前一定先不加-i看看效果
    -i.bak:修改原文件之前进行备份

    模式(处理具体哪些行):

    1. 空模式,表示所有的行都执行动作

    2. 以行号作为模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    1). 单独的行号
    如:1 就是处理第1行
    $ 处理最后一行

    2). 起始行,结束行
    如:1,5 处理第1到5行

    3). 起始行~步长
    每隔多少行操作一次
    如:2~2 从第2行开始,每隔1行 ~2 每次加2

    4). 起始位置,+N
    表示从起始位置开始,后面的N行都进行处理
    如:3,+5 处理3-8行
    1. 以正则作为模式
    1
    2
    3
    4
    5
    6
    1). /正则表达式/
    如:/^root/

    2). /正则表达式1/,/正则表达式2/
    表示从第一次匹配到正则表达式1开始到第一次匹配到正则表达式2之间的所有行
    如:/^bin/,/sh$/

    动作—–处理命令:

    ! 非 : 放在命令(动作)前面表示取反 (以行为单位)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    1. d 删除 delete 

    2. p 打印 print

    3. r 读取 read // -n p //vim里面的读取文件 1 r /root/a.txt //读取新行内容到第1行的下面

    4. w 写 write //vim里面的另存为到其他文件 1 w /root/a.txt

    5. a 追加 在匹配的行下面插入内容 append //vim p

    6. i 插入 在匹配行的上一行插入内容 insert //vim P

    7. c 修改 本行替换 change

    8. y 转换的命令,一一对应转换 // 类似 tr -t

    9. n 处理下一行 next (跳过)

    10. q 退出,不会再向模式空间读入新的行 quit

    11. s 查找替换

    ​ ‘模式 s/旧的内容(正则表达式)/替换内容(新的内容)/[修饰符]’

    ​ 修饰符:

    ​ g:全局替换

    ​ n:n为数字,1-512 替换第n个匹配到的内容

    ​ p:打印 -n

    ​ w:把处理过的行写入到另一个文件

    -———————————————————————-

    \u upper 大写字母

    \l lower 小写字母

    将文件中所有小写字母替换成大写字母:

    1
    # sed 's/[a-z]/\u&/g' /tmp/pass

    将文件中所有大写字母替换成小写字母

    1
    # sed 's/[A-Z]/\l&/g' /tmp/pass 
    随堂练习
    1. 从以root开头的行,到以login结尾的行,将sbin替换为bin (/etc/passwd文件或其一部分)
    1

    1. 将格式为2024/09/17的日期,替换为2024; 09; 17这样的格式(注意分号后面有空格)
    1

    1. 将pass文件每行打印3次
    1

    1. 只打印pass文件的第1行和第3行
    1

    1. 删除pass文件的第一行和最后一行
    1

    1. 删除pass文件中所有的数字
    1

    1. 将文件中所有的root单词(仅root,不操作rooter这样的单词)替换为ROOT
    1

    1. 在文件的第一行插入information
    1

    1. 将含有root的整行的内容替换为ROOT
    1

    1. 将文件中所有的a或b或c替换为大写字母
    1

    1. 用sed分别实现head -1和tail -1的功能
    1

    模式空间和保留空间(扩展)

    模式空间:

    用于处理文本行,最大保存8192字节

    保留空间:

    用于保留文本行,最大保存8192字节,默认有一个空行

    置换命令

    h:将模式空间的内容复制到保留空间 —— 覆盖模式

    H:将模式空间的内容追加到保留空间 —— 追加模式

    g:将保留空间的内容复制到模式空间 —— 覆盖模式

    G:将保留空间的内容追加到模式空间 —— 追加模式

    x:将模式空间和保留空间中的内容进行交换

    案例:

    1.在每行下面添加一个空行

    1

    2.将第一行移动到最后一行

    1

    3.移动第一行到第二行下面

    1

    4.将第一行复制到每个偶数行下面

    1

    1. 移动第一行到第三行下面
    1

    6.用sed实现tac

    1

    扩展:

    控制流

    ! 命令取反 例: 1!d 删除第一行以外的行

    {} 命令组合 命令用分号分隔 {1h;G} 可以理解为 -e 参数的另一种写法

    = 打印行号(输入行的号码,而非处理的次数行号) 例如: sed -n ‘2{=;p}’ infile

    n 读入下一行到模式空间 例:’4{n;d}’ 删除第5行

    N 追加下一行到模式空间.

    P 输出多行模式空间的第一部分,直到第一个嵌入的换行符为止。在执行完脚本的最后一个命令之后,模式空间的内容自动输出。P命令经常出现在N命令之后和D命令之前。

    D 删除模式空间中直到第一个换行符的内容。它不会导致读入新的输入行,相反,它返回到脚本的顶端,将这些指令应用与模式空间剩余的内容。

    这三个命令能建立一个输入. 输出循环,用来维护两行模式空间,但是一次只输出一行。

    这个循环的目的是只输出模式空间的第一行,然后返回到脚本的顶端将所有的命令应用于模式空间的第二行。没有这个循环,当执行脚本中的最后一个命令时,模式空间中的这两行都将被输出。

    删除文件倒数第二行

    # sed ‘N;$!P;D’ a.txt

    删除文件最后两行

    # sed ‘N;$!P;$!D;$d’ a.txt

    awk

    awk 文本编辑器. 也是一种非交互式的编辑器

    一种编程语言

    功能:

    对文本数据进行汇总和处理,是一个报告生成器,能够对数据进行排版printf

    工作模式

    1. 行工作模式,读入一行文件,存在“$0”里

    2. 使用内置变量FS(字段分隔符)分割一行,存到$1-$100

    3. 输出时也是用内置变量OFS,输出该行

    与sed主要异同:

    相同点:

    1.他们的语法基本相同

    2.他们都是流编辑器,工作方式都是读入一行,处理动作,输出结果

    3.他们都可以使用正则表达式作模式匹配

    4.他们都允许使用脚本

    不同点:

    1.sed主要处理行,awk主要处理列

    2.sed处理动作必须与文件内容相关,awk处理动作可以与文件内容无关,并且awk的动作要用{}括起来.

    1. awk的语法
    1
    awk [选项] '模式{动作...}' [文件列表]...

    选项

    1
    -F  指定分隔符   //默认是用空格为分隔符
    2. 简单使用

    //匹配含有root的行,并以 : 号为分隔符,打印 1 3 4 列

    1
    2
    3
    # awk -F: '/root/{print $1,$3,$4}' /tmp/pass 
    1 root 0 0
    10 operator 11 0
    3. awk的模式匹配

    1)空模式:也就是每一行都处理的模式

    2)以行为单位

    1
    2
    3
    a. 匹配行     NR==5					-> 第5行

    b. 匹配范围 NR >=3 && NR <= 8 -> 3 ~ 8行

    3)正则表达式

    注意:awk****是不支持使用行号定址的

    1
    /^root/     -> 匹配以 root开头的行
    4. 基本操作

    1)设定输入分隔符 -F

    分隔符可以是字母. 数字. 符号和空白

    可以同时指定多个分隔符

    默认是空白

    ——单个字符做分隔符

    ——复合分隔符,多个字符组成的分隔符 比如: :/

    ——同时指定多个分隔符 如 (空格)和/都是分隔符 # df -h |awk -F “ +|%” ‘//$/{print $5}’

    ——每个字符都是一个字段 即以空为分隔符 # echo hello |awk -F “” ‘{print $1,$2}’

    2)awk输出:

    (1) print

    打印内容可以是文件中内容. 也可以和文件无关

    例:

    1》打印passwd文件中的用户名及其shell

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

    2》对1》中的输出进行格式化,要求输出结果:用户的shell是shell名

    ​ root用户的shell是/bin/bash

    ​ bin用户的shell是/sbin/nologin

    1
    # awk -F ":" '{print $1" 用户的shell是 "$7}' /etc/passwd

    print要点:

    a. 各个输出字段之间用逗号分隔,而输出时默认以空格分隔

    b. print后面如果不指定字段,那么就会打印一整行 =~ print $0

    c. print输出时默认是有换行符的

    练习:

    1. 在每行下面打印一个空行
    1
    awk '{print $0"\n"}' /opt/pass
    1. ifconfig和awk结合,取出IP地址
    1
    ifconfig ens33 | awk '/netmask/{print $2}'
    1. df和awk结合,取出根分区已用空间的百分比数值(去掉%)
    1
    df -Th | awk '/\/$/{print $6}' | awk -F"%" '{print $1}'

    (2) printf —— 可以格式化输出,默认没有换行

    使用格式:

    printf “format”,item1,item2,… …,itemn

    format的指示符都是以%开头的,后面跟一个字符表示不同的数据类型,如:

    %s:表示是字符串

    %d:表示十进制整数

    %f:表示浮点数,也就是小数

    %%:表示%本身

    %x:表示十六进制数

    %o:表示八进制数

    %c :表示字符

    修饰符: N(数字): 表示显示宽度 %5d

    -:左对齐,默认是右对齐 %-5s

    printf要点:

    a. 与print不同的是,printf需要指定格式

    b. 格式(format)是用来指定后面的每个item的输出格式

    c. printf默认不会自动打印换行符,需要时候手动添加”\n”

    d. printf默认没有输出分隔符

    例:

    格式化输出passwd文件的用户名,UID,GID三列

    1
    # awk -F ":" '{printf "%-20s %-5s %s\n",$1,$3,$4}' /etc/passwd

    3)awk的操作符

    (1)算数运算符

    -x:表示负数 +x 或 (x):表示正数

    x+y x-y x*y x/y x%y x**y x^y (x的y次幂)

    (2)关系运算符

    a. 数值之间的关系运算符

    > < >= <= == !=

    b. 字符串之间的关系运算符

    == != ~ !~

    $1==”root”

    $1!=”root”

    x ~ /y/ —— 匹配正则

    x !~ /y/ —— 不匹配正则

    随堂练习

    1. 打印/etc/fstab中含有boot的行
    1

    1. 打印/etc/passwd文件中UID为14的用户的用户名. uid以及家目录
    1

    1. 打印用户的shell为可登录shell的用户名及shell
    1

    (3)逻辑运算符

    && NR >= 4 && NR <=8

    || NR == 4 || NR ==8

    (4)赋值运算符(详见变量)

    = += -= *= /= %= **= ^=

    ++ –

    练习: 以 /etc/passwd 例子

    1. 打印uid在59到99之间的用户名和uid(包含59,但不包含99)
    1

    1. 打印uid和gid不相同的用户的用户名,uid及gid
    1

    1. 交换passwd文件的第一个字段和第二个字段(以冒号为分隔)
    1

    1. 打印100以内能够被7整除以及包含7的数: 如7,14,17…
    1

    5. awk的变量
    1)内置变量

    (1) $0 表示一整行的内容

    (2) $1~$100

    $1:第一列

    $2:第二列

    … …

    $100:第100列

    (3) 与记录相关的变量

    FS(Field Separator):字段分隔符,默认是空白 默认 空格

    RS(Record Separator):记录分隔符,即行的分隔符 默认 \n

    OFS(Output):输出字段分隔符 (列的分隔符,默认是空格)

    ORS:输出记录分隔符 (行的分隔符)

    (4) 与数据相关的变量

    NR:记录数,awk所处理的记录的总数 NR在很多情况下可以看成行号

    FNR:当前文件所处理的记录数

    NF:当前行的字段数 ,列 ,

    $NF:当前行最后一个字段的值

    $(NF-1):当前行倒数第二个字段的值

    2)自定义变量

    命名:由字母. 数字. 下划线组成,不能以数字开头,不要使用关键字,最好见名知意

    变量名区分大小写,变量可以先定义再使用,也可以直接使用(整型)

    给变量赋值:

    变量名=数值

    变量名=”字符串” 注意:此处一定有引号

    取消变量:

    delete var_name

    变量的长度:

    length(var_name)

    6. 赋值运算

    a+=$2 等效于 a=a+$2 //先运算在赋值

    a+=5 等效于 a=a+5

    a++ 先赋值,再自增

    ++a 先自增,再赋值

    7. awk的特殊模式

    awk [选项] ‘BEGIN{动作}模式{动作}END{动作}’ 文件列表

    BEGIN{} :在读取文件之前就执行,并且只执行一次

    模式{}:这一部分可能会执行多次

    END{}:在处理完文本之后执行,并且只执行一次

    BEGIN:一般用于初始化分隔符. 定义变量. 定义数组. 打印表头等

    END:汇总数据 比如:打印总成绩. 打印平均成绩等等

    说明:

    BEGIN和END可以单独存在

    1)只有BEGIN,后面不需要加文件

    2)只有END 后面必须接文件,文件可以是空文件,也可以是有内容的文件

    8. 扩展: awk 内嵌 if for

    基本的If语句:if (condition 判断表达式) { # 条件满足时执行的代码 } //单分支

    例如1

    1
    # awk -F: ‘{if($1=="root"){print $1,$3}}’ /etc/passwd

    例如2

    1
    # awk -F: ‘BEGIN{i=0} {if($3>=1000){i++}}END{print i}’ /etc/passwd

    if (condition) { # 条件满足时执行的代码 } else { # 条件不满足时执行的代码 } //两分支

    例如1

    1
    # awk -F: ‘{if($3>=1000){i++}else{j++}} END{print i,j}' /etc/passwd

    基于数字范围的For循环:for (i = start; i <= end; i++) { # 循环执行的代码 }

    例如1

    1
    # awk 'BEGIN{for(i=0;i<=5;i++){print i}}'

    例如2

    1
    # awk 'BEGIN{sum=0;for(i=1;i<=100;i++){sum=sum+i};print sum}
    9. 扩展:awk数组

    数组:array

    awk数组为关联数组,即可以使用字符串作下标.

    常用于收集信息, 计算次数等

    数组的定义:

    1
    数组名[下标]=值

    数组长度:

    1
    length(数组名)	//统计数组有多少个元素

    数组的取消:

    1
    2
    3
    delete  数组名               # 取消整个数组

    delete 数组名[下标] # 取消数组中的一个元素

    数组的遍历: 使用for循环来遍历数组

    1
    2
    3
    语法:for (变量 in 数组名){ print 数组名[变量]}   

    此处变量的取值列表为数组的下标

    ​ 统计apache日志中每个IP的访问次数,取出前五名

    练习:

    ​ 1. 统计系统中每种shell的用户有几个?

    ​ 2. 统计服务器上tcp连接情况(即每种状态有多少个?) netstat -tan

    ​ 3. 使用awk实现tac的功能

    扩展:

    1. 有两个文件,内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # cat a.txt
    tom 20
    jerry 19
    lily 18

    # cat b.txt
    lily female
    tom male
    jerry male

    使用awk处理这两个文件,输入效果如下:

    1
    2
    3
    lily   18  female
    tom 20 male
    jerry 19 male
    1

    随堂练习
    1. 打印passwd文件的奇数行的行号和内容
    1

    1. 打印每行的倒数第3个字段
    1

    1. 从第一行开始,每隔三行打印一次(即打印1 4 7这样的行)
    1

    1. 统计UID和GID不相等的行数(不要用wc命令)
    1

    1. 统计/etc目录下所有普通文件的总大小(M)
    1
    2


    grep

    grep家族

    ========================================================

    grep: 在文件中全局查找指定的正则表达式,并打印所有包含该表达式的行

    egrep: 扩展的egrep,支持更多的正则表达式元字符 // grep -E

    九. expect 免交互

    expect 把交互式操作变成非交互式

    如何执行expect脚本

    1. expect 脚本名称

    2. chmod a+x 脚本名称

      ./脚本名称

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #vim  su.sh
    #!/usr/bin/expect

    #普通账户之间切换,切换完成之后再执行一些命令!
    set timeout 1
    spawn su - user2 #生成命令
    expect "Password:" #捕获关键字
    send "111111\n" #发送字符串
    expect "$"
    send "pwd\n"
    interact #执行完成后保持切换后的状态。如果没有这一句登录完成后会退出,而不是留在远程终端上。
    # 涉及 su ssh 等操作

    file://C:/Users/Administrator/AppData/Local/Temp/.YO9NU2/1.png

    非交互生成密钥对及传递公钥

    案例脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/bin/bash
    yum install expect -y &>/dev/null
    rm -rf /root/.ssh/{id_rsa,id_rsa.pub,known_hosts}

    /usr/bin/expect <<EOF
    set timeout 300
    spawn ssh-keygen
    expect "Enter file in which to save the key (/root/.ssh/id_rsa):"
    send "\n"
    expect "Enter passphrase (empty for no passphrase):"
    send "\n"
    expect "Enter same passphrase again:"
    send "\n"



    spawn ssh-copy-id 192.168.1.250
    expect {
    "yes/no" { send "yes\n"; exp_continue }
    "root@192.168.1.250's password:" { send "qfedu\n"}
    }

    expect eof
    EOF

    生成密钥对,单分支

    生成密钥对,多分支

    传递公钥

    image-20241218115854764

    完整shell脚本

    准备ip 用户名 密码文件

    1
    2
    3
    4
    # cat ip.txt 
    192.168.11.101 root 123
    192.168.11.102 root 123
    192.168.11.103 root 123
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #!/bin/bash
    [ -f /usr/bin/expect ] || yum install expect -y &> /dev/null

    # 生成公私钥
    expect <<EOF
    set timeout 2
    spawn ssh-keygen
    expect "(/root/.ssh/id_rsa):"
    send "\n"

    expect {
    "(empty for no passphrase):" {
    send "\n"
    expect "passphrase again:"
    send "\n"
    }

    "Overwrite (y/n)?" {
    send "n\n"
    }
    }
    expect eof
    EOF


    #传递公钥
    while read line
    do
    IP=`echo $line | awk '{print $1}'`
    UserName=`echo $line | awk '{print $2}'`
    Pass=`echo $line | awk '{print $3}'`
    expect <<EOF
    set timeout 1000
    spawn ssh-copy-id $UserName@$IP
    expect {
    "connecting (yes/no)?" {
    send "yes\n"
    exp_continue
    }
    "password:" {
    send "$Pass\n"
    }
    }
    expect eof
    EOF

    done < ip.txt