当前位置: 首页 > news >正文

小菜狗的云计算之旅,shell脚本语言的基本内容和用法

目录

前言

一、Shell脚本语言的基本结构

1、Shell脚本的用途:

2、 Shell脚本基本结构:

3、 创建Shell脚本过程

4、 脚本注释规范

5、第一个脚本

6、 执行脚本

7、脚本错误

二、Shell脚本语言的变量用法详解

1、变量

2、 变量类型

3、 Shell中变量命名法则

4、 变量定义和引用

5、 环境变量

6、只读变量

7、位置与预定义变量

8、 退出状态码变量

9、 展开命令行

9.1展开命令执行顺序

9.2 防止扩展

9.3 加引号来防止扩展

9.4 变量扩展

10、 脚本安全和set(暂时不用)

10.1 $-变量

10.2 set命令实现脚本安全

三、 Shell字符串详解

1、Shell字符串拼接

2、Shell字符串截取

3、汇总

4、Shell的格式化输出printf

4.1、语法格式:

4.2、常用格式替换符:

4.3、常用转义字符:

四、Shell脚本语言的运算

4.1 算数运算

4.2 Bash中的算术运算

4.2 实现算术运算

4.3 增强型赋值:

4.2 逻辑运算(了解,不用掌握)

4.3 短路运算(不用了解)

4.4 条件测试命令

4.4.1 条件测试命令及其语法

4.4.2 变量测试

4.4.3 文件测试表达式

4.4.4 字符串测试表达式

4.4.5 整数测试表达式

4.4.6 逻辑操作符

4.5 关于()与 { }

4.6 组合测试条件

4.6.1 第一种方式[ ]

4.6.2 第二种方式[[ ]]

4.7 使用read命令命令来接受输入

4.7.1 语法结构

4.7.2 选项:

五、bash的配置文件

5.1 按生效范围划分为两类

5.1.1 全局配置:

5.1.2 个人配置

5.2 shell登录的两种方式分类

5.2.1 交互式登录

5.2.2 非交互式登录

5.3 按功能划分分类

5.3.1 Profile类

5.3.2 Bashrc类

5.4 编辑配置文件生效

六、流程控制

6.1 条件选择

6.1.1 选择执行if语句

6.1.1.1 单分支

6.1.1.2 双分支

6.1.1.3 多分支

6.1.2条件判断case语句

6.2 循环

6.2.1 循环执行介绍

6.2.2 for循环

6.2.3 while循环

6.2.4 until循环(不用)

6.2.5 循环控制语句continue

6.2.6 循环控制语句break

6.2.7 shift与select技术

七、Shell中的数组

7.1 Shell数组的概念

7.2 Shell数组的定义

7.2.1 数组的基本定义

7.2.2 采用键值对的形式赋值

7.2.3 通过分别定义数组变量的方法来定义

7.2.4 动态定义数组数量

7.3 Shell数组的打印

7.4 Shell数组的赋值

7.5 Shell数组的拼接合并

7.6 Shell删除数组元素

7.7 获取数组某范围的元素

7.8 数组元素的替换

7.9关联数组

7.9.1 定义关联数组

7.9.2 列出数组索引

7.9.2 获取所有键值对

7.10 mapfile命令

7.10.1 mapfile命令介绍

7.10.2 mapfile语法结构

7.10.3 使用示例

八、Shell中的函数

8.1 Shell函数的定义

8.2 Shell函数的调用

8.3 Shell函数详解

九、Shell高级

9.1 echo 带颜色输出

9.1.1 echo命令介绍

9.1.2 使用echo达到输出有颜色字体的效果

9.2 Shell的八大扩展功能

9.2.1 花括号

9.2.2 波浪号

9.2.3 变量替换

9.2.4 命令替换

9.2.5 算数替换

9.2.6 进程替换

9.2.7 单词切割

9.2.8 路径替换

9.3 Shell中的expect

9.3.1 expect介绍

9.3.2 expect命令

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

9.3.3.1.expect的单分支语法

9.3.3.2.expect的多分支语法

9.3.4 通过实际脚本来理解expect

9.3.5 实例

9.4 Shell中的信号捕捉trap(不用了解)

9.4.1 Linux信号

9.4.2 trap命令

十、Shell编程之正则表达式

1、正则表达式

1.正则表达式概述

2.正则表达式的分类

3.正则表达式的使用

4.常见例题

电话例题

电子邮箱例题

2、Shell常用命令

1.sort命令——排序工具

2.uniq命令——去重工具

3.tr命令——修改工具

4.cut命令——截取工具

5.split命令——拆分工具

6.paste命令——合并工具

7.eval命令——两次扫描工具(不需要)

十一、sed编辑器

1.sed编辑器概念

2.sed编辑器工作流程

处理大文件较卡问题

3.sed编辑器用法

命令格式-n 只显示满足 条件的输出结果

常用选项

常用操作

常用方式

打印内容

删除行内容 不能加-n 因为永远看不到东西

替换内容

插入内容

十二、awk编辑器

1.概念

2.工作原理

3.用法

命令格式

常用选项

常见的内建变量

常用方式

按行输出文本

按字段输出文本

1. 变量赋值

2. 输出控制

3. 命令参数选择

通过管道、双引号调用 Shell命令

按数组输出文本以及统计

十三、find命令详解

1、find基本语法

2、常用选项

2.1 示例:查找大于1MB的文件

2.2 示例:查找最近7天内修改过的文件

2.3 示例:查找属主为user1的文件

2.4 示例:执行自定义命令

3、高级用法

3.1 使用逻辑操作符

3.1.1 示例:查找大于1MB且是普通文件的文件

3.1.2 示例:查找修改时间在7天之前或文件名以.bak结尾的文件

3.2 搜索多个路径

3.2.1 示例:查找多个目录下的所有文件

3.3 将find结果用于其他命令

3.3.1 示例:在搜索结果中执行grep

3.3.2 示例:将搜索结果输出到文件

4、find命令的性能优化

十四、bc命令

1、语法

2、选项

3、参数

4、实例

前言

编程语言分类

静态和动态语言:

  • 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如Java语言、C语言

  • 动态编译语言:不事先声明,可随时改变类型,如Shell语言、python语言、javascript语言、php语言

强类型和弱类型语言:

  • 强类型语言:不同类型数据操作,必须经过强制转换成同一类型才能运算,如java,C#(静态编译语言大部分是强类型语言)

  • 弱类型语言:语言的运行时会隐式地做数据类型转换。无需指定类型,默认均为字符型;参与计算会自动进行隐式类型转换;变量无需事先定义可直接调用。如Shell语言,php,JavaScript

脚本检查工具:

yum install -y epel-release​yum install -y ShellCheck

一、Shell脚本语言的基本结构

1、Shell脚本的用途:

  • 自动化常用命令

  • 执行系统管理和故障排除

  • 创建简单的应用程序

  • 处理文本或文件

2、 Shell脚本基本结构:

Shell脚本编程:是基于过程式(面向过程式,根据时间值往下走),解释执行的语言

编程语言的基本结构:

  • 各种系统命令的组合

  • 数据存储:变量,数组

  • 表达式:a+b

  • 控制语句:if、case、for、while

shell脚本:包含一些命令或声明,并符合一定格式的文本文件

格式要求:首行执行shebang机制

#声明后续语句是通过那种语言写的#!/bin/bash#!/usr/bin/python#!/usr/bin/perl

3、 创建Shell脚本过程

  1. 使用vim创建文本文件,第一行必须包括shell声明序列:

    #!/bin/bash
  2. 加执行权限,给予执行权限,在命令行上指定脚本的绝对或相对路径

     [root@localhost ~]# chmod +x shellScript/hello.sh 
  3. 运行脚本,直接运行解释器,将脚本作为解释器程序的参数运行。

     [root@localhost ~]# /root/shellScript/hello.sh 

4、 脚本注释规范

  • 第一行一般为调用使用的语言

  • 程序名,避免更改文件名为无法找到正确的文件

  • 版本号

  • 更改后的时间

  • 作者相关信息

  • 该程序的作用,及注意事项

  • 最后是各版本的更新简要说明

5、第一个脚本

 [root@localhost ~]# cat shell/hostname.sh #!/bin/bashecho "My hostname is `hostname`"echo "Time is `date +'%F %T'`"[root@localhost ~]# chmod +x shell/hostname.sh[root@localhost ~]# shell/hostname.sh

6、 执行脚本

1、增加执行权限,执行脚本时会创建一个子shell,不影响现有的shell环境

 chmod +x sh./sh/root/shellScript/sh

2、使用 . 或者source,执行脚本时不会创建一个子shell,会影响现有的shell环境

 source sh. sh  (尽量减少使用  )

注意:尽量不要使用该方式执行脚本!!!

课后实例:备份脚本

 [root@localhost ~]# mkdir /backup/[root@localhost ~]# vim shell/backup.sh[root@localhost ~]# cat shell/backup.sh #!/bin/bashecho -e "\033[1;32m\033[0m"sleep 2cp -av /etc/ /backup/etc`date +%F`/echo -e "\033[1;32mBackup is finished\033[0m"[root@localhost ~]# chmod +x shell/backup.sh [root@localhost ~]# ./shell/backup.sh Starting backup.....Backup is finished

案列:

1、创建一个脚本列出所有的普通文件

 [root@iptables ~]# vim script1.sh#!/bin/bash#auth:Hou#Use:列出home中所有的普通文件#Date:2505-06-04#version:1.0find /home -type f

2、增加执行权限

 [root@iptables ~]# chmod +x script1.sh[root@iptables ~]# ls -l总用量 16drwxr-xr-x. 2 root root    6 5月  21 17:38 公共drwxr-xr-x. 2 root root    6 5月  21 17:38 模板drwxr-xr-x. 2 root root    6 5月  21 17:38 视频drwxr-xr-x. 2 root root    6 5月  21 17:38 图片drwxr-xr-x. 2 root root    6 5月  21 17:38 文档drwxr-xr-x. 2 root root    6 5月  21 17:38 下载drwxr-xr-x. 2 root root    6 5月  21 17:38 音乐drwxr-xr-x. 2 root root    6 5月  21 17:38 桌面-rw-------. 1 root root 1192 5月  21 17:07 anaconda-ks.cfg-rw-r--r--. 1 root root 1419 5月  21 17:37 initial-setup-ks.cfg-rw-r--r--. 1 root root  512 5月  24 06:42 lv1.txt-rwxr-xr-x. 1 root root  117 6月   4 06:24 script1.sh

3、执行脚本

 #当前目录下[root@iptables ~]# ./script1.sh#绝对路径[root@iptables opt]# /root/script1.sh#改变当前shell环境[root@iptables ~]# . script1.sh[root@iptables ~]# source script1.sh

4、没有权限时,只有 . 和source 和 bash可以执行,写相对路径和绝对路时必须得有执行权限

 [root@iptables ~]# chmod -x script1.sh-rw-r--r--. 1 root root  121 6月   4 06:48 script1.sh[root@iptables ~]# ./script1.sh-bash: ./script1.sh: 权限不够[root@iptables ~]# /root/script1.sh-bash: /root/script1.sh: 权限不够[root@iptables ~]# . script1.sh/home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history[root@iptables ~]# source script1.sh/home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history

5、当没有执行权限时,. 和 source可以切换脚本里面的运行环境,切换到家目录

 [root@iptables ~]# vim script1.sh#!/bin/bash#auth:Hou#Use:列出系统中所有的普通文件#Date:2505-06-04#version:1.0cd /homefind /home -type f​[root@iptables ~]# bash script1.sh /home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history[root@iptables ~]# . script1.sh /home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history[root@iptables ~]# source script1.sh/home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history

6、. 和source会沿用当前副shell环境执行,bash和./会开启子shell 对系统影响小

 [root@iptables ~]# vim script1.sh#!/bin/bash#auth:Hou#Use:列出系统中所有的普通文件#Date:2505-06-04#version:1.0cd /home./script1.shfind /home -type f​[root@iptables ~]# chmod +x script1.sh 开启子shell[root@iptables ~]# ./script1.sh ./script1.sh:行7: ./script1.sh: 没有那个文件或目录/home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history开启新shell[root@iptables ~]# bash script1.sh script1.sh:行7: ./script1.sh: 没有那个文件或目录/home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history​现在是什么shell环境就是什么环境 开起副shell[root@iptables ~]# . script1.sh -bash: ./script1.sh: 没有那个文件或目录/home/hou/.bash_logout/home/hou/.bash_profile/home/hou/.bashrc/home/hou/.bash_history[root@iptables home]# 

7、脚本错误

  • 语法错误,会导致后续的命令不继续执行,可以用bash -n shellname检查错误

  • 命令错误,后续的命令还会继续,可以使用bash -x shellname检查

  • 逻辑错误,只能使用bash -x进行观察

二、Shell脚本语言的变量用法详解

1、变量

变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据

2、 变量类型

变量类型:

  • 内置变量:如PS1,PATH,UID,HOSTNAME,HISTSIZE

  • 用户自定义变量

 [root@iptables ~]# a=
  • 预定义变量

[root@iptables ~]# echo $?
0
  • 位置变量(在脚本位置跟几个参数,第一个参数叫第一个位置变量)

[root@iptables ~]# ls /etc/ /home/
/etc/:
accountsservice             hosts                      printcap
adjtime                     hp                         profile
/home/:
hou

不同的变量存放的数据不同,决定了以下:

  1. 数据存储方式

  2. 参与的计算

  3. 表示的数据范围

变量数据类型:

  • 字符串

  • 数值:整型,浮点型(小数)、bash不支持浮点数(1.3)

3、 Shell中变量命名法则

  • 不能使用程序中的保留字,如:if,for

  • 只能使用数字,字母及下划线,且不能以 数字开头

  • 见名思意,用英文名字,并体现真正含义

  • 统一命名规则:驼峰命名法(即能体现出当前变量的含义,又能满足当前的规范)

[root@iptables ~]# userName=list
  • 全局变量名大写

[root@iptables ~]# env
SSH_CONNECTION=192.168.162.1 62320 192.168.162.135 22
LANG=zh_CN.UTF-8
  • 局部变量小写

  • 函数名小写

4、 变量定义和引用

变量的生效范围(变量作用域)

  • 普通变量:生效范围为当前shell进程;对当前shell之外的其他shell进程,包括当前shell的子shell进程均无效(局部变量)

[root@iptables ~]# a=1
[root@iptables ~]# bash
[root@iptables ~]# echo $a[root@iptables ~]# exit
exit
[root@iptables ~]# echo $a
1
  • 环境变量:生效范围为当前shell进程及其子进程(全局变量)

[root@iptables ~]# export a=1[root@iptables ~]# echo $a
1
[root@iptables ~]# bash
[root@iptables ~]# echo $a
1
[root@iptables ~]# exit 
exit
[root@iptables ~]# echo $a
1
  • 本地变量:生效范围为当前shell进程中某代码片段,通常指函数

变量赋值:

name="value"value可以是以下多种类型
直接字符串:name='root'
变量引用:name="$USER"
命令应用:name=`command`  || name=$(command)
通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/[root@iptables ~]# rs2=./*
[root@iptables ~]# echo $rs2
./公共 ./模板 ./视频 ./图片 ./文档 ./下载 ./音乐 ./桌面 ./anaconda-ks.cfg ./initial-setup-ks.cfg ./lv1.txt ./script1.sh

注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存。

变量引用:

$name
${name}

弱引用和强引用:

  • “$name”:弱引用,其中的变量引用会被替换成为变量值

[root@iptables ~]# str1=hou
[root@iptables ~]# str2='str1'
[root@iptables ~]# echo $str2
str1
  • ‘$name’:强引用,其中的变量引用不会被替换成变量值,而保持原字符串

[root@iptables ~]# str3="#str1"
[root@iptables ~]# echo $str3
hou
  • 数字一定不能引

  • 反引号,可以直接输出命令内容

[root@iptables ~]# result1=ls
[root@iptables ~]# echo $result1
ls
[root@iptables ~]# result1='ls'
[root@iptables ~]# echo $result1
ls
[root@iptables ~]# result1="ls"
[root@iptables ~]# echo $result1
ls
[root@iptables ~]# result1=`ls`
[root@iptables ~]# echo $result1
公共 模板 视频 图片 文档 下载 音乐 桌面 anaconda-ks.cfg initial-setup-ks.cfg lv1.txt script1.sh

实例:

[root@localhost ~]# name='Mike'
[root@localhost ~]# NAME="$USER"
[root@localhost ~]# hostname=`hostname`
[root@localhost ~]# echo "My name is $name"
My name is Mike
[root@localhost ~]# echo "My name is $NAME"
My name is root
[root@localhost ~]# echo "My hostname is $hostname"
My hostname is localhost.localdomain
[root@localhost ~]# [root@localhost ~]# NUM=`seq 10`
[root@localhost ~]# echo $NUM
1 2 3 4 5 6 7 8 9 10
[root@localhost ~]# echo "$NUM"
1
2
3
4
5
6
7
8
9
10
[root@localhost ~]#

查看已定义的所有变量:

[root@localhost ~]#set | grep name[root@iptables ~]# set | grep rs2
rs2='./*'
[root@iptables ~]# set | grep str1
str1=hou
str2=str1
str3=str1#查看所有变量的数量
[root@iptables ~]# set | wc -l
3056

删除变量

[root@localhost ~]#unset shellname1 shellname2
//实例:
[root@localhost ~]# echo $name 
Mike
[root@localhost ~]# unset name
[root@localhost ~]# echo $name [root@localhost ~]# 
[root@iptables ~]# unset str1
[root@iptables ~]# echo $str1

实例:"{}"的使用

[root@localhost ~]# NAME=mage
[root@localhost ~]# AGE=20
[root@localhost ~]# echo $NAME
mage
[root@localhost ~]# echo $AGE
20
[root@localhost ~]# echo $NAME $AGE
mage 20
[root@localhost ~]# echo $NAME_$AGE
20
[root@localhost ~]# echo ${NAME}_$AGE
mage_20
[root@localhost ~]# 
root@iptables ~]# str1=hou
[root@iptables ~]# echo $str2
str1
[root@iptables ~]# echo $str3
str1
[root@iptables ~]# str4=$str144444
[root@iptables ~]# echo $str4[root@iptables ~]# str4=${str1}44444
[root@iptables ~]# echo $str4
hou44444

显示系统信息

[root@localhost ~]# vim shell/OS.sh  
#!/bin/bash
RED="\E[1;31m"
GREEB="\E[1;32m"
END="\E[0m"
echo -e "\E[1;32m----------Host systeminfo----------$END"
echo -e "HOSTNAME: $RED `hostname`$END"
echo -e "IPADDR:   $RED `ifconfig ens160 | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1` $END"
echo -e "OSVERSION: $RED `cat /etc/redhat-release`$END"
echo -e "KERNEL:   $RED `uname -r`$END"
echo -e "CPU:       $RED `lscpu | grep "型号名称:" | tr -s ' ' ' '|cut -d ' ' -f 2-5` $END"
echo -e "MEMORY:   $RED `free -h | grep Mem | tr -s ' ' ' '|cut -d ' ' -f 4` $END"
echo -e "DISK:     $RED `lsblk | grep '^sda' | tr -s ' ' | cut -d ' ' -f 4` $END"
echo -e "\E[1;32m----------       END     ----------$END"
[root@localhost ~]# chmod +x shell/OS.sh 
[root@localhost shellScript]# ./os.sh 
----------Host systeminfo----------
HOSTNAME:  localhost.localdomain
IPADDR:    19168.115.213 
OSVERSION:  CentOS Linux release 7.9.2009 (Core)
KERNEL:    3.10.0-1160.el7.x86_64
CPU:        11th Gen Intel(R) Core(TM) 
MEMORY:    1.0G 
DISK:      200G 
----------       END     ----------

利用变量实现动态命令

[root@localhost ~]# CMD=hostname
[root@localhost ~]# $CMD
localhost.localdomain
[root@localhost ~]# 

5、 环境变量

环境变量:

  • 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量。

  • 一旦子进程修改了从父进程继承的变量,将会传递新的值给孙子进程

  • 一般只在配置文件中使用,在脚本中较少使用

课程引入:普通变量生效的范围与环境变量生效的范围

[root@localhost ~]# vim shell/father.sh 
#!/bin/bash
NAME=father
echo "father.sh:NAME=$NAME"
echo "fatther is PID=$BASHPID"
shell/son.sh[root@localhost ~]#vim shell/son.sh
#!/bin/bash
echo "son.sh:NAME=$NAME"
NAME=son
echo "son.sh:NAME=$NAME"
echo "son.sh PID is $BASHPID"
echo "son.sh father pid is $PPID"
sleep 100[root@localhost ~]#chmod -R +x shell/*[root@localhost ~]# ./shell/father.sh 
father.sh:NAME=father
fatther is PID=12053
son.sh:NAME=
son.sh:NAME=son
son.sh PID is 12054
son.sh father pid is 12053#子进程无法使用父进程的变量,需要自己赋值

变量声明和赋值:

export name=VALUE
declare -x name =VALUE
#或者先赋值再声明~value可以是以下多种类型
直接字符串:name='root'
变量引用:name="$USER"
命令应用:name=`command`  || name=$(command)
通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/

declare命令详解:(了解)

declare 为 shell 指令,在第一种语法中可用来声明变量并设置变量的属性([rix]即为变量的属性),在第二种语法中可用来显示 shell 函数。若不加上任何参数,则会显示全部的 shell 变量与函数(与执行 set 指令的效果相同)

语法

declare [+/-][rxi][变量名称=设置值] 或 declare -f

参数说明

  • +/-  "-“可用来指定变量的属性,”+"则是取消变量所设的属性。

  • -f   仅显示函数

  • r   将变量声明为只读变量。注意,一旦设置为只读变量,既不能修改变量的值也不能删除变量,甚至不能通过+r取消只 读属性

  • x   指定的变量会成为环境变量,可供shell以外的程序来使用

  • i   将变量声明为整数型(integer)

  • p 显示指定变量的被声明类型。

实例

1.声明整数型变量

#!/bin/bash
declare -i ab //声明整数型变量
ab=56 //改变变量内容
echo $ab //显示变量内容--->
56

改变变量属性

#!/bin/bash
#声明整数型变量
declare -i ef 
ef=1
echo $ef
#变量赋值,赋予文本值
ef="wer"
echo $ef
#取消变量属性
declare +i ef
ef="wer"
echo $ef

3.设置变量只读

#!/bin/bash
declare -r ab #设置变量为只读
ab=88 #改变变量内容
echo $ab #显示变量内容--->
declare.sh:行3: ab: 只读变量

4.声明数组变量

#!/bin/bash
#声明数组变量
declare -a cd
cd[0]=a
cd[1]=b
cd[2]=c
#输出数组的指定内容
echo ${cd[1]}
#显示整个数组变量内容
echo ${cd[@]}

5.显示函数

#!/bin/bash
#声明函数
declare -f 
function command_not_found_handle(){if [ -x /usr/lib/command_not_found_handle ];then/usr/bin/python /usr/lib/command_not_found_handle -- $1;return $?;elseif [ -x /usr/share/command_not_found_handle ];then/usr/bin/python /usr/share/command_not_found_handle --$1;return $?;elsereturn 127;fi;fi;
}

变量引用:

$name
${name}

完善课程导入:

[root@localhost ~]# vim shell/father.sh 
#!/bin/bash
NAME=father
export NAME
echo "father.sh:NAME=$NAME"
echo "fatther is PID=$BASHPID"
shell/son.sh[root@localhost ~]#vim shell/son.sh
#!/bin/bash
echo "son.sh:NAME=$NAME"
NAME=son
echo "son.sh:NAME=$NAME"
echo "son.sh PID is $BASHPID"
echo "son.sh father pid is $PPID"
sleep 100[root@localhost ~]#chmod -R +x shell/*[root@localhost ~]# ./shell/father.sh 
father.sh:NAME=father
fatther is PID=12053
son.sh:NAME=father
son.sh:NAME=son
son.sh PID is 12054
son.sh father pid is 12053
[root@localhost ~]# ./shell/father.sh 
father.sh:NAME=father
fatther is PID=12142
son.sh:NAME=father
son.sh:NAME=son
son.sh PID is 12143
son.sh father pid is 12142#父进程定义了一个环境变量,在子进程上可以进行调用#父进程无法使用子进程的变量#子进程自己定义了一个同名变量,就覆盖环境变量

显示所有环境变量:

[root@localhost ~]# env
[root@localhost ~]# printenv
[root@localhost ~]# export
[root@localhost ~]# declare -x

删除变量

[root@localhost ~]#unset shellname1 shellname2

Bash内建的环境变量

PATH
SHELL
USER
UID
PWD
SHLVL #shell的嵌套层数,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_      #下划线,表示前一命令的最后一个参数

6、只读变量

只读变量:只能声明定义,但后续不能修改和删除

声明只读变量:

readonly name
declare -r name

查看只读变量:

readonly [-p]
declare -r

7、位置与预定义变量

位置变量:在Bash Shell中内置的变量,在脚本代码中调用命令行传递给脚本的参数

$1,$2,... 对应第一个,第二个等参数,shift[n]换位置,最多9个(可以多个)要括起来{10}{11}
#预定义变量
$0  命令本身,包括路径
$*  传递给脚本的所有参数,全部参数合成一个字符串
$@  传递给脚本的所有参数,每个参数为独立字符串
$#  传递给脚本的参数的个数
$?	上个命令的退出状态,或函数的返回值(0代表成功,非0数字一定不成功)
$$	当前shell进程ID。对于Shell脚本,就是这些脚本所在的进程ID注意:$@,$*只有被双引号括起来的时候才会有差异
清空所有位置变量set --
//写在脚本内部

实例演示1:

[root@localhost ~]# vim shell/ARG.sh
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"
echo "The number of are is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"
[root@localhost ~]# chmod +x shell/ARG.sh 
[root@localhost ~]# shell/ARG.sh {1..10}
1st arg is 1
2st arg is 2
3st arg is 3
4st arg is 4
The number of are is 10
All args are 1 2 3 4 5 6 7 8 9 10
All args are 1 2 3 4 5 6 7 8 9 10
The scriptname is ARG.sh
[root@localhost ~]# 

实例演示2:编写一个移动文件脚本

[root@localhost ~]# vim shell/move.sh
#!/bin/bash
WANGING_COLOR="echo -e \E[1;31m"
END="\E[0m"
DIR=/tmp/`date +%F_%H-%M-%S`
mkdir $DIR
mv $*  $DIR
${WANGING_COLOR} MOVE $* to $DIR $END
[root@localhost ~]# chmod +x shell/move.sh
[root@localhost ~]# touch {a,b,c}
[root@localhost ~]# ls
a  anaconda-ks.cfg  b  c  shell
[root@localhost ~]# shell/move.sh a b cMOVE a b c to /tmp/2022-08-16_10-07-55 
[root@localhost ~]# tree /tmp/
/tmp/
└── 2022-08-16_10-07-55├── a├── b└── c1 directory, 3 files
[root@localhost ~]# 
[root@iptables ~]# vim script1.sh 
#!/bin/bash
#auth:Hou
#Use:列出系统中所有的普通文件
#Date:2505-06-04
#version:1.0
echo $1 $2 $3 $4 $5 $6 $6 $7 $8 $9
echo $0
echo $*
echo $@
echo $#
echo $?
echo $$
[root@iptables ~]# ./script1.sh ./script1.sh0
0
127304
[root@iptables ~]# ./script1.sh  a b c d e f g h i
a b c d e f f g h i
./script1.sh
a b c d e f g h i
a b c d e f g h i
9
0
127415#$@,$*只有被双引号括起来的时候才会有差异
[root@iptables ~]# vim script1.sh 
#!/bin/bash
#auth:Hou
#Use:列出系统中所有的普通文件
#Date:2505-06-04
#version:1.0
echo $1 $2 $3 $4 $5 $6 $6 $7 $8 $9
echo $0
for i in "$*"
doecho $i
done
for i in "$@"
doecho $i
done
echo $#
echo $?
echo $$
[root@iptables ~]# ./script1.sh  a b c d e f g h i
a b c d e f f g h i
./script1.sh
a b c d e f g h i
a
b
c
d
e
f
g
h
i
9
0
129620
[root@iptables ~]# vim script1.sh 
#!/bin/bash
#auth:Hou
#Use:列出系统中所有的普通文件
#Date:2505-06-04
#version:1.0
set --
echo $1 $2 $3 $4 $5 $6 $6 $7 $8 $9
echo $0
for i in "$*"
doecho $i
done
for i in "$@"
doecho $i
done
echo $#
echo $?
[root@iptables ~]# ./script1.sh  a b c d e f g h i./script1.sh0
0
130355

8、 退出状态码变量

进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败,的取值范围为[0,255]

$?的值为0 代表成功
$?的值不为0 代表失败

用户可以在脚本中使用以下命令自定义退出状态码

exit [n]  (普通128)

改变脚本退出状态码

[root@iptables ~]# ./script1.sh  a b c d e f g h i./script1.sh0
0
130355
[root@iptables ~]# echo $?
0
[root@iptables ~]# vim script1.sh 
set --
echo $1 $2 $3 $4 $5 $6 $6 $7 $8 $9
echo $0
for i in "$*"
doecho $i
done
for i in "$@"
doecho $i
done
echo $#
echo $?
echo $$
exit 100
[root@iptables ~]# ./script1.sh  ./script1.sh0
0
131617
[root@iptables ~]# echo $?
100

实例:

[root@localhost ~]# ping -c 2 www.baidu.com > /dev/null 
[root@localhost ~]# echo $?
0
[root@localhost ~]# cmd
-bash: cmd: 未找到命令
[root@localhost ~]# echo $?
127
[root@localhost ~]# 

注意:

  • 脚本中一旦遇到了exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字

  • 如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

实例1:$?获取上一个命令的退出状态

#!/bin/bash
if [ "$1"==100 ];thenexit 0 #参数正确,退出状态为0
elseexit 1 #参数错误,退出状态为1
fi
[root@iptables ~]# vim script1.sh 
#set --
echo $1 $2 $3 $4 $5 $6 $6 $7 $8 $9
echo $0
for i in "$*"
doecho $i
done
for i in "$@"
doecho $i
done
exit
echo $#
echo $?
echo $$[root@iptables ~]# ./script1.sh  ./script1.sh[root@iptables ~]# ./script1.sh  123 234 456
123 234 456
./script1.sh
123 234 456
123
234
456

exit表示退出当前 Shell 进程,我们必须在新进程中运行 test.sh,否则当前 Shell 会话(终端窗口)会被关闭,我们就无法取得它的退出状态了

[root@localhost re_study]# bash test.sh 100
[root@localhost re_study]# echo $?
0
[root@localhost re_study]# bash test.sh 99
[root@localhost re_study]# echo $?
1
[root@localhost re_study]# 

实例2:$?获取函数的返回值

#!/bin/bash
#得到两个数相加的和
function add(){return `expr $1 + $2`
}
add 23 50 #调用函数
echo $? #获取函数返回值
# 运行结果:
[root@localhost re_study]# bash test.sh
73

9、 展开命令行

9.1展开命令执行顺序
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明(~)
命令替换$()和``
再次把命令行分成命令词
展开文件通配(*,?,[abc]等)
准备I/O重导向(<,>)
运行命令
9.2 防止扩展
反斜线(\)会使随后的字符按原意解释  转义符
\t
[root@iptables ~]# a=1
[root@iptables ~]# echo $a
1
[root@iptables ~]# echo "\$a"
$a
[root@iptables ~]# echo "\t"
\t
[root@iptables ~]# echo -e "\t"[root@iptables ~]# echo -e "\ta"a
[root@iptables ~]# echo -e "\va"a
[root@iptables ~]# echo -e "\na"a
[root@iptables ~]# echo -e "b\na"
b
a
[root@iptables ~]# echo -e "b\va"
ba
[root@iptables ~]# echo -e "b\ra"
a

实例:

[root@localhost ~]# echo Your cost: \$5.00
Your cost: $5.00
[root@localhost ~]#
9.3 加引号来防止扩展
单引号(' ')防止所有扩展
双引号(" ")可防止扩展,但是以下清空例外:$(美元符号)
9.4 变量扩展
``:反引号,命令替换
\:反斜线,禁止单个字符扩展
!:叹号,历史命令替换

10、 脚本安全和set(暂时不用)

set命令:可以用来定制shell环境

10.1 $-变量
  • h:hashell,打开选项后,Shell会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭.默认开启

  • i:interactive-comments,包含这个选项说明当前的shell是一个交互式的shell。所谓的交互式shell,在脚本中,i选项是关闭的

  • m:monitor,打开监控模式,就可以通过Job control来控制进程的停止,继续,后台或者前台执行等

  • B:braccexpand,大括号扩展

  • H:history,H选项打开,可以展开历史列表中的命令,可以通过!来完成,例如!!返回最近的一个历史命令,!n返回第n个历史命令

实例:

[root@localhost ~]# echo $-
himBHs
[root@localhost ~]# hash
命中	命令1	/usr/sbin/ping
[root@localhost ~]# set +h
[root@localhost ~]# echo $-
imBHs
[root@localhost ~]# hash
-bash: hash: 已禁用哈希
[root@localhost ~]# [root@localhost ~]# echo {1..10}
1 2 3 4 5 6 7 8 9 10
[root@localhost ~]# set +B
[root@localhost ~]# echo {1..10}
{1..10}
[root@localhost ~]# [root@localhost ~]# set +H
[root@localhost ~]# history !!
-bash: history: !!: 需要数字参数
[root@localhost ~]# history !3
-bash: history: !3: 需要数字参数
[root@localhost ~]# set -H
[root@localhost ~]# history !!
history set -H
-bash: history: set: 需要数字参数
[root@localhost ~]# history !3
history ifconfg
-bash: history: ifconfg: 需要数字参数
[root@localhost ~]# 
10.2 set命令实现脚本安全
  • -u:在扩展一个没有设置的变量时,显示错误信息,等同于set -o nounset

  • -e:如果一个命令返回一个非0退出的状态值(失败)就退出,等同于set -o errexit

  • -o:option 显示,打开或关闭选项

    • 显示选项:set -o

    • 打开选项:set -o 选项

    • 关闭选项:set +o 选项

  • -x:当执行命令时,打印命令及其参数,类似bash -x

实例:

[root@localhost ~]# set -o
allexport      	off
braceexpand    	off
emacs          	on
errexit        	off
errtrace       	off
functrace      	off
hashall        	on
histexpand     	on
history        	on
ignoreeof      	off
interactive-comments	on
keyword        	off
monitor        	on
noclobber      	off
noexec         	off
noglob         	off
nolog          	off
notify         	off
nounset        	off
onecmd         	off
physical       	off
pipefail       	off
posix          	off
privileged     	off
verbose        	off
vi             	off
xtrace         	off
[root@localhost ~]# vim shell/error.sh
#!/bin/bash
echo "Hello World"
sssss
echo "Hello Linux"
[root@localhost ~]# chmod +x shell/error.sh 
[root@localhost ~]# shell/error.sh 
Hello World
shell/error.sh:行3: sssss: 未找到命令
Hello Linux
[root@localhost ~]# vim shell/error.sh
#!/bin/bash
set -e
echo "Hello World"
sssss
echo "Hello Linux"
[root@localhost ~]# shell/error.sh 
Hello World
shell/error.sh:行4: sssss: 未找到命令
[root@localhost ~]# 

三、 Shell字符串详解

字符串(String)就是一系列字符的组合。字符串是Shell编程中最常用的数据类型之一

字符串可以由单引号''包围,也可以由""包围,也可以不用引号,三种方式的区别

  1. 由单引号' '包围的字符串

    • 任何字符都会原样输出,在其中使用变量是无效的

    • 字符串中不能出现单引号,即使对单引号进行转义也不行

  2. 由双引号" "包围的字符串

    • 如果其中包含了某个变量,那么该变量就会被解析(得到该变量的值),而不是原样输出

    • 字符串中可以出现双引号,只要进行转义就行

  3. 不被引号包围的字符串

    • 不被引号包围的字符串中出现变量也会被解析,这一点和双引号""包围的字符串一样

    • 字符串中不能出现空格,否则空格后面的字符串会作为其他变量或者命令解析

通过代码演示一下三种形式的区别

#!/bin/bash
n=74
str1=c.biancheng.net$n 
str2="shell \"Script\" $n"
str3='C语言中文网 $n'
echo $str1
echo $str2
echo $str3# 运行结果
c.biancheng.net74
shell "Script" 74
C语言中文网 $n
[root@iptables ~]# a=a b c
bash: b: 未找到命令...
[root@iptables ~]# a='a b c'
[root@iptables ~]# echo $a
a b c
[root@iptables ~]# a="a b c"
[root@iptables ~]# echo $a
a b c
[root@iptables ~]# a="$a b c"
[root@iptables ~]# echo $a
a b c b c
[root@iptables ~]# a='$a b c'
[root@iptables ~]# echo $a
$a b c

str1 中包含了$n,它被解析为变量 n 的引用。$n后边有空格,紧随空格的是 str2;Shell 将 str2 解释为一个新的变量名,而不是作为字符串 str1 的一部分

str2 中包含了引号,但是被转义了(由反斜杠\开头的表示转义字符)。str2 中也包含了$n,它也被解析为变量 n 的引用

str3 中也包含了$n,但是仅仅是作为普通字符,并没有解析为变量 n 的引用

获取字符串长度

在Shell中获取字符串长度很简单,具体方法如下:

${#string_name}
string_name:表示字符串名字
[root@iptables ~]# a=juexingjiaoyu
[root@iptables ~]# echo ${#a}
13
[root@iptables ~]# echo $#a
0a

1、Shell字符串拼接

在脚本语言中,字符串的拼接(也称为字符串连接或者字符串合并)往往都非常简单,例如:

  • PHP中使用.即可连接两个字符串

  • JavaScript中使用+即可将两个字符串合并为一个

然而,在Shell中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接

#!/bin/bash
name="shell"
url="http://c.biancheng.net/shell/"
str1=$name$url #中间不能有空格
str1=$name":"$url
str2="$name $url" #如果被双引号包围,那么中间可以有空格,也可以出现别的字符串
str3="$name:$url" 
str4="${name}Script:${url}Index.html" #在变量后加上字符串,需要给变量名加上大括号
[root@iptables ~]# str1=abc
[root@iptables ~]# str2=com
[root@iptables ~]# echo $str1$str2
abccom
[root@iptables ~]# echo $str1$str2pp
abc
[root@iptables ~]# echo $str1${str2}pp
abccompp

2、Shell字符串截取

Shell截取字符串通常有两种方式,从指定位置开始截取和从指定字符(子字符串)开始截取

从指定位置开始截取

这种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串

既然需要指定起始位置,那么就要涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数?答案是:Shell同时支持两种计数方式

1.从字符串左边开始计数

如果想从字符串的左边开始计数,那么截取字符串的具体格式如下:

${string:start:length}`其中,Sting是要截取的字符串,start是起始位置(从左边开始,从0开始计数),length是要截取的长度(省略的话表示直到字符串的末尾)

例如:

url="c.biancheng.net"
echo ${url:2:9}>结果为:bianchengurl="c.biancheng.net"
echo ${url:2} #省略length,截取到字符串末尾>结果为:biancheng.net
[root@iptables ~]# str3=123456789
[root@iptables ~]# echo ${str3:2:2}
34

2.从右边开始计数

如果想从字符串的右边开始计数,那么截取字符串的具体格式如下:

${string:0-start:length}`同第 1) 种格式相比,第 2) 种格式仅仅多了0-,这是固定的写法,专门用来表示从字符串右边开始计数。这里需要强调两点:
从左边开始计数时,起始数字是 0(这符合程序员思维);
从右边开始计数时,起始数字是 1(这符合常人思维)
计数方向不同,起始数字也不同。
不管从哪边开始计数,截取方向都是从左到右。

例如:

[root@iptables ~]# echo ${str3:0-7:3}
345
url="c.biancheng.net"
echo ${url:0-13:9}>结果为:biancheng 从右边数:b是第13个字符url="c.biancheng.net"
echo ${url:0-13} #省略length,直接截取到字符串末尾
>结果为:biancheng.net

从指定字符(子字符串)开始截取

这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符

1)使用#号截取右边字符

使用#号可以截取指定字符(或子字符串)右边的所有字符,具体格式如下:

${string#*chars}#其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)

例如:

[root@iptables ~]# echo ${str3#*5}
6789
url="http://c.biancheng.net/index.html"
echo ${url#*:}>结果为://c.biancheng.net/index.htmlecho ${url#*p:}
echo ${url#*ttp:}`如果不需要忽略chars左边的字符,那么也可以不写*
url="http://c.biancheng.net/index.html"
echo ${url#http://}
>结果为:c.biancheng.net/index.html`注意:以上写法遇到第一个匹配的字符(子字符串)就结束了
url="http://c.biancheng.net/index.html"
echo ${url#*/}
>结果为:/c.biancheng.net/index.html。url 字符串中有三个/,输出结果表明,Shell 遇到第一个/就匹配结束了

使用##可以直到最后一个指定字符(子字符串)再匹配结束

${string##*chars}

例如:

[root@iptables ~]# echo ${str3#*5}
6789123456789
[root@iptables ~]# echo ${str3##*5}
6789
#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url#*/}
# 结果为:/c.biancheng.net/index.html
echo ${url##*/}
# 结果为:index.htmlstr="-----aa+++aa@@@"
echo ${str#*aa}
# 结果为:+++aa@@@
echo ${str##*aa}
# 结果为:@@@

2)使用%截取左边字符

使用%号可以截取指定字符(或者子字符串)左边的所有字符

[root@iptables ~]# echo ${str3%5*}
1234567891234
[root@iptables ~]# echo ${str3%%5*}
1234
${string%chars*}
`请注意*的位置,因为要截取 chars 左边的字符,而忽略 chars 右边的字符,所以*应该位于 chars 的右侧。其他方面%和#的用法相同#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url%/*}  #结果为 http://c.biancheng.net
echo ${url%%/*}  #结果为 http:
str="---aa+++aa@@@"
echo ${str%aa*}  #结果为 ---aa+++
echo ${str%%aa*}  #结果为 ---

3、汇总

格式说明
${string:start :length}从string字符串的左边第start个字符开始,向右截取length个字符。
${string:start}从string字符串的左边第start个字符开始截取,直到最后。
${string:0-start:length}从string字符串的右边第start个字符开始,向右截取length个字符。
${string:0-start}从string字符串的右边第start个字符开始截取,直到最后。
${string#*chars}从string字符串第一次出现chars的位置开始,截取chars右边的所有字符。
${string##*chars}从string字符串最后一次出现chars的位置开始,截取chars右边的所有字符。
${string%chars*}从string字符串第一次出现chars的位置开始,截取chars左边的所有字符。
${string%%chars*}从string字符串最后一次出现chars的位置开始,截取chars左边的所有字符。

4、Shell的格式化输出printf

4.1、语法格式:
printf "指定的格式" "文本1" "文本2" .....
4.2、常用格式替换符:
替换符功能
%s字符串
%f浮点格式,保留小数点位数%.nf,n为数字
%b相对应的参数中包括转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%cASCII字符,即显示对应参数的第一个字符
%d,%i十进制整数
%o八进制值
%u不带正负号的十进制值
%x十六进制值(a-f)
%X十六进制值(A-F)
%%表示%本身

说明:%s中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,-表示左对齐

[root@iptables ~]# vim personal.sh#!/bin/bash
read -p "请输入你的姓名:" name
read -p "请输入你的年龄:" age
read -p "请输入你的性别:" sex
read -p "请输入你的电话:" phone
read -p "请输入你的体重:" weight
printf "%s 你好,你的年龄 %d,性别 %s,电话 %d,体重 %.2f" $name $age $sex $phone $weight
[root@iptables ~]# vim personal.sh
[root@iptables ~]# ./personal.sh
请输入你的姓名:张三    
请输入你的年龄:24
请输入你的性别:男
请输入你的电话:123456789
请输入你的体重:100
张三 你好,你的年龄 24,性别 男,电话 123456789,体重 100.00

4.3、常用转义字符:
转义符功能
\a警告字符,通常为ASCII的BEL字符
\b后退
\f换页
\n换行
\r回车
\t水平制表符
\v垂直制表符
\\表示\本身

实例

[root@localhost ~]# printf "%s\n" 1 2 3 4
1
2
3
4
[root@localhost ~]# printf "%f\n" 1 2 3 4
1.000000
2.000000
3.000000
4.000000
[root@localhost ~]# printf "%.2f\n" 1 2 3 4 #.2f表示保留两位小数
1.00
2.00
3.00
4.00 
[root@localhost ~]# printf "(%s)" 1 2 3 4;echo " "
(1)(2)(3)(4) 
[root@localhost ~]# printf " (%s) " 1 2 3 4;echo " "(1)  (2)  (3)  (4)  
[root@localhost ~]# printf " (%s) (%s)\n" 1 2 3 4;echo " "(1) (2)(3) (4)
[root@localhost ~]# printf " %s %s\n" 1 2 3 4;echo " "1 23 4
[root@localhost ~]# printf "%s %s %s\n" 1 2 3 4
1 2 3
4  
[root@localhost ~]# #%-10s表示宽度10个字符,左对齐
[root@localhost ~]# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男性 20岁 70KG 小红 女性 18岁 50KG 
姓名     性别     年龄 体重 
小明     男性     20岁 70KG 
小红     女性     18岁 50KG 
[root@localhost ~]# #将十进制的1000转换为16进制数
[root@localhost ~]# printf "%X\n" 1000
3E8
[root@localhost ~]# printf "%x\n" 1000
3e8
[root@localhost ~]# #将十六进制的C转换为十进制
[root@localhost ~]# printf "%d\n" 0xc
12
[root@localhost ~]# [root@localhost ~]# var="welcome to study";printf "\033[31m%s\033[0m\n" $var
welcome
to
study
[root@localhost ~]# var="welcome to study";printf "\033[31m%s\033[0m\n" "$var"
welcome to study
[root@localhost ~]#

四、Shell脚本语言的运算

4.1 算数运算

shell支持算术运算,但只支持整数,不支持小数

4.2 Bash中的算术运算
-- + 加法运算
-- - 减法运算
-- * 乘法运算
-- / 除法运算  (除数不能为0)
-- % 取模,即取余数
-- ** 乘方 #乘法符号在有些场景需要转义
4.2 实现算术运算
1. let var=算术表达式 
2. var=$[算术表达式]
3. var=$((算术表达式))
4. var=$(expr arg1 arg2 arg3 ...)  ()=``
5. declare -i var = 数值
6. echo '算术表达式' | bc  (支持浮点数)
[root@iptables ~]# let var=1+1
[root@iptables ~]# echo $var
2
[root@iptables ~]# var=$[1+2]
[root@iptables ~]# echo $var
3
[root@iptables ~]# let var=n1+n2
[root@iptables ~]# echo $var
3
[root@iptables ~]# var=$((3+3))
[root@iptables ~]# echo $var
6
#操作数和操作符之间必须用空格隔开
[root@iptables ~]# expr 1+2
1+2
[root@iptables ~]# expr 1 + 2
3
[root@iptables ~]# var=$(expr 1 + 2)
[root@iptables ~]# echo $var
3
[root@iptables ~]# var=`expr 1 + 2`
[root@iptables ~]# echo $var
3#浮点数
[root@iptables ~]# echo "scale=3;5/4" | bc
1.250
[root@iptables ~]# echo "scale=5;5/4" | bc
1.25000#取模,取余数
[root@iptables ~]# var=$[5%4]
[root@iptables ~]# echo $var
1
[root@iptables ~]# i=2
[root@iptables ~]# j=5
[root@iptables ~]# echo "var=$[i*j]"
var=10

实例:使用bc计算小数和declare -i计算

[root@localhost ~]# echo "scale=3;20/3"|bc
6.666
[root@localhost ~]# echo "scale=3;2/3"|bc
.666
[root@localhost ~]# i=20
[root@localhost ~]# j=20
[root@localhost ~]# declare -i sum=i*j
[root@localhost ~]# echo $sum
400
[root@localhost ~]# 

内建的随机数生成器变量:

$RANDOM  取值范围:0-32767
$[RANADOM%10]取0-10之间的随机数
[root@iptables ~]# echo $[RANDOM%10]
5
[root@iptables ~]# echo $[RANDOM%10]
8
[root@iptables ~]# echo $[RANDOM%10+1]

实例:生成0-49之间的随机数

[root@localhost ~]# echo $[$[$RANDOM%50]+1]
40
[root@localhost ~]# echo $[$RANDOM%50]
44
[root@localhost ~]# echo $[$[$RANDOM%50]+1]  #生成1~50之间的随机数
[root@localhost ~]# echo $[RANDOM % 100 + 1]

实例:生成随机颜色字符串

[root@localhost ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"
Study
[root@localhost ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"
Study
[root@localhost ~]#
4.3 增强型赋值:
+= 
i+=10 <==> i=1+10
-=
i-=j  <==> i=i-j
*=乘法赋值
/=除法
%=取余
++ 
i++,++i <==> i=i+1 (自增)
--
i--,--i <==> i=i-1 (自减)

格式:

let varOPERvalue

实例:自增,自减

[root@iptables ~]# i=2
[root@iptables ~]# let i+=10
[root@iptables ~]# echo $i
12[root@localhost ~]# let var+=1
[root@localhost ~]# echo $var
1
[root@localhost ~]# let var++
[root@localhost ~]# echo $var
2
[root@localhost ~]# let var-=1
[root@localhost ~]# echo $var
1
[root@localhost ~]# let var--
[root@localhost ~]# echo $var
0
[root@localhost ~]#[root@localhost ~]# unset i j ;i=1;let j=i++;echo "i=$i,j=$j" 
i=2,j=1
[root@localhost ~]# unset i j ;i=1;let j=++i;echo "i=$i,j=$j"
i=2,j=2
[root@iptables ~]# unset i j ;i=1;let j=i+i;echo "i=$i,j=$j"
i=1,j=2
[root@localhost ~]# # i++ 与 ++i的区别:
i++ 先赋值再运算
++i 先运算再赋值

实例:鸡兔同笼问题

[root@localhost ~]# vim shell/chicken.sh
#!/bin/bash
HEAD=35
FOOT=94RABBIT=$[(FOOT-HEAD-HEAD)/2]
CHOOK=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为: "$CHOOK[root@localhost ~]# chmod +x shell/chicken.sh 
[root@localhost ~]# shell/chicken.sh 
兔子的数量为::12
鸡的数量为:23
[root@localhost ~]# # 在脚本中写入变量,让用户在命令行写入需要计算的数值
[root@localhost ~]# vim shell/chicken.sh
#!/bin/bash
HEAD=$1
FOOT=$2RABBIT=$[(FOOT-HEAD-HEAD)/2]
CHOOK=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为: "$CHOOK[root@ansible-salve1 ~]# ./chicken.sh 30 80
兔子的数量为:10
鸡的数量为: 25
[root@ansible-salve1 ~]# 

面试题:计算出所有人的年龄总和

[root@ansible-salve1 shell]# vim nianling.txt
[root@ansible-salve1 shell]# cat nianling.txt
a=20
b=18
c=22
[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt 
20
18
22
[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
60
tr '\n' +:将这些部分用+连接成一个字符串,将换行符\n替换为加号+,将多行数值合并为一行,用加号连接。上一步的输出经此处理后变为:20+18+22+(假设文件末尾有换行符,否则无末尾加号)
grep -Eo ".*[0-9]":提取每个部分中最长的以数字结尾的子串(即去掉末尾的非数字字符),并每个结果占一行(但由于正则表达式匹配整个字符串,可能只输出一行)
-E:启用扩展正则表达式
-o:仅输出匹配的部分[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt 
20
18
22
[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt | tr '\n' +| grep -Eo ".*[0-9]" | bc
60
[root@ansible-salve1 shell]# 

4.2 逻辑运算(了解,不用掌握)

True用数字表示1,False用数字表示0

  • 与:&

    1 与 1 = 1   
    1 与 0 = 0
    0 与 1 = 0
    0 与 0 = 0
  • 或:|

    1 或 1 = 1
    1 或 0 = 1
    0 或 1 = 1
    0 或 0 = 0
  • 非:!

    !1 = 0  !True=False
    !0 = 1  !False=True
  • 异或:^

    #异或的两个值,相同为假,不同为真
    1 ^ 1 =0
    1 ^ 0 =1
    0 ^ 1 =1
    0 ^ 0 =0

4.3 短路运算(不用了解)

  • 短路与

    CMD1 短路与 CMD2
    --第一个CMD1结果为0(假),总的结果必定为0,所以不需要执行CMD2
    --第二个CMD1结果为1(真),第二个CMD2必须要参与计算,才能得到最终的结果
  • 短路或

    CMD1 短路或 CMD2
    --第一个CMD1结果为1(真),总的结果必定为1,因此不需要执行CMD2
    --第一个CMD1结果为0(假),第二个CMD2必须要参与运算,才能得到最终的结果

4.4 条件测试命令

条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便在条件性环境下进行执行。

  • 若真,则状态码变量$?返回0

  • 若假,则状态码变量$?返回1

扩展: [ ] 与 [[ ]] 的区别

`区别1:
[ ]是符合POSIX标准的测试语句,兼容性强,几乎可以运行在所有的Shell解释器中
[[ ]]仅可运行在特定的几个Shell解释器中(如Bash)`区别2:
> < 可以在[[ ]]中使用ASCII码顺序进行排序,而[]不支持`区别3:
在[ ]中使用-a 和 -o 表示逻辑与和逻辑或,[[ ]]使用&& 和 || 表示,[[ ]]不支持-a`区别4:
在[ ]中==是字符匹配,在[[ ]]中==是模式匹配`区别5:
[ ]不支持正则匹配,[[ ]]支持用=~进行正则匹配`区别6:
[ ]仅在部分Shell中支持用()进行分组,[[ ]]均支持`区别7:
在[ ]中如果变量没有定义,那么需要用双引号引起来,在[[ ]]中不需要
4.4.1 条件测试命令及其语法

语法1:test <测试表达式> 说明:test命令和<测试表达式>之间至少有一个空格

# 在shell中,大于用 -gt 表示,小于用 -lt 表示,大于或等于用 -ge 表示,小于或等于用 -le表示 ,不相等用-ne 表示
[root@ansible-salve1 ~]# test 1 -lt 2
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# test 2 -lt 1
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# 

语法2:[<测试表达式>] 说明:该方法和test命令的用法一样,[]的两边和内容之间至少有一个空格

[root@ansible-salve1 ~]# [ 1 -gt 3 ]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# [ 1 -lt 3 ]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# 

语法3:[[ <测试表达式> ]] 说明:比test和[]更新的语法格式。[[]]的边界和内容之间至少有一个空格。[[]]中可以使用通配符等进行模式匹配

[root@ansible-salve1 ~]# [[ 1 > 3 ]]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# [[ 1 < 3 ]]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# 

语法4:((<测试表达式>)) 说明:一般用于if语句里,双小括号两端不需要有空格,测试对象只能是整数

[root@ansible-salve1 ~]# ((1>2))
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# ((1<2))
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
4.4.2 变量测试

语法规则:-v VAR 变量var是否被定义

示例:判断NAME变量是否被定义

[root@ansible-salve1 shell]# [[ -v NAME ]]
[root@ansible-salve1 shell]# echo $?
1
[root@ansible-salve1 shell]# NAME=1
[root@ansible-salve1 shell]# [[ -v NAME ]]
[root@ansible-salve1 shell]# echo $?
0
[root@ansible-salve1 shell]#

语法规则: -R VAR 变量VAR是否被引用

示例:判断NAME变量是否被引用

[root@ansible-salve1 shell]# NAME=10
[root@ansible-salve1 shell]# test -v NAME
[root@ansible-salve1 shell]# echo $?
0
[root@ansible-salve1 shell]# test -R NAME
[root@nsible-salve1 shell]# echo $?
1
[root@ansible-salve1 shell]# 
运算符作用示例(假设 VAR=""
-v VAR检查变量是否被定义(存在)[ -v VAR ] → 真(变量已定义)
-z VAR检查变量值是否为空(长度为0)[ -z VAR ] → 真(值为空)
-n VAR检查变量值是否非空(长度>0)[ -n VAR ] → 假(值为空)
4.4.3 文件测试表达式
常用的文件测试操作符说明
-a/-e 文件文件是否存在
-b 文件文件是否存在,且为块文件,如果文件存在且是一个块文件,则结果为0
-c 文件文件是否存在且为字符文件,如果文件存在且是一个字符文件,则结果为0
-L 文件 或 -h 文件文件存在且为链接文件则为真
-d 文件文件存在且为目录则为真,即测试表达式成立
-f 文件文件存在且为普通文件则为真,即测试表达式成立
-s 文件文件存在且文件大小不为0则为真
-S 文件文件是否存在且为套接字文件
-p 文件文件是否存在且为管道文件
-u 文件文件是否存在且拥有suid的权限,如果设置了suid,则结果为0
-g 文件文件是否存在且拥有sgid的权限
-r 文件文件存在且可读为真普通用户
-w 文件文件存在且可写为真
-x 文件文件存在且可执行则为真
-t fdfd 文件描述符是否在某终端已经被打开
-N 文件文件自从上一次读取之后是否被修改过
-O 文件当前有效用户是否为文件属主
-G 文件当前有效用户是否为文件属组
f1 -ef f2文件f1是否是文件f2的硬链接
f1 -nt f2,nt为newerthan文件f1比文件f2新则为真,根据文件的修改时间来计算
f1 -ot f2,ot为olderthan文件f1比文件f2旧则为真,根据文件的修改时间来计算

测试文件

[root@ansible-salve1 ~]# test -a test.txt
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# test -d shell
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# touch test.txt
[root@ansible-salve1 ~]# test -w test.txt &&echo  "true"
true
[root@ansible-salve1 ~]# test -r test.txt &&echo  "true"
true
[root@ansible-salve1 ~]# test -x test.txt &&echo  "true"
[root@ansible-salve1 ~]# 
4.4.4 字符串测试表达式
常用字符串测试操作符说明
-n ”字符串“若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为nozero
-z ”字符串“若字符串的长度为0,则为真,z可以理解为zero
>Ascii码是否大于Ascii码,[[]]
字符串1” == ”字符串2“若字符串1长度等于字符串2长度,则为真
“字符串1” != ”字符串2“若字符串1长度不等于字符串2长度,则为真
“字符串1” =~ “字符串2”左侧字符串是否能被右侧的PATTERN所匹配。注意:此表达式用于[[ ]]中:扩展的正则表达式

测试字符串

[root@ansible-salve1 ~]# var_test='Mike' 
[root@ansible-salve1 ~]# echo $var_test 
Mike
[root@ansible-salve1 ~]# [[ -n var_test ]] && echo "True"
True# 通配符
[root@ansible-salve1 ~]# FILE=test.log
[root@ansible-salve1 ~]# [[ "$FILE" == *.log ]] && echo "True"
True
[root@ansible-salve1 ~]# FILE=test.txt
[root@ansible-salve1 ~]# [[ "$FILE" == *.log ]] && echo "True"
[root@ansible-salve1 ~]# [[ "$FILE" != *.log ]] && echo "True"
True
[root@ansible-salve1 ~]# # 扩展的正则表达式
[root@ansible-salve1 ~]# FILE=test.log
[root@ansible-salve1 ~]# [[ "$FILE" =~  \.log$ ]] && echo "True"
True
[root@ansible-salve1 ~]# N=100
[root@ansible-salve1 ~]# [[ "$N" =~ ^[0-9]+$ ]] && echo "True"
True
[root@ansible-salve1 ~]# N=A10
[root@ansible-salve1 ~]# [[ "$N" =~ ^[0-9]+$ ]] && echo "True"
[root@ansible-salve1 ~]# IP=1.2.3.4
[root@ansible-salve1 ~]# [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# 
# 使用正则表达式判断IP是否合法
`[root@ansible-salve1 ~]# IP=1.2.3.333
`[root@ansible-salve1 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# IP=255.255.255.255
[root@ansible-salve1 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# 
4.4.5 整数测试表达式
在[ ] 或 test中使用的比较符号在(()) 或 [[ ]]中使用的比较符号(不用这个做数字比较)说明
-eq\== 或 =相等,equal
-ne!=不相等,not equal
-gt>大于,greater than
-ge> =大于等于,greater equal
-lt<小于,less than
-le< =小于等于,less equal
4.4.6 逻辑操作符
在[ ] 中使用的操作符在test, [[ ]] , (( ))中使用的逻辑操作符说明
-a&&and,与,两边都为真,则结果为真
-o||or,或,有真则真,同假则假
not,非,两端相反,则结果相反

示例

[root@iptables ~]# var=1
[root@iptables ~]# var_a=2
[root@iptables ~]# [ $var -lt 0 -a $var_a -gt 0 ]
[root@iptables ~]# echo $?
1
[root@iptables ~]# [ $var -lt 0 -o $var_a -gt 0 ]
[root@iptables ~]# echo $?
0
[root@iptables ~]# [ ! $var -lt 0 -o $var_a -gt 0 ]
[root@iptables ~]# echo $?
0

4.5 关于()与 { }

( )和 { }都可以将多个命令组合再一次,批量执行,{ } 里的内容需要与两侧用空格隔开并在命令结尾加上;

  • ( )会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境

    [root@ansible-salve1 ~]# name=mage;(echo $name;name=wang;echo $name);echo $name
    mage
    wang
    mage[root@ansible-salve1 ~]# (umask 444;umask);umask
    0444
    0022
    [root@ansible-salve1 ~]# (umask 444;touch 1.txt);umask
    0022
    [root@ansible-salve1 ~]# ll 1.txt 
    --w--w--w- 1 root root 0 11月 13 22:12 1.txt
    [root@ansible-salve1 ~]# echo $BASHPID
    1418
    [root@ansible-salve1 ~]# (echo $BASHPID; sleep 3)
    1501
  • { } 不会开启子shell,在当前shell中运行,会影响当前shell环境

    [root@ansible-salve1 ~]# name=mage;{ echo $name;name=wang;echo $name; };echo $name
    mage
    wang
    wang
    [root@ansible-salve1 ~]# { umask 444;touch 1.txt; };umask
    0444
    [root@ansible-salve1 ~]# umask
    0444
    [root@ansible-salve1 ~]# ll 1.txt 
    --w--w--w- 1 root root 0 11月 13 22:23 1.txt
    [root@ansible-salve1 ~]# echo $BASHPID
    1418
    [root@ansible-salve1 ~]# { echo $BASHPID; sleep 3; }
    1418
    [root@ansible-salve1 ~]#  

4.6 组合测试条件

4.6.1 第一种方式[ ]
[ EXPRESSION1  -a EXPRESSION2]  并且 ==> 条件1与条件2都为真,结果才为真
[ EXPRESSION1  -O EXPRESSION2]  或  ==> 条件1与条件2只要有一个为真,结果就为真
[ !EXPRESSION1 ]  取反

说明:-a 和 -o 需要使用测试命令执行,[[ ]] 不支持

示例:

[root@ansible-salve1 ~]# ll 1.txt 
--w--w--w- 1 root root 0 11月 13 22:23 1.txt
[root@ansible-salve1 ~]# FILE=/root/1.txt 
[root@ansible-salve1 ~]# [ -f $FILE -a -x $FILE ]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# chmod +x /root/1.txt 
[root@ansible-salve1 ~]# [ -f $FILE -a -x $FILE ]
[root@ansible-salve1 ~]# echo $?
0
[root@iptables ~]# [ -f $file -a -x $file ]
[root@iptables ~]# 
[root@iptables ~]# echo $?
1
[root@iptables ~]# [ -f $file -a -r $file ]
[root@iptables ~]# echo $?
0
4.6.2 第二种方式[[ ]]
COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2! COMMAND #非,取反# 结论:如果&& 和 || 混合使用,&&要在前,||放在后

示例

[root@ansible-salve1 ~]# id hehao &> /dev/null  || useradd hehao  # 前面执行不成功,则执行后面的语句
[root@ansible-salve1 ~]# id hehao &> /dev/null  && echo "此账户已存在"
此账户已存在
[root@ansible-salve1 ~]# [root@ansible-salve1 ~]# IP=10.0.0.11;ping -c1 -w1 $IP &> /dev/null && echo $IP is up || echo $IP is down
10.0.0.11 is down
[root@ansible-salve1 ~]# IP=192.168.80.1;ping -c1 -w1 $IP &> /dev/null && echo $IP is up || echo $IP is down
192.168.80.1 is up
[root@ansible-salve1 ~]# 

示例:磁盘空间的判断

# 查看磁盘空间占用率
[root@ansible-salve1 shell]# df | grep '^/dev/nv'  | grep -oE '[0-9]+%' | tr -d %
17
[root@ansible-salve1 shell]# cat fd.sh 
#!/bin/bash
WARNING=10
SPACE_USED=`df | grep '^/dev/nv'  | grep -oE '[0-9]+%' | tr -d %`
["$SPACE_USED" -gt $WARNING] && echo "磁盘空间不足,请尽快处理" |  mail -s "DISK Warning" 1446528135@qq.com

4.7 使用read命令命令来接受输入

read 是 Shell 内置命令,用来从标准输入中读取数据并赋值给变量。如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据

4.7.1 语法结构
read [option] [variables]--options表示选项--variables表示用来存储数据的变量,可以有一个,也可以有多个。-- options和variables都是可选的,如果没有提供变量名,那么读取的数据将存放到环境变量 REPLY 中。
4.7.2 选项:
Option说明
-a array把读取的数据赋值给数组array,从下标0开始
-d delimiter把字符串delimiter指定读取结束的位置,而不是一个换行符(读取的数据不包括delimiter)
-e在获取用户输入的时候,对功能键进行编码转换,不会直接显示功能键对应的字符
-n num读取num个字符,而不是整行字符
-p prompt显示提示信息,提示内容为prompt
-r原样读取(Raw mode),不会把反斜杠字符解释为转义字符
-s静默模式(Silent mode),不会再屏幕上显示输入的字符。例如:输入密码
-t seconds设置超时时间,单位为秒。如果用户没能按时完成,返回一个非0的退出状态
-u fd使用文件描述符fd作为输入源,而不是标准输入,类似于重定向

示例:使用read给多个变量赋值并输出

[root@iptables ~]# vim info.sh
#!/bin/bash
read -p "Enter some information > " name url age
echo "网站名称:$name"
echo "网址:$url"
echo "年龄:$age"
[root@iptables ~]# chmod +x info.sh
[root@iptables ~]# bash info.sh
Enter some information > haoren www.baidu.com 22
网站名称:haoren
网址:www.baidu.com
年龄:22 # 注意:必须在一行内输入所有的值,不能换行,否则只能给第一个变量赋值,后续变量都会赋值失败

示例:只读取一个字符

# -n 1表示只读取一个字符,运行脚本后,只要用户输入一个字符,立即就读取结束,不等待用户按下回车键
[root@ansible-salve1 shell]# vim info1.sh 
#!/bin/bash
read -n 1 -p "Enter a char > " char  && printf "\n"
echo "---------------------------------------------------------"
echo $char
[root@ansible-salve1 shell]# chmod +x info1.sh 
[root@ansible-salve1 shell]# ./info1.sh 
Enter a char > a
---------------------------------------------------------
a[root@iptables ~]# vim info1.sh
#!/bin/bash
read -n 1 -p "Enter a char > " char && printf "\n"
echo $char
[root@iptables ~]# chmod +x info1.sh
[root@iptables ~]# ./info1.sh
Enter a char > h
h

示例:在指定时间内输入密码

#使用&&组合了多个命令,这些命令会依次执行,并且从整体上作为 if 语句的判断条件,只要其中一个命令执行失败(退出状态为非 0 值),整个判断条件就失败了,后续的命令也就没有必要执行
[root@ansible-salve1 shell]# vim info2.sh
#!/bin/bash
ifread -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" &&  #第一次输入密码read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" &&  #第二次输入密码[ $pass1 == $pass2 ]  #判断两次输入的密码是否相等
thenecho "Valid password"
elseecho "Invalid password"
fi
[root@ansible-salve1 shell]# chmod +x info2.sh 
[root@ansible-salve1 shell]# ./info2.sh 
Enter password in 20 seconds(once) > 
Enter password in 20 seconds(again) > 
Valid password
[root@ansible-salve1 shell]# 

示例:利用正则表达式

[root@ansible-salve1 shell]# vim info3.sh
[root@ansible-salve1 shell]# cat info3.sh 
#!/bin/bash
read -p "Are you rich? yes or no: " ANSWER
[[ $ANSWER =~ ^[Yy]|[Yy][Ee][Ss]$ ]] && echo "You Are Rich" || echo "Good Good Study,Day Day up"
[root@ansible-salve1 shell]# chmod +x info3.sh 
[root@ansible-salve1 shell]# ./info3.sh 
Are you rich? yes or no: y
You Are Rich
[root@ansible-salve1 shell]# ./info3.sh 
Are you rich? yes or no: YeS
You Are Rich
[root@ansible-salve1 shell]# 

示例:工作选项

[root@ansible-salve1 shell]# vim backup.sh 
#!/bin/bash
SRC=/etc/
DEST=/data/backup_`date +%F_%H-%M-%S`
cp -a $SRC $DEST
[ $? -eq 0 ] && echo "备份成功" || echo "备份失败"
[root@ansible-salve1 shell]# chmod +x backup.sh
[root@ansible-salve1 shell]# vim info4.sh
#!/bin/bash
cat <<EOF
请选择:
1.备份文件
2.清理日志文件
3.软件升级
4.软件回滚
5.删库跑路
EOF
read -p "请输入上面的数字1-5:" MENU
[ $MENU -eq 1 ] && ./backup.sh
[ $MENU -eq 2 ] && echo "清理日志文件"
[ $MENU -eq 3 ] && echo "软件升级"
[ $MENU -eq 4 ] && echo "软件回滚"
[ $MENU -eq 5 ] && echo "删除跑路"[root@ansible-salve1 shell]# chmod +x info4.sh 
[root@ansible-salve1 shell]# ./info4.sh 
请选择:
1.备份数据库
2.清理日志文件
3.软件升级
4.软件回滚
5.删库跑路
请输入上面的数字1-5:1
备份成功
[root@ansible-salve1 shell]# 

五、bash的配置文件

bash shell的配置文件很多,可以分为以下类别

5.1 按生效范围划分为两类

5.1.1 全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
5.1.2 个人配置
~/.bash_profile
~/.bashrc

5.2 shell登录的两种方式分类

5.2.1 交互式登录
  • 直接通过终端输入账户密码

  • 使用su - username 切换用户

配置文件执行顺序:

/etc/profile --> /etc/profile.d/*.sh  --> ~/.bash_profile --> ~/.bashrc -->  /etc/bashrc
5.2.2 非交互式登录
  • su username

  • 图形界面下打开的终端

  • 执行脚本

  • 任何其他的bash实例

配置文件执行顺序:

/etc/profile.d/*.sh   -->  /etc/bashrc --> ~/.bashrc

5.3 按功能划分分类

5.3.1 Profile类

profile类为交互式登录的shell提供配置

  • 全局:/etc/profile, /etc/profile.d/*.sh

  • 个人:~/.bash_profile

功能:

  • 用于定义环境变量

  • 运行命令或脚本

5.3.2 Bashrc类

bashrc类:为非交互式和交互式登录的shell提供配置

  • 全局:/etc/bashrc

  • 个人:~/.bashrc

功能:

  • 定义命令别名和函数

  • 定义本地变量

5.4 编辑配置文件生效

source 配置文件

六、流程控制

6.1 条件选择

6.1.1 选择执行if语句
if结构:
[root@ansible-salve1 shell]# help if
if: if 条件; then 命令; [ elif 命令; then 命令; ]... [ else 命令; ] fi根据条件执行命令。`if COMMANDS'列表被执行。如果退出状态为零,则执行`then COMMANDS' 列表。否则按顺序执行每个 `elif COMMANDS'列表,并且如果它的退出状态为零,则执行对应的 `then COMMANDS' 列表并且 if 命令终止。否则如果存在的情况下,执行 `else COMMANDS'列表。整个结构的退出状态是最后一个执行的命令的状态,或者如果没有条件测试为真的话,为零。退出状态:返回最后一个执行的命令的状态。
[root@ansible-salve1 shell]# 
6.1.1.1 单分支
if [ 条件判断式 ];then命令
fi 或者if [ 条件判断式 ]then命令
fi
[root@iptables ~]# vim info3.sh
#!/bin/bash
read -p "请输入你的年龄:"  age
if [ $age -ge 18 ]
thenecho "你已成年"
elseecho "你未成年"
bash $0
fi
[root@iptables ~]# bash info3.sh
请输入你的年龄:15
你未成年
[root@iptables ~]# bash info3.sh
请输入你的年龄:22
你已成年
6.1.1.2 双分支
if [ 条件判断式 ]then命令
else命令
fi#!/bin/bash
read -p "请输入日志文件路径:" log_file# 单分支:仅当文件存在时统计行数
if [ -f "$log_file" ]  # -f 检查是否为普通文件
thenline_count=$(wc -l < "$log_file")  # 统计行数(不显示文件名)echo "文件 $log_file 共有 $line_count 行"
fi
6.1.1.3 多分支
if [ 条件判断式1 ]then命令
elif [ 条件判断式2 ]then 命令
...
...
else命令
fi[root@iptables ~]# vim info4.sh
#!/bin/bash
read -p "请输入分数(0-100):" score
if [ $score -ge 90 ]
thenecho "优秀"
elif [ $score -lt 90 -a $score -ge 75 ]
thenecho "良好"
elif [ $score -lt 75 -a $score -ge 60 ]
thenecho "及格"
elif [ $score -lt 60 ]
thenecho "不及格"
fi
[root@iptables ~]# ./info4.sh
请输入分数(0-100):80
良好
[root@iptables ~]# vim info4.sh
[root@iptables ~]# ./info4.sh
请输入分数(0-100):20
不及格
[root@iptables ~]# ./info4.sh
请输入分数(0-100):66
及格
[root@iptables ~]# ./info4.sh
请输入分数(0-100):95
优秀

示例:依据BMI参数写出判断语句

[root@ansible-salve1 shell]# vim info5.sh#!/bin/bash
read -p "请输入身高(m为单位):" HIGH
if [[ ! "$HIGH" =~ ^[0-2].?[0-9]{,2}$ ]]thenecho "请不要输入错误的身高";exit 1;
fi
read -p "请输入体重(Kg为单位):" WEIGHT
if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]]thenecho "请不要输入错误的体重";exit 1;
fiBMI=`echo $WEIGHT/$HIGH^2|bc` if [ $BMI -le 18 ] ;thenecho "你太瘦了,请注意身体建康"
elif [ $BMI -lt 24 ] ;thenecho "身材很棒!"
elseecho "你太胖了,注意节食,加强运动"
fi[root@ansible-salve1 shell]# chmod +x info5.sh
[root@ansible-salve1 shell]# ./info5.sh

说明:

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

  • if语句可嵌套

6.1.2条件判断case语句

格式

[root@ansible-salve1 shell]# help case
case: case 词 in [模式 [| 模式]...) 命令 ;;]... esac基于模式匹配来执行命令。基于 PATTERN 模式匹配的词 WORD,有选择的执行 COMMANDS 命令。`|' 用于分隔多个模式。退出状态:返回最后一个执行的命令的状态。
[root@ansible-salve1 shell]# 

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

case支持glob风格的通配符

*: 任意长度任意字符
?: 任意单个字符
[]: 指定范围内的任意单个字符
|: 或,如a|b ,a或b

示例:

[root@ansible-salve1 shell]# vim info6.sh 
#!/bin/bash
read -p "Do you agree(yes/no)?" INPUTINPUT=`echo $INPUT | tr 'A-Z' 'a-z'`case $INPUT in
y|yes)  echo "You input is Yes";;
n|no)echo "You input is No";;
*)      echo "Input fales,please input yes or no!"
esac  
[root@ansible-salve1 shell]# chmod +x info6.sh
[root@ansible-salve1 shell]# ./info6.sh 
Do you agree(yes/no)?yes
You input is Yes
[root@ansible-salve1 shell]# ./info6.sh 
Do you agree(yes/no)?no
You input is No
[root@ansible-salve1 shell]# ./info6.sh 
Do you agree(yes/no)?111
Input fales,please input yes or no!
[root@ansible-salve1 shell]# 

示例:工作选项

[root@ansible-salve1 shell]# vim info7.sh
#!/bin/bash
cat <<EOF
请选择:
1.备份文件
2.清理日志文件
3.软件升级
4.软件回滚
5.删库跑路
EOF
read -p "请输入上面的数字1-5:" MENU
case $MENU in
1)./backup.sh;;
2)echo "清理日志";;
3)echo "软件升级";;
4)echo "软件回滚";;
5)echo "删库跑路";;
*)echo "Input False"
esac
[root@ansible-salve1 shell]# chmod +x info7.sh 
[root@ansible-salve1 shell]# ./info7.sh 
请选择:
1.备份数据库
2.清理日志文件
3.软件升级
4.软件回滚
5.删库跑路
请输入上面的数字1-5:1
备份成功
[root@ansible-salve1 shell]# 

6.2 循环

6.2.1 循环执行介绍

将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件

重复运行次数

  • 循环次数事先已知

  • 循环次数事先未知

常见的循环的命令:for,while

#循环的逻辑:程序先进行语句判断,如果为真则执行循环语句,然后再进行语句判断,直至语句判断失败才跳出
6.2.2 for循环

格式1:

# 第一种写法
for NAME [in words ...]; do commands;done# 第二种写法
for 变量 in 列表循环体
done# 第三种写法
for 变量 in 列表
do循环体
done

执行机制:

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

示例:

[root@iptables ~]# for i in 1 2 3 4 5 6 ; do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6
[root@iptables ~]# for i in {1..9}; do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
[root@iptables ~]# for i in {1..9..2}; do echo i=$i;done 
i=1
i=3
i=5
i=7
i=9
[root@iptables ~]# for i in `seq 10`;do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
[root@iptables ~]# sum=0; for i in `seq 10` ; do let sum+=i;done;echo sum=$sum
sum=55seq [选项] 结束值          # 生成 1 到 结束值 的序列(步长 1)
seq 起始值 结束值         # 生成 起始值 到 结束值 的序列(步长 1)
seq 起始值 步长 结束值    # 生成 起始值 到 结束值 的序列(步长为指定值)
seq 5   # 输出:1 2 3 4 5(每行一个数字)
seq 3 7   # 输出:3 4 5 6 7
seq 2 2 10   # 步长为 2,输出:2 4 6 8 10
seq 5 3 20   # 步长为 3,输出:5 8 11 14 17 20(20 是最后一个不超过 20 的数)

for循环列表生成方式

  • 直接给出列表

  • 整数列表

    {start..end}
    $(seq [start [step]] end)
  • 返回列表的命令

    $(COMMAND)
  • 使用glob,如:*.sh

    # 示例
    [root@iptables ~]# for file in `ls /var/log/*.log`;do ll $file;done
    -rw-------. 1 root root 0 6月   6 03:27 /var/log/boot.log
    -rw-r--r--. 1 root root 108685 6月   6 07:01 /var/log/dnf.librepo.log
    -rw-r--r--. 1 root root 199035 6月   6 07:01 /var/log/dnf.log
    -rw-r--r--. 1 root root 7703 6月   6 07:00 /var/log/dnf.rpm.log
    -rw-r--r--. 1 root root 540 6月   6 07:00 /var/log/hawkey.log
    -rw-------. 1 root root 4480 6月   6 00:19 /var/log/kdump.log
    -rw-r--r--. 1 root root 217 6月   3 21:49 /var/log/vmware-network.1.log
    -rw-r--r--. 1 root root 217 6月   3 21:40 /var/log/vmware-network.2.log
    -rw-r--r--. 1 root root 217 5月  26 17:25 /var/log/vmware-network.3.log
    -rw-r--r--. 1 root root 286 5月  26 01:56 /var/log/vmware-network.4.log
    -rw-r--r--. 1 root root 217 5月  26 01:51 /var/log/vmware-network.5.log
    -rw-r--r--. 1 root root 217 5月  22 03:45 /var/log/vmware-network.6.log
    -rw-r--r--. 1 root root 286 5月  22 03:44 /var/log/vmware-network.7.log
    -rw-r--r--. 1 root root 217 5月  21 17:26 /var/log/vmware-network.8.log
    -rw-r--r--. 1 root root 217 6月   6 00:19 /var/log/vmware-network.log
    -rw-------. 1 root root 34106 6月   6 05:59 /var/log/vmware-vmsvc-root.log
    -rw-------. 1 root root 8209 6月   6 00:20 /var/log/vmware-vmtoolsd-root.log
    -rw-------. 1 root root 12597 6月   6 00:20 /var/log/vmware-vmusr-root.log
    -rw-r--r--. 1 root root 23076 5月  21 17:37 /var/log/Xorg.9.log
  • 变量引用,如: $@, $#,$* 位置参数

    # 示例
    [root@iptables ~]# vim info9.sh #!/bin/bash
    sum=0
    for i in $@;dolet sum+=i
    done
    echo sum=$sum
    [root@iptables ~]# bash info9.sh 1 2 3
    sum=6

示例:打印99乘法表

[root@iptables ~]# vim info99cf.sh#!/bin/bash
for i in {1..9};dofor j in `seq $i`;doecho -e "${j}x$i=$[j*i]\t\c"doneecho
done
[root@iptables ~]# bash info99cf.sh
1x1=1
1x2=2   2x2=4
1x3=3   2x3=6   3x3=9
1x4=4   2x4=8   3x4=12  4x4=16
1x5=5   2x5=10  3x5=15  4x5=20  5x5=25
1x6=6   2x6=12  3x6=18  4x6=24  5x6=30  6x6=36
1x7=7   2x7=14  3x7=21  4x7=28  5x7=35  6x7=42  7x7=49
1x8=8   2x8=16  3x8=24  4x8=32  5x8=40  6x8=48  7x8=56 8x8=64
1x9=9   2x9=18  3x9=27  4x9=36  5x9=45  6x9=54  7x9=63 8x9=72   9x9=81
#另一种用()表达
#!/bin/bash
for i in {1..9};dofor ((j=1;j<=$i;j++));doecho -e "${j}x$i=$[j*i]\t\c"doneecho
done
~        

生产案例:将指定目录下的所有文件的后缀都进行备份

# 取出文件的后缀名
[root@ansible-salve1 shell]# echo nianling.sh | sed -En 's/^(.*)\.([^.]+)$/\2/p'
sh
# 取出文件的前缀名
[root@ansible-salve1 shell]# echo nianling.sh | sed -En 's/^(.*)\.([^.]+)$/\2/p'
nainling[root@ansible-salve1 shell]# vim info10.sh 
#!/bin/bash
DIR=/root/shell
cd $DIR
for FILE in *;doPRE=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\1/p'`cp $FILE $PRE.bak
done
[root@ansible-salve1 shell]# chmod +x info10.sh 

面试题:要求将目录中YYYY-MM-DD中的所有文件移动到YYYY-MM/DD目录下

 1.创建YYYY-MM-DD,当前日期一年前365天到目前一共365个目录,里面有10个文件,$random.log[root@ansible-salve1 ~]# vim mkdir.sh 
#!/bin/bash
for i in {1..365};doDIR=`date -d "-$i day" +%F`mkdir /data/test/$DIRcd /data/test/$DIRfor n in {1..10};dotouch $RANDOM.logdone
done  # 2.移动到YYYY-MM/DD下[root@ansible-salve1 ~]# mkdir mv.sh
#!/bin/bash
DIR=/data/test
cd $DIR
for DIR in *;doYYYY_MM=`echo $DIR | cut -d"_" -f1,2`DD=`echo $DIR |cut -d"_" -f3`[ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/nullmv $DIR/*  $YYYY_MM/$DD
done

面试题:扫描一个网段192.168.80.0/24,判断输入的网段中主机的在线状态,将在线的主机IP打印出来

 [root@ansible-salve1 ~]# vim ip.sh
#!/bin/bash
IP=192.168.80.
for i in `seq 254`;do{ping -c1 -W1 $IP$i &> /dev/null && echo $IP$i "is up"}&
done
wait              

格式2:

双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使用bash shell 实现C语言风格的变量操作l=10;((l++))

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do循环体
done

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次

  • 控制变量的修正表达式:每轮循环结束后会先进行控制变量修正运算,然后在做条件判断

示例:1到100的和

#!/bin/bash
sum=0
for i in {1..100} ;dolet sum+=i
done
echo sum=$sum
另一种方法
sum=0
for ((i=1;i<=100;i++));dolet sum+=i
done
echo sum=$sum                        

示例:九九乘法表

#!/bin/bash
for ((i=1;i<10;i++));dofor ((j=1;j<=i;j++));doecho -e "${j}x$i=$((j*i))\t\c"doneecho
done     

示例:打印三角形

# 直角三角形
#!/bin/bash
for((i=1;i<=10;i++));dofor((j=1;j<=2*i-1;j++));doecho -e '*\c'doneecho
done
[root@iptables ~]# bash number1-100.sh
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************# 等腰三角形
#!/bin/bash
for((i=1;i<=10;i++));dofor((k=1;k<=10-i;k++));doecho -e ' \c'donefor((j=1;j<=2*i-1;j++));doecho -e '*\c'doneecho
done
[root@iptables ~]# bash number1-100.sh*********************************************************************************
*******************
6.2.3 while循环
[root@ansible-salve1 shell]# help while
while: while 命令; do 命令; done只要测试成功即执行命令。只要在 `while' COMMANDS 中的最终命令返回结果为0,则展开并执行 COMMANDS 命令。退出状态:返回最后一个执行的命令的状态。
[root@ansible-salve1 shell]# 

格式:

while command; do commands;donewhile condition;do 循环体 done

说明:

condition:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“Ture”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:condition一般应该有循环控制变量;而此变量的值会在循环不断地被修正

进入条件:condition为True

退出条件:condition为False

无限循环

while true;do循环体
done

案例1:猜数游戏

#!/bin/bash
# 脚本生成一个100以内得随机数,提示用户猜数字,根据用户得输入,提示用户猜小了或猜大了,直至用户猜对脚本结束
# RANDOM为系统自带的系统变量,值为0~32767的随机数
# 使用取余算法将随机数变为1~100的随机数
num=$[RANDOM%100+1]
echo $num# 使用read 提示用户猜数字
# 使用if判断用户猜数字的大小关系:
# -eq(等于),-ne(不等于),-gt(大于),-ge(大于等于),-lt(小于),-le(小于等于)
while :
do read -p "数字炸弹在1~100的随机数,请选择数字:" nif [ $n -eq $num ];thenecho "恭喜,爆炸了"exitelif [ $n -gt $num ];thenecho "你猜大了,缩小范围为:0~$n之中"elseecho "你猜小了,缩小范围为$n~100之中"fi
done

案例2:DNS地址查询小程序

#!/bin/bash
# Function 基于DNS字典文件做一个中国电信DNS查询地址
IPPattern='^(\<([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\>\.){3}\<([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\>$'echo -e  "\e[34m --------------------------这是一个指定地区查询对应的DNS地址小程序-------------------------- \e[0m"
read -p "请输入你想查询的省份:" Province
read -p "请输入你想查询的城市: " City
echo -e  "\e[34m --------------------------你查询的地址为$Province$City ,正在为你查询,请稍后---------------------- \e[0m"
count=0
while read line
doprovince=$(echo $line | awk -F " " '{print $2}') #省份city=$(echo $line | awk -F " " '{print $3}') #城市if [[ "${city}" =~ ${IPPattern} ]];thencity=" "dns_address=$(echo $line | awk -F " " '{print $3}') #DNS地址elsedns_address=$(echo $line | awk -F " " '{print $4}') #DNS地址fiif [[ "$province" == $Province* ]];thenif [[ "$city" == $City* ]];thenecho -e "\e[32m $province $city地址为:$dns_address \e[0m"fifi
done < /root/re_study/Shell_Study/dns_list.txt
echo -e  "\e[34m --------------------------这就是以上对$Province$City的DNS地址查询,查询结果仅供参考---------------------- \e[0m"
exit 0

案例3:持续检查服务器负载

#!/bin/bash
#Fuction:持续检查服务器负载,如果负载过高则发送警告邮件
while true
doload=$(uptime | awk '{print $10}'|tr -s "," ' ')if [ $(echo "$load > 1.0" | bc) -eq 1 ];thenecho "Warning:Server load is high:$load" | mail -s "Server load warning" admin@example.comfisleep 300
done# echo "$load > 1.0" | bc 返回一个数字布尔值,0为True,1为False

案例4:持续检查应用日志

#!/bin/bash
#fuction:持续检查错误日志.如有错误日志则发送警告邮件
while true
doif grep -q "Error" /var/log/maillog;thenecho "Warning:Error log found in maillog" | mail -s "Application error" admin@example.comfisleep 600
done

案例5:持续同步文件夹

#!/bin/bash
#Fuction:持续将本地文件夹同步到远程服务器上
while true
dorsync -avz /local/folder/ user@remote-server:/remote/folder/sleep 3600
done

案例6:持续备份数据库

#!/bin/bash
#Fuction:持续备份数据库,每天备份一次
while true:
donow=$(date +"%Y-%m-%d-%H-%M-%S")mysqldump -uroot -p password mydb > "/backup/mydb_$now.sql"sleep 86400
done

案例7:逐行读取文件

#!/bin/bash
#Fuction:逐行读取文件
while read line
doecho $line
done < filename.txt

案例8:计数器循环

#!/bin/bash
#Fuction:计数器循环器
counter=0
while [ $counter -lt 10 ]
doecho $countercounter=$((counter+1))
done

案例9:监听文件变化

#!/bin/bash
while inotifywait -e modify filename
doecho "File Changed"sleep 10
done
6.2.4 until循环(不用)
[root@ansible-salve1 ~]# help until
until: until 命令; do 命令; done当测试不同过时执行命令。`until' COMMANDS 命令的最终命令返回状态不为 0 时,展开并执行 COMMANDS 命令。退出状态:返回最后一个执行的命令的状态。

格式:

until Commands;do commands;doneuntil condition;do循环体
done

说明:

进入条件:condition为false

退出条件:condition为ture

无限循环

until false;do循环体
done

案例1:等待网络服务启动

#!/bin/bash
#Function:等待MySQL服务启动
until mysqladmin ping > /dev/null 2>&1
dosleep 1systemctl start mariadb.service
done
echo "MySQL服务已经启动"`这个脚本等待 MySQL 服务启动,每隔 1 秒钟检查一次 MySQL 是否已经启动。当 MySQL 服务已经启动,脚本会输出一条消息。

案例2:等待用户输入

#!/bin/bash
#Function:等待用户输入
until read -p "请输入你的姓名: " name && [ -n "$name"]
#test -n 字符串字符串的长度非零
doecho "姓名不能为空"
done
echo "你的姓名为: $name"`这个脚本等待用户输入姓名,如果输入为空,则提示用户重新输入。当输入不为空时,脚本会输出用户输入的姓名

案例3:等待文件被删除

#!/bin/bash
# Function:等待文件被删除
until [ ! -f /path/to/file ]
dosleep 1
done
echo "文件已被删除"`这个脚本等待文件 /path/to/file 被删除,每隔 1 秒钟检查一次文件是否存在。当文件不存在时,脚本会输出一条消息

案例4:等待用户按下某个键

#!/bin/bash
until read -n1 -p "按下Y/y继续..." Key && [ "$Key" == "Y" ] || [ "$Key" == "y" ]
doecho "无效的键 $Key" 
done
echo "成功的继续"

案例5:等待某个端口开放

#!/bin/bash
#Function:等待端口开放
until nc -z localhost 8080
dosleep
done
echo "端口已开放"
`nc为namp工具所具备的命令,yum -y install nc

使用事项

  • until循环与while循环类似,不同之处在于until循环会一直循环,直到条件为真才会停止,而while会一直循环,直到条件为假才会停止

  • until 循环的语法格式为:until condition; do commands; done,其中 condition 表示循环条件,commands 表示需要执行的命令

  • until 循环中,可以使用 sleep 命令来延迟循环执行的时间,避免过度消耗系统资源

  • 在循环条件中使用 && 运算符可以将多个条件组合起来,只有当所有条件都满足时,循环才会结束。使用 || 运算符可以将多个条件组合起来,只有当任意一个条件满足时,循环才会结束

  • 在循环体中使用 break 命令可以提前结束循环

  • 在循环体中使用 continue 命令可以跳过当前循环,直接执行下一次循环

  • 在使用 until 循环时,要确保循环条件最终能够被满足,否则循环会一直执行下去,直到被手动中断

  • 在循环条件中,要使用测试命令或者其他命令来判断条件是否为真,比如使用 [] 或者 test 命令来进行比较或者测试

  • 在循环体中,可以使用各种 Shell 命令和语句来执行一系列操作,比如打印输出、文件操作、进程管理、网络通信等等

  • until 循环可以用于各种场景,比如等待某个操作完成、等待某个资源可用、等待某个服务启动等等,只需要根据具体需求编写相应的脚本即可

6.2.5 循环控制语句continue

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

格式:

for (());do循环体1...if command2;thencontinuefiCMDn....
done

示例: 结束最内层循环

#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && continueecho $jdoneecho ----------------------------donefor ((j=0;j<10;j++));doif [ $j -eq 5 ];thencontinuefiecho $i  $jdoneecho done

示例:结束外层循环

#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && continue  2echo $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh 
0
1
2
3
4
0
1
2
3
4
6.2.6 循环控制语句break

break[N]:提前结束第N层后的全部循环;最内层为第1层,默认为1

示例:结束内层循环

#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && breakecho $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh 
0
1
2
3
4
----------------------------
0
1
2
3
4
----------------------------

示例:结束外层循环

#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && break 2echo $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh 
0
1
2
3
4
[root@ansible-salve1 shell]# 

案例1:餐厅简易点餐系统

#!/bin/bash
sum=0
COLOR='echo -e \033[1;31m'
COLOR2='echo -e \033[1;32m'
END="\033[0m"while true;doecho -e "\033[33;1m\c"cat <<EOF1)鲍鱼2)满汉全席3)龙虾4)燕窝5)帝王蟹6)退出
EOF
echo -e "\033[0m"read -p "请点菜:" MENU
case $MENU in
1|4)$COLOR'菜价:$10'$ENDlet sum+=10;;
3|5)$COLOR'菜价:$20'$ENDlet sum+=20;;
2)$COLOR'菜价:$1000'$ENDlet sum+=1000;;
6)$COLOR2"你点菜的总价格是$sum"$ENDbreak;;
*)echo "没有这道菜,请重新点单";;
esac
$COLOR2"你点的菜总价格是$sum"$END
done`

案例2:持续监控应用状态

#!/bin/bash
#Fuction:基于While循环持续监控应用状态
while true
doif systemctl status sshd | grep -q "active (running)";thenbreakfisleep 10
done
6.2.7 shift与select技术

shift命令用于对参数的向左移动,通常用于在不知道传入参数个数的情况下依次遍历每个参数,然后进行相应的处理(常见与Linux中各种程序的启动脚本)。在扫描处理脚本程序的参数时,经常要用到shift命令

shift命令每执行一次,参数序列顺次左移一个位置,$#的值减1,用于分别处理每个参数,移出去的参数不可再用

注意:$#表示脚本后跟随的参数总的个数,$n可以获取脚本后跟随的第n个参数的值

[root@nqs22-tps22 ~]# type shift
shift 是 shell 内嵌
[root@nqs22-tps22 ~]# help shift
shift: shift [n]移位位置参数。重命名位置参数 $N+1、$N+2 ... 到 $1、$2 ...  如果没有给定 N,则假设为1.退出状态:返回成功,除非 N 为负或者大于 $#。

参考实例:依次读取输入的参数并打印参数个数:

[root@nqs22-tps22 ~]# vim run.sh
[root@nqs22-tps22 ~]# cat run.sh 
#!/bin/bash
while [ $# != 0 ];do
echo "第一个参数为:$1,参数个数为:$#"
shift
done
[root@nqs22-tps22 ~]# sh run.sh a b c d e f
第一个参数为:a,参数个数为:6
第一个参数为:b,参数个数为:5
第一个参数为:c,参数个数为:4
第一个参数为:d,参数个数为:3
第一个参数为:e,参数个数为:2
第一个参数为:f,参数个数为:1

参考实例:将参数从左到右逐个移动

[root@nqs22-tps22 ~]# vim shift.sh
#!/bin/bash
while [ $# -ne 0 ]
do 
echo "第一个参数为:$1 参数个数为:$#"
shift
done
[root@nqs22-tps22 ~]# sh shift.sh  Lily Lucy Jake Mike
第一个参数为:Lily 参数个数为:4
第一个参数为:Lucy 参数个数为:3
第一个参数为:Jake 参数个数为:2
第一个参数为:Mike 参数个数为:1
[root@nqs22-tps22 ~]# 

七、Shell中的数组

7.1 Shell数组的概念

数组是若干数据的集合,其中存放的每一份数据都称为元素。Shell不限制数组的大小,理论上可以存放无限量的数据,Shell数组元素的下标也是从0开始计数

获取数组中的元素要使用下标[ ],下标可以是一个整数,也可以是一个结果为整数的表达式;下标必须大于等于0

注意:Shell只支持一维数组,不支持多维数组

7.2 Shell数组的定义

7.2.1 数组的基本定义

Shell中,用小括号()来表示数组,数组元素之间用空格来分隔

#arrayname=(1 2 3 4 5)
`输出定义数组中的全部元素
#echo ${arrayname[*]}
#echo ${arrayname[@]}
`输出定义数组中的第一个元素
#echo ${arrayname[0]}
`输出定义数组中的第二个元素
#echo ${arrayname[1]}
`输出定义数组中的元素个数
#echo ${#arrayname[*]}
7.2.2 采用键值对的形式赋值

Shell中用小括号将变量括起来,同时采用键值对的形式赋值

#array2=([1]=one [2]=two [3]=three)
echo ${array2[*]} #输出定义数组的所有元素
echo ${array2[@]} #输出定义数组的所有元素
echo ${#array2[@]} #输出定义数组的元素个数
7.2.3 通过分别定义数组变量的方法来定义
#array3[1]=a
#array3[2]=b
#array3[3]=c
`输出定义数组中的全部元素
echo ${array3[@]}
`输出定义数组中的第一个元素
echo ${array3[1]}
7.2.4 动态定义数组数量

动态地定义数组变量,并使用命令的输出结果作为数组的内容

# mkdir -p /array
# touch /array/{1..5}.txt
# ls /array
# array4=($(ls /array))
# echo ${array4[*]}
# echo ${array4[@]}
# echo ${#array4[*]}

7.3 Shell数组的打印

  • 打印单个数组元素: ${数组名[下标]} 。当未指定数组下标时,下标默认从0开始

  • 打印全部数组内容:${数组名[@]}或 ${数组名[*]}

  • 打印数组元素的个数:${#数组名[@]}或 ${#数组名[*]}

7.4 Shell数组的赋值

如果下标不存在,则自动添加一个新的元素;如果下标存在,则覆盖原来的值

7.5 Shell数组的拼接合并

所谓Shell数组拼接(数组合并),就是将两个数组连接成一个数组

拼接数组的思路是:先利用@或者*,将数组扩展成列表,然后再合并到一起,具体格式如下:

array_new=(${array1[@]} ${array2[@]})
array_new=(${array1[*]} ${array2[*]})
`两种方式是等价的,选择其一即可。其中,array1 和 array2 是需要拼接的数组,array_new 是拼接后形成的新数组。
[root@iptables ~]# echo ${arr2[@]} ${arr1[*]}
a b c d e f g h i j k l 1 2 3 4 5 6 7 8 9
[root@iptables ~]# arr3=(${arr2[@]} ${arr1[*]})
root@iptables ~]# echo ${arr3[@]}
a b c d e f g h i j k l 1 2 3 4 5 6 7 8 9

示例:

#!/bin/bash
array1=(1 2 3 4 5)
array2=("https://www.baidu.com" "https://www.hehao.online" "https://www.taobao.com")
array_new=(${array1[*]} ${array2[*]})
echo ${array_new[@]}--->结果
1 2 3 4 5 https://www.baidu.com https://www.hehao.online https://www.taobao.com

7.6 Shell删除数组元素

Shell中,使用unset关键字来删除数组元素,具体格式如下:

unset array_name[index]
`其中,array_name表示数组名,index表示数组下标unset array_name 
`删除整个数组

7.7 获取数组某范围的元素

Shell中直接通过${数组名[@/*]:起始位置:长度}获取数组给定范围内元素,返回字符串,中间用空格分开

#!/bin/bash
array=(yoona lucy tom)
echo ${array[*]}
echo ${array[*]:1:2}
echo ${array[@]:0:2}
-->结果为:
yoona lucy tom
lucy tom
yoona lucy

7.8 数组元素的替换

${数组名[@/*]/查找字符/替换字符}该操作不会改变原先数组内容,如果需要修改,使用覆盖

#!/bin/bash
array=(yoona lucy tom)
echo ${array[@]/lucy/lily}
echo ${array[*]}--->结果为:
yoona lily tom
yoona lucy tom
#不会改变原先数组内容
[root@iptables ~]# arr4=(123 456 789)
[root@iptables ~]# echo ${arr4[@]/456/741}
123 741 789
[root@iptables ~]# echo ${arr4[*]}
123 456 789
[root@iptables ~]# echo ${arr4[@]}
123 456 789

7.9关联数组

Bash支持关联数组,它可以使用字符串作为数组索引,有时候采用字符串索引更容易理解

7.9.1 定义关联数组

首先需要使用声明语句declare将一个变量声明为关联数组

# declare -A assArray
[root@iptables ~]# declare -A arr4
[root@iptables ~]# arr4=(a b c)
bash: arr4: a: 为关联数组赋值时必须使用下标
bash: arr4: b: 为关联数组赋值时必须使用下标
bash: arr4: c: 为关联数组赋值时必须使用下标
[root@iptables ~]# arr4=([0]=a [1]=b [2]=c)
[root@iptables ~]# echo ${arr4[@]}
a b c

声明后,可以有两种方法将添加到关联数组中

1.利用内嵌索引-值列表的方法

# assArray=([lucy]=beijing [yoona]=shanghai)
# echo ${assArray[lucy]}--->结果为:
beijing
[root@iptables ~]# declare -A arr4
[root@iptables ~]# arr4=([name]=hou [age]=25)
[root@iptables ~]# echo ${arr4[name]}
hou

2.使用独立的索引-值进行赋值

[root@iptables ~]# arr4[name]=haojunkai
[root@iptables ~]# arr4[age]=1
[root@iptables ~]# echo ${arr4[@]}
haojunkai 1# assArray[lily]=shandong
# assArray[sunny]=xian
# echo ${assArray[sunny]}
-->结果为:xian
7.9.2 列出数组索引

每一个数组都有一个索引用于查找。使用${!数组名[@/*]}获取数组的索引列表

# echo ${!assArray[*]}
# echo ${!assArray[@]}[root@iptables ~]# echo ${!arr4[@]}
name age
7.9.2 获取所有键值对
#! /bin/bash
declare -A cityArray
cityArray=([yoona]=beijing [lucy]=shanghai [lily]=shandong)
for key in ${!cityArray[*]}
doecho "${key} come from ${cityArray[$key]}"
done--->结果为:
lily come from shandong
yoona come from beijing
lucy come from shanghai

7.10 mapfile命令

7.10.1 mapfile命令介绍

mapfile命令用于从标准输入读取行并赋值到数组

7.10.2 mapfile语法结构
mapfile [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]

选项介绍

`-d delim :将delim设为行分隔符,代替默认的换行符
`-n count :从标准输入中获取最多的count行,如果count为0那么获取全部
`-O origin:从数组下标为origin的位置开始赋值,默认的下标为0
`-s count: 跳过对前count行的读取
`-t : 读取时移除分隔符delim(默认为换行符)
`-u fd:从文件描述符fd中读取
`-C callback:每当读取了quantum行时,调用callback语句
`-c quantum:设定读取的行数为quantum
`array(可选):用于输出的数组名称。如果没有指定数组名称,那么会默认写入到变量名为MAPFILE的数组中# 如果使用-C时没有同时使用-c指定quantum的值,那么quantum默认为5000。
#当callback语句执行时,将数组下一个要赋值的下标以及读取的行作为额外的参数传递给callback语句。
#如果使用-O时没有提供起始位置,那么mapfile会在实际赋值之前清空该数组
7.10.3 使用示例
`dns_list.txt
www.baidu.com
www.taobao.com
www.douyin.com
www.4399.com[root@localhost Shell_Study]# mapfile array < dns_list.txt 
[root@localhost Shell_Study]# echo ${#array[*]}
4
[root@localhost Shell_Study]# echo ${array[#]}
[root@localhost Shell_Study]# echo ${array[@]}
www.baidu.com www.taobao.com www.douyin.com www.4399.com[root@iptables ~]# cat /etc/passwd | cut -d: -f1 > 1.txt
[root@iptables ~]# cat 1.txt 
[root@iptables ~]# mapfile arr7< 1.txt 
[root@iptables ~]# echo ${arr7[@]}
root bin daemon adm lp sync shutdown halt mail operator games ftp nobody dbus systemd-coredump systemd-resolve tss polkitd geoclue unbound rtkit pipewire pulse dnsmasq qemu clevis usbmuxd gluster rpc chrony setroubleshoot saslauth libstoragemgmt sssd cockpit-ws cockpit-wsinstance flatpak colord rpcuser gdm gnome-initial-setup sshd avahi tcpdump hou apache hehao

八、Shell中的函数

8.1 Shell函数的定义

Shell函数的本质是一段可以重复使用的脚本代码,这段代码被提前编好了,放在了指定位置,使用时直接调用即可

Shell 中的函数和C++、Java、Python、C# 等其它编程语言中的函数类似,只是在语法细节有所差别。

Shell 函数定义的语法格式如下:

function name() {statements[return value]
}

对各个部分的说明:

  • function是 Shell 中的关键字,专门用来定义函数;

  • name是函数名;

  • statements是函数要执行的代码,也就是一组语句;

  • return value表示函数的返回值,其中 return 是 Shell 关键字,专门用在函数中返回一个值;这一部分可以写也可以不写,会把当前的值存在内存中,返回当前程序执行的位置上。

{ }包围的部分称为函数体,调用一个函数,实际上就是执行函数体中的代码。

函数定义的简化写法

1.
name() {statements[return value]
}
2.
function name() {statements[return value]
}

8.2 Shell函数的调用

调用 Shell 函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可:

name

如果传递参数,那么多个参数之间以空格分隔:

name param1 param2 param3

不管是哪种形式,函数名字后面都不需要带括号。

和其它编程语言不同的是,Shell 函数在定义时不能指明参数,但是在调用时却可以传递参数,并且给它传递什么参数它就接收什么参数。

8.3 Shell函数详解

Shell中的函数在定义时不能指明参数,但是在调用时却可以传递参数。函数参数是Shell位置参数的一种,在函数内部可以使用$n来接收,例如:$1表示第一个参数,$2表示第二个参数,依次类推

除了$n,还有另外三个比较重要的变量:

  • $#可以获取传递的参数的个数;

  • $@或者$*可以一次性获取所有的参数

扩展:在Shell中 @ 与 @与 @与*的区别

在Shell脚本中,$*和$@是Shell脚本的特殊变量,作用都是获取传递给脚本或函数的所有参数$@与$*的相同点:当它们没有被双引号包裹时,两者是没有区别的,都代表一个包含接收到的所有参数的数组,各个数组元素都是传入的独立参数$@与$*的不同点:当被双引号包裹时,$@仍为一个数组,而$*会将所有参数整合成一个字符串
[root@iptables ~]# vim qiuhe.sh
#位置变量
#!/bin/bash
x=$1
y=$2
sum(){for i in `seq $x $y`dolet rs+=idonereturn $rs
}
sum 
echo $rs
[root@iptables ~]# bash qiuhe.sh 1 100
5050

九、Shell高级

9.1 echo 带颜色输出

9.1.1 echo命令介绍
`语法:
echo [-ne] [字符串]/echo [--help] [--version]
补充说明:echo会将输入的字符串送往标准输出。输出的字符串加以空白字符隔开,并在最后加上换行符
参数:
-n:不要再最后自动换行
-e:打开反斜杠ESC转义。若字符串出现以下字符,则特别加以处理,而不会将它当成一般文字输出:\a:发出警告声\b:删除前一个字符\c:最后不加上换行符号\f:换行光标仍停留在原来的位置\n:换行且光标移至行首\r:光标移至行首,但不换行\t:插入tab\v与\f相同\\:插入\\nnn:插入nnn(八进制)所代表的ASCII字符
-E:取消反斜杠ESC转义
-help:显示帮助
-version:显示版本信息[root@iptables ~]# echo -e "${arr6[@]} \n"
haorenyishengpingan
9.1.2 使用echo达到输出有颜色字体的效果
echo -e "\e[背景底色号码;字体颜色号码m 文本内容 \e[0m"
echo -e "\033[背景;字体颜色m 字符串\033[0m"`扩展:使用printf显示颜色字体
printf "\e[背景底色号码;字体颜色号码m 格式化输出符号 \e[0m" "文本内容"
显示黑色背景绿色字体
printf "\e[40;32m %s\n \e[0m" "hello world"

对应的颜色范围:

字体背景颜色范围 40-47
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色字基本颜色号码 30-37
30:黑
31:红
32:绿
33:黄
34:蓝色
35:紫色
36:深绿
37:白色字体高亮颜色号码 90-97
90:黑
91:红
92:绿
93:黄
94:蓝色
95:紫色
96:深绿
97:白色字背景颜色范围 40-47
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色

还有一些特殊的颜色

\33[0m 关闭所有属性
\33[1m 设置高亮度
\33[4m 下划线
\33[5m 闪烁
\33[7m 反显
\33[8m 消隐
\33[30m — \33[37m 设置前景色
\33[40m — \33[47m 设置背景色
\33[nA 光标上移n行
\33[nB 光标下移n行
\33[nC 光标右移n行
\33[nD 光标左移n行
\33[y;xH设置光标位置
\33[2J 清屏
\33[K 清除从光标到行尾的内容
\33[s 保存光标位置
\33[u 恢复光标位置
\33[?25l 隐藏光标
\33[?25h 显示光标

9.2 Shell的八大扩展功能

9.2.1 花括号

在shell脚本中,可以使用括号对字符串进行扩展,我们可以在一对花括号中包含一组以分号分隔的字符串或者字符串序列组成一个字符串扩展,注意最终输出结果以空格分隔,使用该扩展花括号不可以被引号引用,花括号的数量必须是偶数个

[root@localhost ~]# echo {1,5} #对字符串进行扩展
1 5
[root@localhost ~]# echo {hello,world} #对字符串进行扩展
hello world
[root@localhost ~]# echo {a..z} #对字符串序列进行扩展
a b c d e f g h i j k l m n o p q r s t u v w x y z
#字符串后面可以跟一个步长整数,默认为1或-1
[root@localhost ~]# echo {a..z..2}
a c e g i k m o q s u w y
[root@localhost ~]# echo {a..z..3}
a d g j m p s v y
[root@localhost ~]# echo {1..9..3}
1 4 7
[root@localhost ~]# echo {1..9..2}
1 3 5 7 9
[root@localhost ~]# echo "{a..z}" #花括号扩展不能使用引号
{a..z}
[root@localhost ~]# echo t{i,o}p #花括号前后都可以添加可选字符串
tip top
[root@localhost ~]# echo t{o,e{a,m}}p #花括号支持嵌套
top teap temp
#花括号批量操作
[root@localhost ~]# mkdir -p t{o,e{a,m}}p
[root@localhost ~]# touch t{o,e{a,m}}p/{a,b,c,d}e.txt
touch 命令实际接收的参数是上述 12 个文件路径
top ae.txt
..
tamp ae.txt
..
temp ae.txt[root@iptables ~]# echo  {1..100..2} 
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99#创建多级目录 -p 递归 -m 设置目录权限 -v 创建的详细过程
mkdir -pv a/b/c d/e   # 输出:# mkdir: created directory 'a'# mkdir: created directory 'a/b'# mkdir: created directory 'a/b/c'# mkdir: created directory 'd'# mkdir: created directory 'd/e
[root@iptables ~]# mkdir -p dir1/{dir2/{d1,d2},dir3/{d1,d2}}
[root@iptables ~]# tree dir1
dir1
├── dir2
│   ├── d1
│   └── d2
└── dir3├── d1└── d2
9.2.2 波浪号

波浪号在Shell脚本中默认代表当前用户家目录

[root@localhost /]# echo ~ #显示当前用户的家目录
/root
[root@localhost /]# echo ~/elk
/root/elk
[root@localhost /]# echo ~elk #显示特定用户的家目录,该用户必须存在
/home/elk
[root@localhost /]# echo ~+ #显示当前工作目录
/
[root@localhost /]# echo ~- #显示前一个工作目录
/root
9.2.3 变量替换

在Shell脚本中我们会使用 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆,如果 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆,如果 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆,如果后面是位置变量且多余一个数字,必须使用{}

[root@localhost ~]# a="hello word"
[root@localhost ~]# echo $a
hello word
[root@localhost ~]# echo ${a}
hello word
[root@localhost ~]# b=a
[root@localhost ~]# echo ${b} #直接返回变量的值
a
[root@localhost ~]# echo ${!b} #间接引用a变量的值
hello word
[root@localhost ~]# c=b
[root@localhost ~]# echo ${!c} #尽可以实现一层简介引用
a

变量替换操作还可以测试变量是否存在及是否为空,若变量不存在或为空,则可以为变量设置一个默认值

Shell脚本支持多种形式的变量测试与替换功能,如下表所示

语法格式功能描述
${变量:-关键字}如果变量未定义或为空,则返回关键字,否则返回变量值
${变量:=关键字}如果变量未定义或为空,则将关键字赋值给变量,并返回结果,否则直接返回变量值
${变量:?关键字}如果变量未定义或为空,则通过标准错误显示包含关键字的错误信息,否则返回变量值
${变量:+关键字}如果变量未定义或为空,则直接返回空,否则返回关键字
一、[root@localhost ~]# echo $bb
[root@localhost ~]# echo ${bb:-bbb}
bbb
[root@iptables ~]# echo ${bb:-bbb}
c
[root@iptables ~]# bb=c
[root@iptables ~]# echo ${bb:+bbb}
bbb二、[root@localhost ~]# echo $bb
[root@localhost ~]# echo ${bb:=bbb}
bbb
[root@localhost ~]# echo $bb
bbb
[root@iptables ~]# bb=c
[root@iptables ~]# echo ${bb:=bbb}
c三、[root@iptables ~]# echo $bb
[root@iptables ~]# echo ${bb:?bbb}
-bash: bb: bbb
[root@iptables ~]# bb=a
[root@iptables ~]# echo ${bb:?bbb}
a四、[root@iptables ~]# echo ${bb:+bbb}
[root@iptables ~]# bb=c
[root@iptables ~]# echo ${bb:+bbb}
bbb

此外,变量替换还有非常实用的字符串切割与掐头去尾功能

偏移量起始值为0

语法格式功能描述
${变量:偏移量}从变量的偏移量位置开始,切割截取变量的值到结尾
${变量:偏移量:长度}从变量的偏移量位置开始,切割截取特定长度的变量值
${变量#关键字}用关键字对变量进行模式匹配,从左到右删除匹配到的内容,关键字可以用*表示,使用#匹配时为最短匹配
${变量##关键字}用关键字对变量进行模式匹配,从左到右删除匹配到的内容,关键字可以用*表示,使用##匹配时为最长匹配
${变量%关键字}用关键字对变量进行模式匹配,从右到左删除匹配到的内容,关键字可以用*表示,使用%匹配时为最短匹配
${变量%%关键字}用关键字对变量进行模式匹配,从右到左删除匹配到的内容,关键字可以用*表示,使用%%匹配时为最长匹配

这几种变量替换方式,都不会改变原变量的值

#!/bin/bash
#!/bin/bash
home="hello world linux java spring"
echo ${home:2}
#llo world linux java spring
echo ${home:2:5}
#llo w
echo ${home#he}
# lo world linux java spring
echo ${home#*ja}
# va spring
echo ${home##*r}
# ing
echo ${home%ing}
# hello world linux java spr
echo ${home%i*}
# hello world linux java spr
echo ${home%%i*}
# hello world l

变量内容的统计与替换

语法格式功能描述
${!前缀字符*}查找以指定字符开头的变量名称,变量名之间使用IFS分隔
${!前缀字符@}查找已指定字符开头的变量名称,@在引号中将被扩展为独立的单词
${!数组名称[*]]}列出数组中所有下标,*在引号中被扩展为一个整体
${!数组名称[@]]}列出数组中所有下标,@在引号中被扩展为独立的单词
${#变量}统计变量的长度,变量可以是数组
${变量/旧字符串/新字符串}将变量中的旧字符串替换为新字符串,仅替换第一个
${变量//旧字符串/新字符串}将变量中的旧字符串替换为新字符串,替换所有
${变量^匹配字符}将变量中的小写替换为大写,仅替换第一个
${变量^^匹配字符}将变量中的小写替换为大写,替换所有
${变量,匹配字符}将变量中的大写替换为小写,仅替换第一个
${变量,匹配字符}将变量中的大写替换为小写,替换所有
9.2.4 命令替换
#我们可以通过$(命令)或`命令`方式实现替换[root@localhost /]# echo -e "$(date +%Y-%m-%d;uptime)"
2021-07-1423:47:56 up 29 days,  4:12,  1 user,  load average: 0.36, 0.18, 0.14
[root@localhost /]# echo "系统登录人数:$(who | wc -l)"
系统登录人数:1
[root@localhost /]# echo "系统登录人数:`who | wc -l`"
系统登录人数:1
9.2.5 算数替换

通过算数替换阔可以进行算数计算并返回计算结果,算数替换扩展的格式为$(()),也可以使用$[]的方式,算数扩展支持嵌套

[root@localhost /]# echo $((i++))
1
[root@localhost /]# echo $((++i))
3
[root@localhost /]# echo $((--i))
2
[root@localhost /]# echo $((18%5)) #取余
3
[root@localhost /]# echo $((2**3)) #幂运算
8
[root@localhost /]# echo $((2>3))
0
[root@localhost /]# echo $((2<3))
1
[root@localhost /]# echo $((2!=3))
1
9.2.6 进程替换

进程替换将进程的返回结果通过命令管道的方式传递给另一个进程

语法格式为:<(命令)或者>(命令)

一旦使用了进程替换功能,系统将会在/dev/fd目录下创建文件描述符文件,通过该文件描述符将进程的输出结果传递给其他进程

Linux系统中可以使用管道将前一个命令输出重定向到文件,但是一旦使用了重定向输出到文件,输出结果无法在屏幕上显示

[root@localhost /]# ls /etc/*.conf > ~/conf.log
[root@localhost /]# cat ~/conf.log
/etc/asound.conf
...后续内容省略...

使用tee命令既可以重定向到文件,又可以在屏幕上显示输出结果

[root@localhost /]# ls /etc/*.conf |  tee ~/conf.log
/etc/dracut.conf
...后续内容省略...
[root@localhost /]# cat ~/conf.log
/etc/dracut.conf
...后续内容省略...
9.2.7 单词切割

单词切割又叫做分词,Shell使用IFS变量进行分词处理。如果没有自定义IFS变量,默认为空格,Tab制表符,换行符

[root@localhost ~]# read -p "输入:" x y z
输入:1 2 3
[root@localhost ~]# echo $x
1
[root@localhost ~]# echo $y
2
[root@localhost ~]# echo $z
3
#自定义IFS变量的值	
[root@localhost ~]# IFS=$',' read -p "输入:" x y z
输入:4,5,6
[root@localhost ~]# echo $x
4
[root@localhost ~]# echo $y
5
[root@localhost ~]# echo $z
6
[root@iptables ~]# IFS=$',' read -p "输出:" x y z
输出:4 5 6
[root@iptables ~]# echo $y[root@iptables ~]# echo $x
4 5 6
9.2.8 路径替换

除非使用set -f禁用路径替换,否则bash会在路径和文件名中搜索*、?和[符号,如果找到了这些符号则进行模式匹配的替换。

使用shopt命令时开启了nocaseglob选项,则bash的进行模式匹配时不区分大小写,默认区分大小写。

此外还可以开启extglob选项,让bash支持扩展通配符。

shopt命令-s选项可以开启特定的Shell属性,-u选项可以关闭特定的Shell属性

[root@localhost shopt]# touch {a,A,b,B}.txt
[root@localhost shopt]# ls a.txt
a.txt
[root@localhost shopt]# shopt -s nocaseglob
[root@localhost shopt]# shopt nocaseglob
nocaseglob      on
[root@localhost shopt]# ls B*
b.txt  B.txt
[root@localhost shopt]# ls a*
a.txt  A.txt
[root@localhost shopt]# shopt -u nocaseglob
[root@localhost shopt]# ls a*
a.txt
[root@localhost shopt]# shopt -s extglob
[root@localhost shopt]# ls !(a.txt|b.txt)
A.txt  B.txt
[root@localhost shopt]# shopt -u extglob
[root@localhost shopt]# ls !(a.txt|b.txt)
-bash: !: event not found

basenamedirname:

basename:可以获取一个路径中的文件名

dirname:仅保留路径,删除文件名

[root@iptables ~]# basename /root/inof3.sh
inof3.sh
[root@iptables ~]# dirname /root/inof3.sh
/root

9.3 Shell中的expect

9.3.1 expect介绍
	expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率。
`expect 的安装
yum -y install expect
9.3.2 expect命令
`语法格式:
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
常用选项
-c :从命令行执行expect脚本,默认expect是交互地执行的
-d :可以输出调试信息expect中相关的命令
spawn:启动新的进程
send:用于向进程发送字符串
expect:从进程接收字符串
interacr:允许用户交互
exp_continue:匹配多个字符串在执行动作后加此命令
set timeout 30:设置超时时间timeout为30s,expect命令阻塞超时时会自动往下继续执行。将timeout配置为-1时表示expect一直阻塞直到与期待的字符串匹配上才继续往下执行。超时时间timeout默认为10s。
[lindex $argv n]:可以在脚本中使用该命令获取在脚本执行时传入的第n个参数。这里argv为传入的参数,另外argv为传入的参数,另外argc表示传入参数的个数,$argv0表示脚本名字。另外我们也可以使用[lrange $argv sn en]命令获取第sn到第en个参数。
9.3.3 expect最常用的语法(tcl语言:模式-动作)
9.3.3.1.expect的单分支语法
[root@localhost ~]# expect 
expect1.1> expect "hi" {send "say hi\n"} #捕捉用户输入的hi,然后给用户发送"say hi\n"
hi  #这一行是我输入的,由于被我上面定义的语句捕捉到了,下面一行的输出信息就是我之前自定义的
say hi
expect1.2>  # 如果不想使用该程序了,可以通过输入"exit"或者ctrl+D来正常退出交互式界面
9.3.3.2.expect的多分支语法
[root@localhost ~]# expect
expect1.1> expect "hi" {send "say hi\n"} "bye" {send "byebye\n"}
bye
byebye
9.3.4 通过实际脚本来理解expect
#!/usr/bin/expect
# 使用expect来解释该脚本
set timeout 30 
# 设置超时时间,单位为秒,默认情况下是10s
set host "njdx01.91vps1.com" 
#设置远程连接的主机
set port "31026"  
#设置SSH端口变量
set username "root"
set pass "Gizakps@1289" 
#设置密码
spawn ssh $username@$host -p$port 
# spawn:是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。
# 它主要的功能是给ssh运行进程加个壳,用来传递交互指令
expect "*password*" {send "$pass\r"} 
#这里的expect也是expect的一个内部命令,这个命令的意思是判断上次输出结果里是否包含"password"的字符串,如果有则立即返回;否则就等待一段时间后返回
interact 
#执行完后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了`特别提醒:该脚本不能通过bash 脚本名来运行,而是需要通过
#chmod +x 脚本名
#./脚本名

在上述的示例中,设计到expect中一个非常重要的概念–模式-动作;即上述expect "*password*" {send "$password\r"}这句代码表达出来的含义。简单的说就是匹配一个模式,就执行对应的做东;匹配到password字符串,就输入密码。你可能回看到这样的代码

expect {"password"{send "$pass\r"exp_continue}eof{send "eof"}
}

其中exp_continue表示循环式匹配,通常匹配之后都会退出语句,但如果有exp_continue则可以不断循环匹配,输入多条命令,简化写法。

很多时候,我们需要传递参数到脚本中,现在通过下面这段代码来看看如何在expect中使用参数:

#!/usr/bin/expect
if {$argv < 3} {puts "Usage:cmd <host> <username> <password>"exit 1
}
set timeout -1
# 表示expect一直阻塞直到与期待的字符串匹配上才继续往下运行
set host [lindex $argv 0]
set username [lindex $argv 1]
set port [lindex $argv 2]
set pass [lindex $argv 3]
spawn ssh $username@$host -p$port
expect "*password*" {send "$pass\r"}
interact# 在expect,$argc表示参数个数,而参数值存放在$argv中,比如取第一个参数就是[index $argv 0],以此类推`特别提醒:该脚本不能通过bash 脚本名来运行,而是需要通过
#chmod +x 脚本名
#./脚本名 参数1 参数2 参数3 参数4
9.3.5 实例

1.传参式登录指定主机

#!/usr/bin/expect -f
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set timeout 10
spawn ssh root@$ip
expect {
"*yes/no" { send "yes\r";exp_continue}//第一次ssh连接会提示yes/no,自动发送yes
"*password:" { send "$pass\r"}
}
interact`chmod +x 脚本名
`./脚本名 参数1 参数2

2.利用expect批量ssh互信

#!/bin/bash
# 判断id_rsa密钥文件是否存在
if [ ! -f ~/.ssh/id_rsa ];thenssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa# -t 
elseecho "id_rsa has created ..."
fi# 分发到各个节点,这里分发到host文件中的主机中
while read line 
douser=`echo $line | cut -d " " -f 2`ip=`echo $line | cut -d " " -f 1`passwd=`echo $line | cut -d " " -f 4`port=`echo $line | cut -d " " -f 3`expect <<EOFset timeout 10spawn ssh-copy-id $user@$ip -p$portexpect {"*yes/no" { send "yes\n";exp_continue }"password" { send "$passwd\n" }}expect "password" { send "$passwd\n" }
EOF
done < /root/host.txt`/root/host.txt
njdx01.91vps1.com root 21062 Gizakps@1289
xadx01.91vps1.com root 26154 Gizakps@1289
dllt01.91vps1.com root 20270 Gizakps@1289
9.4 Shell中的信号捕捉trap(不用了解)

Linux常用命令trap用于指定在接收信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作

9.4.1 Linux信号

信号(IPC)最初是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。简单来说信号是操作系统(内核)响应某些条件而产生的一个事件(给进程)。进程之间无法通信,可以使用信号来解决。

信号是由于某些错误条件而生成的,如内存段冲突,浮点处理器错误或非法指令等。他们由 shell 和终端处理器生成来引起中断,他们还可以作为在进程中传递消息或修改行为的一种方式,明确地由一个进程发送给另外一个进程

Linux常见的信号有:

信号
1SIGHP挂起进程
2SIGINT终止进程
3SIGQUIT停止进程
9SIGKILL无条件终止进程
15SIGTERM尽可能终止进程
17SIGSTOP无条件停止进程,但不是终止进程
18SIGTSTP停止或暂停进程,但不终止进程
19SIGCONT继续运行停止的进程

ctrl+c组合键会产生SIGINT信号,ctrl+z组合键会产生SIGTSTP信号

kill -0 pid不发送任何信号,但是系统会进行错误检查。该命令可以用来检查一个进程是否存在,若存在,即进程正在运行,执行echo $?会返回0.若不存在,即进程已停止运行,执行echo $?会返回1

9.4.2 trap命令

trap命令允许你来指定Shell脚本要监视并拦截的Linux信号

`语法格式:
trap commands signal1 [signal2 signal3 ....]
# 如果当前脚本进程收到上述signals信号中的一个,就会执行commands命令trap '' 信号 # 忽略信号的操作
trap '-' 信号 # 恢复原信号的操作
trap -p # 列出自定义信号操作
trap finish EXIT #当脚本退出时.执行finish函数,当然这个"finish"这个名字可以自定义

脚本示例

#!/bin/bash
trap "echo 'Sorry~I have trapped Ctrl+c'" SIGINT
echo "This is a test script"
count=1
while [ $count -le 10 ];doecho "Loop $count"sleep 1count=$[ $count +1 ]
done
echo "The end"

十、Shell编程之正则表达式

1、正则表达式

1.正则表达式概述

  正则表达式通常用于判断语句中,用来检查某一字符串是否满足某一格式。

  正则表达式是由普通字符元字符组成。普通字符包括小写字母、数字、标点符号及一些其他符号。元字符是指在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。

2.正则表达式的分类

  正则表达式根据从POSIX BRE或者POSIX ERE标准可以分为基本正则表达式扩展正则表达式

基本正则表达式

支持的工具:grepegrepsedawk,注意grep要配合-E或者-P使用。

元字符含义及用法
\转义字符,用于取消特殊符号的含义,例: \!\n\$
^匹配字符串开始的位置,例:^a^the^#^[a-z]
$匹配字符串结束的位置,例: word$^$匹配空行
.匹配除\n之外的任意的一个字符,例: go.dg..d。如果想要匹配包含\n字符可以使用 [.\n]
*匹配前面子表达式0次或者多次,例: goo*dgo.*d
[list]匹配list列表中的一个字符,例: go[ola]d[abc][a-z][a-z0-9][0-9]匹配任意一位数字
[^list]匹配任意非list列表中的一个字符,例:[^0-9][^A-Z0-9][^a-z]匹配任意一位非小写字母
\{n\}匹配前面的子表达式n次,例: go\{2\}d[0-9]\{2\}匹配两位数字
\{n,\}匹配前面的子表达式不少于n次,例: gol{2,l}d[0-9]\{2,\}匹配两位及两位以上数字
\{n,m\}匹配前面的子表达式n到m次,例 : go\{2,3\}d[0-9]\{2,3\}匹配两位到三位数字
grep加-P注: egrepawk使用{n}{n,}{n,m}匹配时 {} 前不用加 \
\w匹配包括下划线的任何单词字符。
\W匹配任何非单词字符。等价于[^A-Za-z0-9_]。包括空格
\d匹配一个数字字符。
\D匹配一个非数字字符。等价于[^0-9]
\s空白符。
\S非空白符。

扩展正则表达式

支持的工具:egrepawk,注意:使用grep要配合-E或者-P使用,sed要配合-r使用。

元字符含义及用法
+匹配前面子表达式1次以上,例: go+d,将匹配至少一个o,如godgoodgoood
?匹配前面子表达式0次或者1次,例: go?d,将匹配gdgod
()将括号中的字符串作为一个整体,例1: g(oo)+d,将匹配oo整体1次以上,如goodgooood
|以或的方式匹配字符串,例:g(oo|la)d,将匹配good或者 glad

3.正则表达式的使用

* ^ $的用法

[root@localhost opt]# cat testfile6
gd
god
good
goood
gooood
goooabcd
gooooood
gooooooodddd
abccba
abccba123
oooogooood
ooooogd
gold
goad
#查询一串字符中有goo*d字样,且o有0个或多个,此字符串可以不在开头或结尾
[root@localhost opt]# grep "goo*d" testfile6 	
god
good
goood
gooood
gooooood
gooooooodddd
oooogooood
#查询以g开头d结尾,中间的o有0个或多个
[root@localhost opt]# grep "^goo*d$" testfile6 	
god
good
goood
gooood
gooooood

[] [^]的用法

#查询一个字符,其中有一段以go开头d结尾,中间的o、l、a这几种字符中的一种
[root@localhost opt]# grep “go[ola]d” testfile6 	
good
gold
goad
#查询一个字符,其中有一段以go开头d结尾,中间的o、l、a这几种字符中的一个或多个
[root@localhost opt]# grep “go[ola]*d” testfile6 
god
good
goood
gooood
gooooood
gooooooodddd
oooogooood
gold
goad
#匹配除了a-g开头的字符
[root@localhost opt]# grep "^[^a-g]" testfile6 	
oooogooood
ooooogd
[root@iptables ~]# cat test1.txt | grep "[^abc]"
gd
god
good
goood
gooood
goooabcd
gooooood
gooooooodddd
abccba123
oooogooood
ooooogd
gold
goad

\{n\} \{n,\} 的用法

#匹配good字符,其中\{2\}只会匹配前面的o两次
[root@localhost opt]# grep "go\{2\}d" testfile6 
good
#匹配go..d,其中o至少2次及以上
[root@localhost opt]# grep "go\{2,\}d" testfile6 
good
goood
gooood
gooooood
gooooooodddd
oooogooood[root@iptables ~]# cat test1.txt | egrep "\W"
lloebw iiuy zz
4545>>%$%^$%&^&*#525478&&(*[root@iptables ~]# cat test1.txt | grep -P "\D"
gd
god
good
goood
gooood
goooabcd
gooooood
gooooooodddd
abccba
abccba123
oooogooood
ooooogd
gold
goad
lloebw iiuy zz
ggg
gggd
ffffd
4545>>%$%^$%&^&*#525478&&(*
#匹配数字字符 \d
[root@iptables ~]# cat test1.txt | grep -P "\d"
abccba123
4545>>%$%^$%&^&*#525478&&(*[root@iptables ~]# cat test1.txt | grep -P "\d$"
abccba123
327563278578923753896
[root@iptables ~]# cat test1.txt | grep -P "^\d"
4545>>%$%^$%&^&*#525478&&(*
327563278578923753896[root@iptables ~]# cat test1.txt | grep -E "g(ooo)+d"
goood
gooooood[root@iptables ~]# cat test1.txt | grep -E "g(oo|ooo)+d"
good
goood
gooood
gooooood
gooooooodddd
oooogooood
[root@iptables ~]# cat test1.txt | grep -E "g(oo|ooo)d"
good
goood

4.常见例题

电话例题

找出区号025开头的号码,且号码与区号间可以是空格、-、没有,号码必须是5或者8开头的八位数。

02588888888
025-5555555555
025 12345678
025 54321678
025ABC88888
025-85432109
028-85643210
0251-52765421[root@iptables ~]# cat phone.txt | egrep "^025[ -]?[58][0-9]{7}$"
02588888888
025 54321678
025-85432109

解题思路

先将电话拆分为3段,第一段找出区号,第二段筛选出号码与区号之间的分隔符,第三段号码后几位。

区号025开头。^是以什么为开头,用()将025作为一个整体

^(025)

号码与区号间可以是空格、-、没有。将题目要求的空格和-写入[],由于?是前面的子表达式0次或1次,0次就符合题目要求的没有

 [ -]?

号码必须是5或者8开头的八位数。用[]确认是5或8数字开头的号码,然后再匹配0-9的7位数结尾的数

[58][0-9]{7}$

总体输出如下

[root@localhost opt]# egrep "^(025)[ -]?[58][0-9]{7}$" iphone.sh 
02588888888
025 54321678
025-85432109

电子邮箱例题

根据 用户名@子域名.[二级域名].顶级域 格式找出符合要求的电子邮箱

zhangsan123@qq.com
li si@163.com
wang@wu@sina.com
zhao liu@126.com
qianqi@sina.com.cn

解题思路

将邮箱拆分为三段,第一段用户名,第二段子域名二级域,第三段顶级域

用户名@:长度要求在6-18位,任意大小写英文,任意数字,除了@符号和空格以外的其它任意符号字符,开头只能是 _ 或者字母

^([a-zA-Z_][^@ ]{5,17})@

子域名.[二级域名]:长度任意,符号只能包含 - _ .

[a-zA-Z0-9\_\-\.]+(\.[a-zA-Z0-9\_\-\.]+)?

.顶级域名: 长度在2-5,任意大小写英文

\.([a-zA-Z]{2,5})$

总体输出如下

[root@localhost opt]# egrep "^([a-zA-Z_][^@ ]{5,17})@[a-zA-Z0-9\_\-\.]+(\.[a-zA-Z0-9\_\-\.]+)?\.([a-zA-Z]{2,5})$" mail.sh 
zhangsan123@qq.com
qianqi@sina.com.cn

2、Shell常用命令

1.sort命令——排序工具

  sort 是一个以行为单位对文件内容进行排序的工具,也可以根据不同的数据类型来排序。例如数据和字符的排序就不一样。

  比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

ASCII码

十进制字符十进制字符十进制字符十进制字符十进制字符
3251370F89Y108l
33!52471G90Z109m
34"53572H91[110n
35#54673I92\111o
36$55774J93]112p
37%56875K94^113q
38&57976L95_114r
39'58:77M96`` `115s
40(59;78N97a116t
41)60<79O98b117u
42*61=80P99c118v
43+62>81Q100d119w
44,63?82R101e120x
45-64@83S102f121w
46.65A84T103g122z
47/66B85U104h123{
48067C86V105i124`
49168D87W106j125}
50269E88X107k126~

语法格式

sort [选项] 参数
cat file | sort 选项

常用选项

选项含义
-n按照数字进行排序。
-r反向排序。
-u等同于uniq,表示相同的数据仅显示一行,即为去重。
-t指定字段分隔符,默认使用[Tab]健分隔。
-k指定排序字段。
-o <输出文件>将排序后的结果转存至指定文件。效果和重定向输出相同。
-f忽略大小写,会将小写字母都转换为大写字母来进行比较。
-b忽略每行前面的空格。

使用方式

字母排序:不加任何选项默认按第一列升序,字母的话就是从a到z由上而下显示

[root@localhost ~]# sort passwd.txt 
abrt:x:173:173::/etc/abrt:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
chrony:x:992:987::/var/lib/chrony:/sbin/nologin
colord:x:997:994:User for colord:/var/lib/colord:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin

ip地址排序,按数字大小排序

[root@localhost ~]# cat ip.txt
192.168.145.45
192.168.145.12
192.168.145.33
192.168.145.22
192.168.145.1
[root@localhost ~]# sort -n ip.txt 
192.168.145.1
192.168.145.12
192.168.145.22
192.168.145.33
192.168.145.45

ip地址反向排序

[root@localhost ~]# cat ip.txt
192.168.145.45
192.168.145.12
192.168.145.33
192.168.145.22
192.168.145.1
[root@localhost ~]# sort -nr ip.txt 
192.168.145.45
192.168.145.33
192.168.145.22
192.168.145.12
192.168.145.1

ip地址排序,若出现ip有重复数字,可以使用-k选项

[root@localhost ~]# cat ip.txt
192.168.145.45
192.168.145.100
192.168.145.33
192.168.145.22
192.168.145.1
[root@localhost ~]# cat ip.txt | sort -t '.' -k 4 -n 
192.168.145.1
192.168.145.22
192.168.145.33
192.168.145.45
192.168.145.100

将输结果不在屏幕上输出而是输出到指定文件

[root@localhost ~]# sort -n -t: -k 3 passwd.txt -o passwd.bak
[root@localhost ~]# cat passwd.bak 
root:x:0:0:root:/root:/bin/bash
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
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin

去掉文件中重复的行(-u等同于uniq)

[root@localhost ~]# cat abc.txt 
apple
apple
orange
banana
banana
banana
pear
pear
[root@localhost ~]# sort -u abc.txt 
apple
banana
orange
pear

2.uniq命令——去重工具

  用于报告或者忽略文件中连续的重复行,常与 sort 命令结合使用

语法格式

uniq [选项] 参数
cat file | uniq 选项

注意:是连续的行,所以通常和sort结合使用先排序使之变成连续的行再执行去重操作,否则不连续的重复行他不能去重

常用选项

选项含义
-c进行计数,并删除文件中重复出现的行。
-d仅显示连续的重复行。
-u仅显示出现一次的行。

使用方式

统计重复行的次数

[root@localhost ~]# cat abc.txt | uniq -c			#对连续重复的行进行统计2 apple1 pear3 banana1 orange
[root@localhost ~]# cat abc.txt | sort | uniq -c	#先排序,在对重复行进行统计2 apple3 banana1 orange1 pear

结合sort去重

[root@localhost ~]# cat abc.txt | sort | uniq -d	#仅显示重复行
apple
banana
[root@localhost ~]# cat abc.txt | sort | uniq -u	#仅显示不重复的行
orange
pear
[root@localhost ~]# cat abc.txt | sort | uniq		#去重
apple
banana
orange
pear

输出出现3次以上ip地址

[root@localhost ~]# vim ip.sh
#!/bin/bash
cat ip.txt | sort -n -t '.' -k4 | uniq -c > ./count.txt		#在ip.txt文件中查找ip地址,并以点为分隔符的第四段进行排序以及统计重复行的次数一并输出至count.txt	
IFSB=$IFS
IFS=$'\n'
for i in $ (cat ./count.txt)
donum=$(echo $i | awk '{print $1}')						#记录重复行的次数			if [ $num -eq 3 ] ;then									#判断ip地址重复次数是否有等于3的echo $i | awk '{print $2}'							#如果有等于3的,输出第二个字段的ip地址fi
done
IFS=$IFSB
[root@localhost ~]# bash ip.sh
192.168.145.33
192.168.145.45

#查找一分钟内三次登录系统输入密码错误的用户,并将该IP地址加入到黑名单/etc/hosts.deny中

[root@localhost ~]# vim demo3.sh
#!/bin/bash
count=$(cat /var/log/secure | grep 'Failed password' |  awk '{print $11}' | sort -n -t '.' -k4 | uniq -c)				#先通过grep过滤出secure文件中输入密码报错的字符行,然后通过分割过滤出每行的ip地址,接着将字段根据ip地址第四段进行排序并进行计数、删除重复行,将获取的值赋给count
IFSB=$IFS
IFS=$'\n'									#修改for循环的IFS环境只以\n换行符进行分割
for i in $count
donum=$(echo $i | awk '{print $1}' ) 		#获取重复行出现的次数#echo $num								#输出重复次数if [ $num -gt 3 ];then 					#判断重复次数大于3次,输出第二字段的ip地址IP=$(echo $i | awk '{print $2}' )echo "sshd:$IP" >> /etc/hosts.denyfi
done
IFS=$IFSB
[root@localhost ~]# bash demo3.sh 
[root@localhost ~]# cat /etc/hosts.deny 
sshd:192.168.145.30

3.tr命令——修改工具

  常用来对来自标准输入的字符进行替换、压缩和删除。

语法格式

tr [选项] [参数]

常用选项

选项含义
-c保留字符集1的字符,其他的字符(包括换行符\n)用 字符集2 替换。
-d删除所有属于 字符集1 的字符。
-s将重复出现的字符串压缩为一个字符;用 字符集2 替换 字符集1。
-t字符集2 替换 字符集1 ,不加选项同结果。

使用方式

大小写字母的替换

[root@localhost ~]# echo abc | tr "a-z" "A-Z"		#将abc字符中所有小写字母替换为大写字母
ABC
[root@localhost ~]# echo abc | tr "ac" "AC"			#将abc字符中所有ac字母替换为AC字母
AbB

其他字符保留与替换,比如换行符

[root@localhost ~]# echo -e "ab\nadcd\nab"						#正常输入一串字符,其中包含换行符
ab
adcd
ab
[root@localhost ~]# echo -e "ab\nadcd\nab" | tr -c "ab" "0"		#将字符串中除了ab字符,其他所有字符都替换为0,可以看出换行符也替换为0了
ab0a0000ab0[root@localhost ~]# 
[root@localhost ~]# echo -e "ab\nadcd\nab" | tr -c "ab\n" "0"	#将字符串中除了ab以及换行符,其他所有字符都替换为0,可以看出换行符保留了,字符格式仍然存在
ab
a000
ab

删除指定字符

[root@localhost ~]# echo 'hello world' 					#正常输出一串字符
hello world
[root@localhost ~]# echo 'hello world' | tr -d ' '		#删除空格后的字符输出
helloworld

压缩连续重复的字符以及可以替换其字符

[root@localhost ~]# echo 'thhhhhhis iiisssss mmmmy firrrrrst wrrrrrrite'
thhhhhhis iiisssss mmmmy firrrrrst wrrrrrrite
[root@localhost ~]# echo 'thhhhhhis iiisssss mmmmy firrrrrst wrrrrrrite' | tr -s 'hismr'
this is my first write
[root@localhost ~]# echo 'thhhhhhis iiisssss mmmmy firrrrrst wrrrrrrite' | tr -s 'hismr' 'HISMR'
tHIS IS My fIRSt wRIte

排序脚本

[root@localhost ~]# vim demo4.sh
#!/bin/bash
read -p"请输入一个数组数:" num
arry=($num)
echo "排序前的数组的列表为: ${arry[@]}"
#先获取数组,然后通过tr将空格替换成换行符进行竖列排序,然后通过sort进行数字排序,最后再将换行符替换成空格以实现从小到大排序
arry1=(`echo ${arry[@]} | tr " " "\n"| sort -n | tr "\n" " "`)
echo "排序后的数组的列表为: ${arry1[@]}"
[root@localhost ~]# bash demo4.sh 
请输入一个数组数:63 4 24 1 3 15
排序前的数组的列表为: 63 4 24 1 3 15
排序后的数组的列表为: 1 3 4 15 24 63

4.cut命令——截取工具

  cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。

语法格式

cut 参数
cat file | cut 选项

常用选项

选项含义
-f通过指定哪一个字段进行提取。cut命令使用"TAB"作为默认的字段分隔符。
-d"TAB"是默认的分隔符,使用此选项可以更改为其他的分隔符。
–complement此选项用于排除所指定的字段。
–output-delimiter更改输出内容的分隔符。

使用方式

筛选出指定多个字段

[root@localhost ~]# cat /etc/passwd | cut -d ":" -f 1,3
root:0
bin:1
daemon:2
adm:3
lp:4
sync:5
shutdown:6
halt:7
mail:8
operator:11
games:12
ftp:14
nobody:99
systemd-network:192
dbus:81

筛选出指定多个字段,修改分隔符输出

[root@localhost ~]# cat /etc/passwd | cut -d ":" -f 1,3 --output-delimiter=","
root,0
bin,1
daemon,2
adm,3
lp,4
sync,5
shutdown,6
halt,7
mail,8
operator,11
games,12
ftp,14
nobody,99
systemd-network,192
dbus,81

5.split命令——拆分工具

  split命令 用于linux系统下将一个大的文件拆分成若干小文件

语法格式

split 选项	参数	原始文件 拆分后文件名前缀

常用选项

选项含义
-l以行数拆分
-b以大小拆分

使用方式

[root@localhost opt]# split -l 20 ./CentOS-Vault.repo Vault		#将CentOS-Vault.repo以20行一个文件进行拆分
[root@localhost opt]# ls
CentOS-Vault.repo  Vaultab  Vaultae  Vaultah  Vaultak  Vaultan  Vaultaq
rh                 Vaultac  Vaultaf  Vaultai  Vaultal  Vaultao
Vaultaa            Vaultad  Vaultag  Vaultaj  Vaultam  Vaultap
[root@localhost opt]# cat -n Vaultaa1	# CentOS Vault contains rpms from older releases in the CentOS-72	# tree.3	4	# C7.0.14065	[C7.0.1406-base]6	name=CentOS-7.0.1406 - Base7	baseurl=http://vault.centos.org/7.0.1406/os/$basearch/8	gpgcheck=19	gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-710	enabled=011	12	[C7.0.1406-updates]13	name=CentOS-7.0.1406 - Updates14	baseurl=http://vault.centos.org/7.0.1406/updates/$basearch/15	gpgcheck=116	gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-717	enabled=018	19	[C7.0.1406-extras]20	name=CentOS-7.0.1406 - Extras
[root@localhost opt]# 

6.paste命令——合并工具

  paste命令用于合并文件的列。

语法格式

paste [-s][-d <间隔字符>] 文件...

常用选项

选项含义
-d<间隔字符>用指定的间隔字符取代制表符
-s把多行内容合并为一行进行显示

使用方式

paste命令合并文件

[root@localhost opt]# paste adb.txt 123.txt 		#默认使用制表符分隔
aa		111
bb		222
cc		333
dd		444
[root@localhost opt]# paste -d "," adb.txt 123.txt 	#可以指定分隔符为逗号
aa,111
bb,222
cc,333
dd,444
[root@localhost opt]# paste -d "," -s adb.txt 123.txt 	#将每一列转换为行输出
aa,bb,cc,dd
111,222,333,444

输出两个文件的第二列至一个新文件中

[root@localhost opt]# cat 1.txt
a A
b B
c C
d D
e E
f F
[root@localhost opt]# cat 2.txt
1:10
2:20
3:30
4:40
5:50
6:60
#先合并两个文件,用空格替换制表符,然后将冒号分隔符替换为空格,最后通过空格截取第2、4字段输出至新文件
[root@localhost opt]# paste -d ' ' 1.txt 2.txt | tr -t ":" " " | cut -d " " -f "2,4" >> 12.txt
[root@localhost opt]# cat 12.txt 
A 1
B 2
C 3
D 4
E 5
F 6

其他合并方式

case 文件1 文件2 >> 新文件vim 文件 ——> 将光标移至最后一行 ——> :r ./当前目录文件

7.eval命令——两次扫描工具(不需要)

 命令字前加eval时,shell会在执行命令之前扫描它两次。eval命令将首先会先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描。

语法格式

eval 命令序列

使用方式

[root@localhost opt]# vim test1.sh
#!/bin/bash
a=100
b=a
eval echo \$$b				#先扫描$b,因为b=a,所以b=100,然后再次扫描,\$意为$本身,最后为echo $b,输出值为100
eval $b=50					#先扫描$b,因为b=a,所有$a=50,最后为echo $a,输出值为50
echo $a
[root@localhost opt]# bash test1.sh 
100
50

十一、sed编辑器

1.sed编辑器概念

  sed是一种流编辑器,流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。

  sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储一个命令文本文件中。

2.sed编辑器工作流程

  sed 的工作流程主要包括读取执行显示三个过程:

读取:sed从输入流(文件、管道、标准输入〉中读取一行内容并存储到临时的缓冲区中(又称模式空间,pattern space)。    执行:默认情况下,所有的sed命令都在模式空间中顺序地执行,除非指定了行的地址,否则临时sed命令将会在所有的行上依次执行。

显示:发送修改后的内容到输出流。在发送数据后,模式空间将会被清空。

  在所有的文件内容都被处理完成之前,上述过程将重复执行,直至所有内容被处理完。

  注意:默认情况下所有的sed命令都是在模式空间内执行的,因此输入的文件并不会发生任何变化,除非是用重定向存储输出。

处理大文件较卡问题

  流编辑器处理大文件比较卡可以使用两种方式来处理。sed命令将大文件分成若干个小文件读取。或者使用cat命令读取全部文件放在临时缓冲区中,然后交给流编辑器(grep、sed、awk)进行处理,由于使用cat命令时IO消耗比较低。

3.sed编辑器用法

命令格式-n 只显示满足 条件的输出结果
sed -e '操作' 文件l 文件2 ...
sed -n -e '操作' 文件1 文件2 ...
sed -f 脚本文件 文件1 文件2 ...
sed -i -e '操作' 文件1 文件2 ...sed -e 'n{				#n意为:指定行
操作l
操作2
...
}' 文件1 文件2 ...
常用选项
选项含义
-e--expression=表示用指定命令来处理输入的文本文件,只有一个操作命令时可省略,一般在执行多个操作命令使用。
-f--file=表示用指定的脚本文件来处理输入的文本文件。
-h--help显示帮助。
-n--quietsilent禁止sed编辑器输出,但可以与p命令一起使用完成输出。
-i直接修改目标文本文件。(改变原文本内容)
-r, --regexp-extended支持扩展正则表达式
常用操作
操作含义
s替换,替换指定字符。
d删除,删除选定的行。
a增加,在当前行下面增加一行指定内容。
i插入,在选定行上面插入一行指定内容。
c替换,将选定行替换为指定内容。
y字符转换,转换前后的字符长度必须相同。
p打印,如果同时指定行,表示打印指定行;如果不指定行,则表示打印所有内容,如果有非打印字符,则以ASCII码输出。其通常与-n选项一起使用。
=打印行号。
l(小写L)打印数据流中的文本和不可打印的AscII字符(比如结束符$、制表符\t)
常用方式
打印内容
[root@localhost ~]# sed -n -e 'p' testfile1			#打印内容[root@localhost ~]# sed -n -e '=' testfile1			#打印行号[root@localhost ~]# sed -n -e 'l' testfile1			#打印隐藏特殊符号[root@localhost ~]# sed -n -e '=;p' testfile1		#打印行号和内容
[root@localhost ~]# sed -n -e '=' -e 'p'testfile1	#打印行号和内容
[root@iptables ~]# sed -n -r "/[a-z]/p" test1.txt
gd
god
good
goood
gooood
goooabcd
gooooood
gooooooodddd
abccba
abccba123
oooogooood
[root@iptables ~]# sed -n -r "/(go+d)/p" test1.txt
god
good
goood
gooood
gooooood
gooooooodddd
[root@iptables ~]# cat passwd | sed  -n '/bash$/p' 
root:x:0:0:root:/root:/bin/bash
hou:x:1000:1000:hou:/home/hou:/bin/bash
hehao:x:1001:1001::/home/hehao:/bin/bash
hf:x:1002:1002::/home/hf:/bin/bash
hh:x:1003:1004::/home/hh:/bin/bash
fan:x:1004:1005::/home/fan:/bin/bash
shulan:x:1005:1006::/home/shulan:/bin/bash

sed编辑器的寻址方式

  • 以数字形式表示行区间

  • 用文本模式来过滤出行

[root@localhost ~]# sed -n '1p' testfile1	1				#打印第1行[root@localhost ~]# sed -n '$p' testfile1					#打印最后一行[root@localhost ~]# sed -n '1,3p' testfile1					#打印1到3行[root@localhost ~]# sed -n '3,$p' testfile1					#从第3行开始打印,直到最后一行结束[root@localhost ~]# sed -n '1,+3p' testfile1				#打印第1行之后的连续3行,即1-4行[root@localhost ~]# sed '5q' testfile1						#打印前5行信息后退出,q表示退出[root@localhost ~]# sed -n 'p;n' testfile1					#打印奇数行,n表示移动到下一行
[root@localhost ~]# sed -n '3{p;n;n;p}' testfile1			#打印第3,5行
sed -n -e '3p' -e '5p'[root@localhost ~]# sed -n 'n;p' testfile1					#打印偶数行

[root@localhost ~]# sed -n '2,${n;p}' testfile1				#从第2行开始,从下一行开始打印,即第3、5、7行[root@localhost ~]# sed -n '/user/p' /etc/passwd			#打印包含user的行[root@localhost ~]# sed -n '/^a/p' /etc/passwd				#打印以a开头的行[root@localhost ~]# sed -n '/bath$/p' t/etc/passwd			#打印以bath结尾的行[root@localhost ~]# sed -n '/ftp\|root/p' /etc/passwd		#打印含有ftp或者root的行
[root@localhost ~]# sed -nr '/ftp|root/p' /etc/passwd		#-r表示支持扩展正则表达式[root@localhost ~]# sed -n '2,/nobody/p' /etc/passwd		#打印从第2行开始,直到第一个包含nobody的行结束[root@localhost ~]# sed -n '2,/nobody/=' /etc/passwd		#打印从第2行开始,直到第一个包含nobody的行结束的行号[root@localhost ~]# sed -nr '/ro{1,}t/p' /etc/passwd		#打印包含root的行,root中o的个数可以是1个以上  ???

删除行内容 不能加-n 因为永远看不到东西
[root@localhost ~]# sed 'd' testfile1						#全删[root@localhost ~]# sed '3d' testfile1						#删除第3行[root@localhost ~]# sed '2,4d' testfile1					#删除第2到4行[root@localhost ~]# sed '$d' testfile1						#删除最后一行[root@localhost ~]# sed '/^$/d' testfile1					#删除空行[root@localhost ~]# sed '/nologin$/d' /etc/passwd			#删除以nologin结尾的文件[root@localhost ~]# sed '/nologin$/!d' /etc/passwd			#"!"表示取反[root@localhost ~]# sed '/2\|3/d' testfile2					#删除第2行和第3行
[root@localhost ~]# sed '/2/,/3/d' testfile2				#从第一个位置打开行删除功能,到第二个位置关闭行删除功能
[root@localhost ~]# sed '/1/,/3/d' testfile2				#从第一个包含1的行打开删除功能,到第一个包含3的行关闭删除功能,然后接着往下扫描重复之前操作,若包含3的行不存在,则一删到底。

替换内容

命令格式

行范围 s/旧字符/新字符串/替换标记

替换标记

数字表明新字符串将替换第几处匹配的地方
g表明新字符串将会替换所有匹配的地方
p打印与替换命令匹配的行,与-n一起使用
w 文件将替换的结果写到文件中

使用方式

[root@localhost ~]# sed -n 's/root/admin/p' /etc/passwd					#将匹配行中第一个root替换为admin然后打印替换的行[root@localhost ~]# sed -n 's/root/admin/2p' /etc/passwd				#将匹配行中第二个root替换为admin然后打印替换的行[root@localhost ~]# sed -n 's/root/admin/gp' /etc/passwd				#将匹配行所有root替换为admin然后打印替换的行
[root@localhost ~]# sed -n 's/root/admin/gw file' /etc/passwd			#将匹配行所有root替换为admin然后保存替换的行至file
[root@localhost ~]# sed -n 's/root/admin/gp' /etc/passwd > file			#将匹配行所有root替换为admin然后保存替换的行至file[root@localhost ~]# sed 's/root//g' /etc/passwd							#将匹配行所有root替换为空的[root@localhost ~]# sed 'l,20 s/^/#/' /etc/passwd						#在第1到20行进行注释

[root@localhost ~]# sed '/^root/ s/^/#/' /etc/passwd					#将以root开头的行进行注释[root@localhost ~]# sed '/root/ s/^/#/' /etc/passwd						#将包含root的行进行注释
[root@localhost ~]# sed -rn 's /.*root.*/#&/p' /etc/passwd				#用正则匹配行内容,然后通过&获取前面匹配的内容进行注释后打印
[root@localhost ~]# sed  -ir 's/.*swap.*/#&/' /etc/fstab				#禁用swap交换空间
[root@localhost ~]# sed -f script.sed testfile2							#对某个文件执行指定命令文件[root@localhost ~]# sed '1,20w out.txt' /etc/passwd
[root@localhost ~]# sed '1,20 s/^/#/w out.txt' /etc/passwd				#将文件的第1到20行进行注释,然后将修改的行内容保存至指定文件[root@localhost ~]# sed -n 's/\/bin\/bash/\/bin\/csh/p' /etc/passwd		#将文件中/bin/bash替换为/bin/csh,然后打印替换的行
[root@localhost ~]# sed -n 's!/bin\/bash!/bin\/csh!p' /etc/passwd		#使用"!"作为字符串分隔符,可以使用除了斜杠的其他字符
[root@localhost ~]# sed -i 's9\945\9\99\98\939' /etc/passwd				#将94599替换为9893

插入内容

基本使用

[root@localhost ~]# sed '/45/c ABC' testfile2							#将含有45的行都替换为ABC[root@localhost ~]# sed 'y/14/ABC/' testfile2							#使所有的1字符转换成a,所有的2字符转换成B,所有的3字符转换成c
[root@iptables ~]# cat f1
AAA
AAA
bbb
ccc
AAA
bbb
AAA
[root@iptables ~]# cat -n f1 | sed 'y/ABC/123/' 1  1112  1113  bbb4  ccc5  1116  bbb7  111[root@localhost ~]# sed '1,3a ABC' testfile2							#在第1行到第3行后都插入ABC新的一行
[root@iptables ~]# cat -n f1 | sed '1,3a abc' 1  AAA
abc2  AAA
abc3  bbb
abc4  ccc5  AAA6  bbb7  AAA[root@localhost ~]# sed '1i ABC' testfile2								#在第一行前插入ABC新的一行[root@localhost ~]# sed '5r /etc/resolv.conf' testfile2					#在第5行读取/etc/resolv.conf文件

保持空间的使用

[root@localhost ~]# sed '/root/{H;d};$G‘ /etc/passwd					#将包含root的行剪切到末尾,H表示复制到剪切板,G表示粘贴到指定行后2  bin:x:1:1:bin:/bin:/sbin/nologin3  daemon:x:2:2:daemon:/sbin:/sbin/nologin52  shulan:x:1005:1006::/home/shulan:/bin/bash1  root:x:0:0:root:/root:/bin/bash10  operator:x:11:0:operator:/root:/sbin/nologin
[root@localhost ~]# sed '1,2H;3,4G' /etc/passwd						#将1、2行复制到3和4行的下面1  root:x:0:0:root:/root:/bin/bash2  bin:x:1:1:bin:/bin:/sbin/nologin3  daemon:x:2:2:daemon:/sbin:/sbin/nologin1  root:x:0:0:root:/root:/bin/bash2  bin:x:1:1:bin:/bin:/sbin/nologin4  adm:x:3:4:adm:/var/adm:/sbin/nologin1  root:x:0:0:root:/root:/bin/bash2  bin:x:1:1:bin:/bin:/sbin/nologin

高级使用(sed分组概念)

[root@localhost ~]# echo "111222333" | sed -r 's/(111)(222)/\2\1/'		#将字符111和 222互换位置
[root@iptables ~]# echo "111222333" | sed -r 's/(111)(222)(333)/\2\3\1/'
222333111
[root@localhost ~]# echo "111222333" | sed -r 's/^(.)(.*)(.)$/\3\2\1/'	#将第一个字符和最后一个字符互换

十二、awk编辑器

1.概念

  sed命令常用于一整行的处理,而awk比较倾向于将一行分成多个"字段"然后再进行处理。awk信息的读入也是逐行读取的,执行结果可以通过print的功能将字段数据打印显示。

  在使用awk命令的过程中,可以使用逻辑操作符 &&表示与、||表示或、!表示非;还可以进行简单的数学运算,如+-*/%^分别表示加、减、乘、除、取余和乘方。

2.工作原理

  逐行读取文本,默认以空格或tab键为分隔符进行分隔,将分隔所得的各个字段保存到内建变量中,并按模式或者条件执行编辑命令。

3.用法

命令格式
awk 选项 '模式或条件 {操作}' 文件1 文件2 ...
awk -f 脚本文件 文件l 文件2 ...
常用选项
-F  指定分隔符
awk -F ',' '{print}' file1
常见的内建变量
内建变量含义
FS列分割符。指定每行文本的字段分隔符,默认为空格或制表位。与-F作用相同。
NF当前处理的行的字段个数。$NF代表最后一个字段。
NR当前处理的行的行号(序数)。
$0当前处理的行的整行内容。
$n当前处理行的第n个字段(第n列)。
FILENAME被处理的文件名。
RS行分隔符。awk从文件上读取资料时,将根据Rs的定义把资料切割成许多条记录,而awk比较倾向于将一行分成多个一次仅读入一条记录,以进行处理。预设值是\n
常用方式
按行输出文本

输出所有内容

[root@localhost opt]# awk '{print}' testfile1 
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
[root@localhost opt]# awk '{print $0}' testfile1 
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve

输出指定行内容

###输出第1行内容
[root@localhost opt]# awk 'NR==1 {print}' testfile1 
one###输出第3行内容
[root@localhost opt]# awk 'NR==3 {print}' testfile1 
three###输出第1~3行内容
[root@localhost opt]# awk 'NR==1,NR==3 {print}' testfile1 
one
two
three
[root@localhost opt]# awk '(NR>=1)&&(NR<=3){print}' testfile1
one
two
three###输出第1行、第3行的内容
[root@localhost opt]# awk 'NR==1||NR==3 {print}' testfile1 
one
three
[root@iptables ~]# cat -n passwd | awk 'NR==1{print}'1  root:x:0:0:root:/root:/bin/bash
[root@iptables ~]# cat -n passwd | awk 'NR==3{print}'3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@iptables ~]# cat -n passwd | awk 'NR==1,NR==3{print}'1  root:x:0:0:root:/root:/bin/bash2  bin:x:1:1:bin:/bin:/sbin/nologin3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@iptables ~]# cat -n passwd | awk '(NR>=1)&&(NR<=3){print}'1  root:x:0:0:root:/root:/bin/bash2  bin:x:1:1:bin:/bin:/sbin/nologin3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@iptables ~]# cat -n passwd | awk 'NR==1||NR==3{print}'1  root:x:0:0:root:/root:/bin/bash3  daemon:x:2:2:daemon:/sbin:/sbin/nologin

输出奇偶数行的内容

###输出偶数行的内容
[root@localhost opt]# awk '(NR%2)==0{print}' testfile1 
two
four
six
eight
ten
twelve###输出奇数行的内容
[root@localhost opt]# awk '(NR%2)==1{print}' testfile1 
one
three
five
seven
nine
eleven
[root@iptables ~]# cat -n passwd | awk '(NR%2)==1{print}'1  root:x:0:0:root:/root:/bin/bash3  daemon:x:2:2:daemon:/sbin:/sbin/nologin5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin[root@iptables ~]# cat -n passwd | awk '(NR%2)==0{print}'2  bin:x:1:1:bin:/bin:/sbin/nologin4  adm:x:3:4:adm:/var/adm:/sbin/nologin6  sync:x:5:0:sync:/sbin:/bin/sync

输出含有字符串的行

###输出含有root的行l1
[root@localhost opt]# awk '/root/{print}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin###输出以root开头的行
[root@localhost opt]# awk '/^root/{print}' /etc/passwd
root:x:0:0:root:/root:/bin/bash###输出以nologin结尾的行
[root@localhost opt]# awk '/nologin$/{print}' /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
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
...
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin[root@iptables ~]# cat -n passwd | awk '/root/{print}'1  root:x:0:0:root:/root:/bin/bash10  operator:x:11:0:operator:/root:/sbin/nologin
[root@iptables ~]# cat  passwd | awk '/^root/{print}'
root:x:0:0:root:/root:/bin/bash
[root@iptables ~]# cat -n passwd | awk '/onlogin$/{print}'
[root@iptables ~]# cat -n passwd | awk '/nologin$/{print}'2  bin:x:1:1:bin:/bin:/sbin/nologin3  daemon:x:2:2:daemon:/sbin:/sbin/nologin4  adm:x:3:4:adm:/var/adm:/sbin/nologin5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

统计以/bin/bash 结尾的行数

    [root@localhost opt]# awk 'BEGIN {x=0};/\/bin\/bash$/{x++};END {print x}' /etc/passwd
2
[root@iptables ~]# cat -n passwd | awk 'BEGIN {x=0};/\/bin\/bash$/{x++};END{print x}' 
7

此命令等同于grep -c "/bin/bash$" /etc/passwd

BEGIN模式表示,在处理指定的文本之前,需要先执行BEGIN模式中指定的动作;awk再处理指定的文本,之后再执行END模式中指定的动作,END{}语句块中,往往会放入打印结果等语句。

按字段输出文本

输出每行中(以控股或制表位分隔)的第3个字段

[root@localhost opt]# awk -F":" '{print $3}' /etc/passwd
0
1
2
3
4
5
6
...
38
42
29
65534
98
74
70
89
72
1000
[root@iptables ~]# cat -n passwd | awk -F":" '{print $3}'
0
1
2
3

输出每行中的第1、3个字段

[root@localhost opt]# awk -F":" '{print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
...
geoclue 989
ntp 38
gdm 42
rpcuser 29
nfsnobody 65534
gnome-initial-setup 988
sshd 74
avahi 70
postfix 89
tcpdump 72
tang 1000
[root@iptables ~]# cat -n passwd | awk -F":" '{print$1,$3,$5}'1  root 0 root2  bin 1 bin3  daemon 2 daemon4  adm 3 adm

输出特定条件的行的字段内容

###输出第3个字段的值小于5的第1、3个字段内容
[root@localhost opt]# awk -F":" '$3<5{print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
[root@iptables ~]# cat -n passwd | awk -F":" '$3<5{print$1,$3,$5}'1  root 0 root2  bin 1 bin3  daemon 2 daemon4  adm 3 adm5  lp 4 lp###输出第3个字段不小于200的行
[root@localhost opt]# awk -F":" '!($3<200){print $1,$3}' /etc/passwd
polkitd 999
libstoragemgmt 998
colord 997
saned 996
gluster 995
saslauth 994
setroubleshoot 993
chrony 992
unbound 991
sssd 990
geoclue 989
nfsnobody 65534
gnome-initial-setup 988
tang 1000
[root@iptables ~]# cat -n passwd | awk -F":" '$3>=200{print$1,$3,$5}'13  nobody 65534 Kernel Overflow User15  systemd-coredump 999 systemd Core Dumper
[root@iptables ~]# cat -n passwd | awk -F":" '!($3<200){print$1,$3,$5}'13  nobody 65534 Kernel Overflow User15  systemd-coredump 999 systemd Core Dumper ###输出第3个字段大于等于1000的行,用if语句进行判断
[root@localhost opt]# awk 'BEGIN{FS=":"};{if($3>=1000){print}}' /etc/passwd
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
tang:x:1000:1000:tang:/home/tang:/bin/bash
[root@iptables ~]# cat -n passwd | awk -F":" '{if($3>=1000){print$1,$3,$5}}'13  nobody 65534 Kernel Overflow User45  hou 1000 hou
[root@iptables ~]# cat -n passwd | awk 'BEGIN{FS=":"}; {if($3>=1000){print$1,$3,$5}}'13  nobody 65534 Kernel Overflow User45  hou 1000 hou47  hehao 1001  

使用三元运算符输出内容

1. 变量赋值

通过三元运算符为变量赋值,根据条件选择不同的值。

核心逻辑:如果条件为真,返回第一个值;否则返回第二个值

示例 1:根据数值大小赋值

a=10
b=$(( a > 5 ? 1 : 0 ))  # 如果 a>5 为真(10>5),b=1;否则 b=0
echo $b  # 输出:1

示例 2:根据字符串匹配赋值

user="root"
role=$(( user == "root" ? "admin" : "guest" ))  # 字符串比较用 =,而非 ==
echo $role  # 输出:admin
2. 输出控制

直接通过三元运算符决定输出内容,避免多层 if-else 嵌套。

示例:根据分数输出等级

score=85
grade=$(( score >= 90 ? "A" : (score >= 80 ? "B" : "C") ))  # 嵌套三元运算符
echo "分数等级:$grade"  # 输出:分数等级:B
3. 命令参数选择

根据条件动态选择命令的参数,简化脚本逻辑。

示例:根据系统类型选择命令

os=$(uname -s)
copy_cmd=$(( os == "Linux" ? "cp" : "copy" ))  # Linux 用 cp,其他系统用 copy(假设)
echo "复制命令:$copy_cmd"  # 输出:复制命令:cp
如果第3个字段的值大于等于第4个字段的值,则把第3个字段的值赋给max,否则第4个字段的值赋给max
[root@localhost opt]# awk -F":" '{max=($3>=$4)?$3:$4;{print max}}' /etc/passwd
0
1
2
4
7
5
6
7
12
11
100
50
99
192
81
999
998
997
32
996
995
994
173
993
172
171
75
992
991
107
59
990
113
989
38
42
29
65534
988
74
70
89
72
1000
[root@iptables ~]# cat -n passwd | awk -F":" '{print$3,$4}'
0 0
1 1
2 2
3 4
4 7
5 0
6 0
7 0
8 12
11 0
12 100
14 50
[root@iptables ~]# cat -n passwd | awk -F":" '{max=($3>=$4)?$3:$4;{print max}}'
0
1
2
4
7
5
6
7
12
11
100
50

输出每行内容和行号

每处理完一条记录,NR值加1

[root@localhost opt]# awk -F":" '{print NR,$0}' /etc/passwd
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 libstoragemgmt:x:998:995:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
18 colord:x:997:994:User for colord:/var/lib/colord:/sbin/nologin
19 rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
20 saned:x:996:993:SANE scanner daemon user:/usr/share/sane:/sbin/nologin
21 gluster:x:995:992:GlusterFS daemons:/run/gluster:/sbin/nologin
22 saslauth:x:994:76:Saslauthd user:/run/saslauthd:/sbin/nologin
23 abrt:x:173:173::/etc/abrt:/sbin/nologin
24 setroubleshoot:x:993:990::/var/lib/setroubleshoot:/sbin/nologin
25 rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
26 pulse:x:171:171:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
27 radvd:x:75:75:radvd user:/:/sbin/nologin
28 chrony:x:992:987::/var/lib/chrony:/sbin/nologin
29 unbound:x:991:986:Unbound DNS resolver:/etc/unbound:/sbin/nologin
30 qemu:x:107:107:qemu user:/:/sbin/nologin
31 tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
32 sssd:x:990:984:User for sssd:/:/sbin/nologin
33 usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
34 geoclue:x:989:983:User for geoclue:/var/lib/geoclue:/sbin/nologin
35 ntp:x:38:38::/etc/ntp:/sbin/nologin
36 gdm:x:42:42::/var/lib/gdm:/sbin/nologin
37 rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
38 nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
39 gnome-initial-setup:x:988:982::/run/gnome-initial-setup/:/sbin/nologin
40 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
41 avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
42 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
43 tcpdump:x:72:72::/:/sbin/nologin
44 tang:x:1000:1000:tang:/home/tang:/bin/bash[root@iptables ~]# cat  passwd | awk -F":" '{print NR,$0}'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@iptables ~]# cat -n passwd | awk -F":" '{print NR,$0}'
1      1        root:x:0:0:root:/root:/bin/bash
2      2        bin:x:1:1:bin:/bin:/sbin/nologin
3      3        daemon:x:2:2:daemon:/sbin:/sbin/nologin

输出两种指定条件的内容

###输出以冒号分隔且第7个字段中包含/bash的行的全部内容
[root@localhost opt]# awk -F":" '$7~"/bash"{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
tang:x:1000:1000:tang:/home/tang:/bin/bash
[root@iptables ~]# cat  passwd | awk -F":" '$7~"/bash"{print $0}'
root:x:0:0:root:/root:/bin/bash
hou:x:1000:1000:hou:/home/hou:/bin/bash
hehao:x:1001:1001::/home/hehao:/bin/bash###输出以冒号分隔且第7个字段中包含/bash的行的第1个字段
[root@localhost opt]# awk -F":" '$7~"/bash"{print $1}' /etc/passwd
root
tang
[root@iptables ~]# cat  passwd | awk -F":" '$7~"/bash"{print $1}'
root
hou
hehao###输出第1个字段中包含root且有7个字段的行的第1、2个字段
[root@localhost opt]# awk -F":" '($1~"root")&&(NF==7){print $1,$2}' /etc/passwd
root x
[root@iptables ~]# cat  passwd | awk -F":" '$7~"/bash"{print $1,$2}'
root x
hou x
hehao x###输出第7个字段既不为/bin/bash,也不为/sbin/nologin的所有行
[root@localhost opt]# awk -F":" '($7!="/bin/bash")&&($7!="/sbin/nologin"){print}' /etc/passwd
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
[root@iptables ~]# cat  passwd | awk -F":" '($7!="/bin/bash")&&($7!="/sbin/nologin"){print $1,$2}'
sync x
shutdown x
halt x

通过管道、双引号调用 Shell命令

统计以冒号分隔的文本段落数,END{}语句块中,往往会放入打印结果等语句。

每处理完一条记录,NR值加1

[root@iptables ~]# echo $PATH   查看当前 Shell 会话中环境变量
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin[root@localhost opt]# echo $PATH | awk 'BEGIN {RS=":"}; END{print NR}'
5
[root@iptables ~]# cat -n passwd | awk 'BEGIN {RS=":"}; END{print NR}'
313

调用wc -l命令统计使用bash 的用户个数

[root@localhost opt]# awk -F: '/bash$/{print | "wc -l"}' /etc/passwd
2
[root@localhost opt]# awk -F: '/bash$/{print}' /etc/passwd | wc -l
2
[root@iptables ~]# cat -n passwd | awk -F":" '/bash$/{print | "wc -l"}'
7
[root@iptables ~]# cat -n passwd | awk -F":" '/bash$/{print}'| wc -l
7
[root@iptables ~]# cat -n passwd | grep -c "bash$"
7

此命令等同于grep -c "bash$" /etc/passwd

查看当前内存使用百分比 (int 去除小数点)

[root@localhost opt]# free -m | awk '/Mem:/ {print int($3/($3+$4)*100)"%"}'
33%
[root@localhost opt]# free -m | awk '/Mem:/ {print $3/$2 * 100"%"}'
24.1379%
[root@iptables ~]# free -m | awk '/Mem:/ {print($3/($3+$4)*100)"%"}'
82.8142%
[root@iptables ~]# free -m | awk '/Mem:/ {print($3/$4)*100"%"}'
478.261%
[root@iptables ~]# free -m | awk '/Mem:/ {print int($3/$4)"%"}'
4%

查看当前CPU空闲率

[root@localhost opt]# top -b -n 1 | awk -F"," '/%Cpu/ {print $4}' | awk '{print $1}'
100.0
[root@iptables ~]# top -b -n 1 | awk -F"," '/%Cpu/ {print $4}' | cut -d" " -f2
81.1
[root@iptables ~]# top -b -n 1 | awk -F"," '/%Cpu/ {print $4}' | awk '{print $1}'
94.0[root@localhost opt]# top -b -n 1 | grep Cpu | awk -F ',' '{print $4}' | awk '{print $1}'
100.0

-b -n 1表示只需要1次的输出结果。

查看当前磁盘的情况

###查看当前磁盘的使用率
[root@localhost opt]# df | awk '$NF=="/" {print $5}'
13%
[root@iptables ~]# df | awk '$NF=="/" {print $5}'
12%
###查看当前磁盘的空闲率
[root@localhost opt]# df | awk '$NF=="/" {print $5}' | awk -F% '{print 100-$1"%"}'
87%
$1为分割后的第一个字段

显示上次系统重启时间

[root@localhost opt]# date -d "$(awk -F "." '{print $1}' /proc/uptime) second ago" +"%F %H:%M:%S"
2023-05-15 08:44:02
[root@localhost opt]# date -d "$(cat /proc/uptime | awk -F. '{print $1} ' ) second ago" +"%Y-%m-%d %H:%M:%S"
2023-05-15 08:44:02

常见日期用法

###当月第一天
[root@localhost opt]# date +%Y%m01
20230501##下个月第一天
[root@localhost opt]# date -d "1 month" +%Y%m01
20230601###上个月第一天
[root@localhost opt]# date -d "-1 month" +%Y%m01
20230401
[root@localhost opt]# date -d "1 month ago" +%Y%m01
20230401###明天
[root@localhost opt]# date -d "1 day" +%Y%m%d
20230516##昨天
[root@localhost opt]# date -d "-1 day" +%Y%m%d
20230514
[root@localhost opt]# date -d "yesterday" +"%Y%m%d" 
20230514###上个月最后一天
[root@localhost opt]# date -d "$(date +%Y%m01) -1 day" +%Y%m%d
20230430###当月最后一天
[root@localhost opt]# date -d "$(date -d "1 month" +%Y%m01) -1 day" +%Y%m%d
20230531

面试题:每个月最后一天去执行指定文件

方式1:脚本中进行判断
###当月最后一天
lastday=$(date -d "$(date -d "1 month" + "%Y%m01") -1 day" +"%Y%m%d")
###今天
today=$(date +%Y%m%d)
###判断今天是否是最后一天
if [ "$today" == "$lastday" ];then 执行文件方式2:计划性任务中判断
0 0 28-31 if [ "$(date -d "$(date -d "1 month" + "%Y%m01") -1 day" +"%Y%m%d")" == "today=$(date +%Y%m%d)"];then 执行文件

调用w命令,并用来统计在线用户数

[root@localhost opt]# awk 'BEGIN{n=0;while("w" | getline) n++ ;{print n-2}}'
2
#n-2 为了去除前两行[root@iptables ~]# w | awk '{n++}END{print n-2}'
1
[root@iptables ~]# w#23:51:17 up 2 days,  2:31,  3 users,  load average: 0.12, 0.35, 0.47
#USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     seat0    login-           五00    0.00s  0.00s  0.04s /usr/li
root     tty2     tty2             五00    3days 17:33  11:11  /usr/bi
root     pts/1    192.168.162.1    07:12    0.00s  2.60s  0.04s w

调用hostname,并输出当前的主机名

[root@localhost opt]# awk 'BEGIN{"hostname" | getline ;{print $0}}'
localhost.localdomain

获取奇偶数行

###获取偶数行
[root@localhost opt]# seq 10 | awk '{getline;print $0}'
2
4
6
8
10###获取奇数行
[root@localhost opt]# seq 10 | awk '{print $0;getline}'
1
3
5
7
9

getline左右无重定向符<|时,awk首先读取到了第一行,就是1,然后使用getline就得到了1下面的第二行,就是2,因为getline之后,awk会改变对应的NFNRFNR$0等内部变量,所以此时的$0的值就不再是1,而是2了,然后将它打印出来。

getline左右有重定向符<|时,getline则作用于定向输入文件,由于该文件是刚打开,并没有被awk读入一行,只是getline读入,那么getline返回的是该文件的第一行,而不是隔行。

查看使用特定分隔符的字符串

###输出A B C D字符串
[root@localhost opt]# echo "A B C D"
A B C D###输出ABCD字符串以及用|分隔的字符串
[root@localhost opt]# echo "A B C D" | awk '{OFS="|";print $0;$1=$1;print $0}'
A B C D
A|B|C|D###输出以逗号分隔的ABCD字符串
[root@localhost opt]# echo "A B C D" | awk 'BEGIN{OFS=","};{$2=$2;print $0}'
A,B,C,D
echo "A B C D" | awk '{print $1","$2","$3","$4}'
A,B,C,D

OFS是列输出分隔符,可以用来指定分隔符。

$1=$1是用来激活$0的重新赋值,也就是说字段$1…和字段数NF的改变会促使awk重新计算$0的值,通常是在改变OFS后而需要输出$0时这样做。

按数组输出文本以及统计

下标是数字时输出文本

[root@localhost opt]# awk 'BEGIN{a[0]=10;a[1]=20;print a[1]}'
20
[root@localhost opt]# awk 'BEGIN{a[0]=10;a[1]=20;print a[0]}'
10

下标是一串字符时输出文本

[root@localhost opt]# awk 'BEGIN{a["abc"]=10;a["xyz"]=20;print a["abc"]}'
10
[root@localhost opt]# awk 'BEGIN{a["abc"]=10;a["xyz"]=20;print a["xyz"]}'
20

其中字符串需要用双引号包起来。

下标是数字,使用循环输出文本

[root@localhost opt]# awk 'BEGIN{a[0]=10;a[1]=20;a[2]=30;for(i in a){print a[1]}}'
20
20
20
[root@localhost opt]# awk 'BEGIN{a[0]=10;a[1]=20;a[2]=30;for(i in a){print a[2]}}'
30
30
30

其中BEGIN中的命令只执行一次

通过数组统计重复行的数量

[root@localhost opt]# cat test.txt
aaa
aaa
bbb
ccc
aaa
bbb
aaa
###统计重复字符串的数量以及输出对应的字符串
[root@localhost opt]# awk '{a[$1]++}END{for(i in a){print a[i],i}}' test.txt 
4 aaa
1 ccc
2 bbb
###从大到小排序
[root@localhost opt]# awk '{a[$1]++}END{for(i in a){print a[i],i}}' test.txt |sort -r
4 aaa
2 bbb
1 ccc

a[1]初始为0,a[1]++后即为1,而这里awk中的a[1]++最终的值是由test.txt文本内容有多少行决定的,文本逐行读取完毕后再执行END中的命令。

统计登录失败的ip地址出现的次数

[root@localhost log]# cat /var/log/secure | awk '/Failed password/{print $11}' | sort -n |uniq -c3 192.168.145.301 192.168.145.45[root@localhost log]# cat /var/log/secure | awk '/Failed password/{a[$11]++}; END{for(i in a){print a[i],i}}'
1 192.168.145.45
3 192.168.145.30

十三、find命令详解

find命令是Unix和类Unix系统中一个强大的文件搜索工具,它允许用户在文件系统中按照不同的条件查找文件和目录。由于其灵活性和功能强大,find命令成为系统管理员和开发人员在维护文件系统和查找特定文件时的首选工具。本文将深入介绍find命令的基本语法、常用选项以及高级用法,以帮助读者更全面地了解和使用这个重要的Unix命令。

1、find基本语法

find命令的基本语法如下:

find [path...] [expression]

其中,path是查找的起始路径,可以是目录名、文件名或通配符。expression是用于指定搜索条件的表达式,它可以包含多个选项和操作符。

2、常用选项

选项作用
-amin<分钟>查找在指定时间曾被存取过的文件或目录,单位以分钟计算;
- anewer<参考文件或目录>查找其存取时间较指定文件或目录的存取时间更接近现在的文件或目录;
- atime<24小时数>查找在指定时间曾被存取过的文件或目录,单位以24小时计算;
- cmin<分钟>查找在指定时间之时被更改过的文件或目录;
- cnewer<参考文件或目录>查找其更改时间较指定文件或目录的更改时间更接近现在的文件或目录;
- ctime<24小时数>查找在指定时间之时被更改的文件或目录,单位以24小时计算;
- daystart从本日开始计算时间;
- depth从指定目录下最深层的子目录开始查找;
- empty寻找文件大小为0 Byte的文件,或目录下没有任何子目录或文件的空目录;
- exec<执行指令>假设find指令的回传值为True,就执行该指令;
- false将find指令的回传值皆设为False;
- fls<列表文件>此参数的效果和指定“ - ls”参数类似,但会把结果保存为指定的列表文件;
- follow排除符号连接;
- fprint<列表文件>此参数的效果和指定“ - print”参数类似,但会把结果保存成指定的列表文件;
- fprint0<列表文件>此参数的效果和指定“ - print0”参数类似,但会把结果保存成指定的列表文件;
- fprintf<列表文件><输出格式>此参数的效果和指定“ - printf”参数类似,但会把结果保存成指定的列表文件;
- fstype<文件系统类型>只寻找该文件系统类型下的文件或目录;
- gid<群组识别码>查找符合指定之群组识别码的文件或目录;
- group<群组名称>查找符合指定之群组名称的文件或目录;
- help或——help在线帮助;
- ilname<范本样式>此参数的效果和指定“ - lname”参数类似,但忽略字符大小写的差别;
- iname<范本样式>此参数的效果和指定“ - name”参数类似,但忽略字符大小写的差别;
- inum<inode编号>查找符合指定的inode编号的文件或目录;
- ipath<范本样式>此参数的效果和指定“ - path”参数类似,但忽略字符大小写的差别;
- iregex<范本样式>此参数的效果和指定“ - regexe”参数类似,但忽略字符大小写的差别;
- links<连接数目>查找符合指定的硬连接数目的文件或目录;
- iname<范本样式>指定字符串作为寻找符号连接的范本样式;
- ls假设find指令的回传值为Ture,就将文件或目录名称列出到标准输出;
- maxdepth<目录层级>设置最大目录层级;
- mindepth<目录层级>设置最小目录层级;
- mmin<分钟>查找在指定时间曾被更改过的文件或目录,单位以分钟计算;
- mount此参数的效果和指定“ - xdev”相同;
- mtime<24小时数>查找在指定时间曾被更改过的文件或目录,单位以24小时计算;
- name<范本样式>指定字符串作为寻找文件或目录的范本样式;
- newer<参考文件或目录>查找其更改时间较指定文件或目录的更改时间更接近现在的文件或目录;
- nogroup找出不属于本地主机群组识别码的文件或目录;
- noleaf不去考虑目录至少需拥有两个硬连接存在;
- nouser找出不属于本地主机用户识别码的文件或目录;
- ok<执行指令>此参数的效果和指定“ - exec”类似,但在执行指令之前会先询问用户,若回答“y”或“Y”,则放弃执行命令;
- path<范本样式>指定字符串作为寻找目录的范本样式;
- perm<权限数值>查找符合指定的权限数值的文件或目录;
- print假设find指令的回传值为Ture,就将文件或目录名称列出到标准输出。格式为每列一个名称,每个名称前皆有“. / ”字符串;
- print0假设find指令的回传值为Ture,就将文件或目录名称列出到标准输出。格式为全部的名称皆在同一行;
- printf<输出格式>假设find指令的回传值为Ture,就将文件或目录名称列出到标准输出。格式可以自行指定;
- prune不寻找字符串作为寻找文件或目录的范本样式;
-regex<范本样式>指定字符串作为寻找文件或目录的范本样式;
- size<文件大小>查找符合指定的文件大小的文件;
- true将find指令的回传值皆设为True;
- type<文件类型>只寻找符合指定的文件类型的文件;
- uid<用户识别码>查找符合指定的用户识别码的文件或目录;
- used<日数>查找文件或目录被更改之后在指定时间曾被存取过的文件或目录,单位以日计算;
- user<拥有者名称>查找符和指定的拥有者名称的文件或目录;
- version或——version显示版本信息;
- xdev将范围局限在先行的文件系统中;
- xtype<文件类型>此参数的效果和指定“ - type”参数类似,差别在于它针对符号连接检查。
2.1 示例:查找大于1MB的文件
find /path/to/search -type f -size +1M

上述命令将在指定路径下查找所有大小大于1MB的文件。

2.2 示例:查找最近7天内修改过的文件
find /path/to/search -mtime -7

这个例子将在指定路径下查找最近7天内修改过的文件。

2.3 示例:查找属主为user1的文件
find /path/to/search -user user1

上述命令将在指定路径下查找所有属主为user1的文件。

2.4 示例:执行自定义命令
find /path/to/search -name "*.log" -exec rm {} \;

这个例子将删除指定路径下所有扩展名为.log的文件。

3、高级用法

3.1 使用逻辑操作符

find命令支持逻辑操作符,如-a(与)、-o(或)、!(非),用于组合多个条件。

3.1.1 示例:查找大于1MB且是普通文件的文件
find /path/to/search -type f -size +1M -a -type f

上述命令将查找指定路径下所有大小大于1MB且是普通文件的文件。

3.1.2 示例:查找修改时间在7天之前或文件名以.bak结尾的文件
find /path/to/search \( -mtime +7 -o -name "*.bak" \)

这个例子将查找指定路径下修改时间在7天之前或文件名以.bak结尾的文件。

3.2 搜索多个路径
3.2.1 示例:查找多个目录下的所有文件
find /path/to/dir1 /path/to/dir2 -type f

上述命令将在dir1和dir2两个目录中查找所有文件。

3.3 将find结果用于其他命令
3.3.1 示例:在搜索结果中执行grep
find /path/to/search -type f -name "*.txt" -exec grep "pattern" {} \;

上述命令将在指定路径下所有扩展名为.txt的文件中搜索包含指定模式的行。

3.3.2 示例:将搜索结果输出到文件
find /path/to/search -type f -name "*.log" > logfiles.txt

这个例子将查找指定路径下所有扩展名为.log的文件,并将结果输出到名为logfiles.txt的文件中。

4、find命令的性能优化

在处理大量文件时,find命令的性能可能成为一个考虑因素。以下是一些建议用于提高性能的技巧:

  • 尽量减少-exec选项的使用,因为每个匹配的文件都会执行一次命令。

  • 使用-print选项代替-exec,将结果输出到标准输出,然后使用管道将结果传递给其他命令。

  • 使用-maxdepth选项限制递归的深度,以减少搜索的范围。(默认到底)

十四、bc命令

bc命令 是一种支持任意精度的交互执行的计算器语言。bash内置了对整数四则运算的支持,但是并不支持浮点运算,而bc命令可以很方便的进行浮点运算,当然整数运算也不再话下。

1、语法

bc [选项][参数]

2、选项

-i:强制进入交互式模式;
-l:定义使用的标准数学库;
-w:对POSIX bc的扩展给出警告信息;
-q:不打印正常的GNU bc环境信息;
-v:显示指令版本信息;
-h:显示指令的帮助信息。

3、参数

文件:指定包含计算任务的文件。

4、实例

算术操作高级运算bc命令它可以执行浮点运算和一些高级函数:

echo "1.212*3" | bc 
3.636

设定小数精度(数值范围)

echo "scale=2;3/8" | bc
0.37

参数scale=2是将bc输出结果的小数位设置为2位。

进制转换

#!/bin/bash
abc=192
echo "obase=2;$abc" | bc

执行结果为:11000000,这是用bc将十进制转换成二进制。

#!/bin/bash
abc=11000000
echo "obase=10;ibase=2;$abc" | bc

执行结果为:192,这是用bc将二进制转换为十进制。

计算平方和平方根:

echo "10^10" | bc
echo "sqrt(100)" | bc

http://www.lryc.cn/news/572739.html

相关文章:

  • wireshark过滤显示rtmp协议
  • 服务器获取外网IP,并发送到钉钉
  • 力扣-136.只出现一次的数字
  • 【MATLAB代码】制导方法介绍与例程——追踪法,适用于二维平面,目标是移动的|附完整源代码
  • java项目打包成jar包,并给jmeter使用
  • Lora训练
  • Maven 之工程化开发核心指南:插件配置、pom 文件与依赖管理
  • 一生一芯 PA2 RTFSC
  • Nginx-Ingress-Controller自定义端口实现TCP/UDP转发
  • js 生成过控制点的曲线
  • 数据库part2---子查询
  • 学习笔记丨AR≠VR:透视沉浸式技术的“虚实象限”法则
  • JuiceFS 集群部署详细指南:使用 SeaweedFS 作为数据存储,ETCD 作为元数据存储
  • Redis如何解决缓存击穿,缓存雪崩,缓存穿透
  • Unity技能编辑器深度构建指南:打造专业级战斗系统
  • Pycharm中Jupyter Notebook 插件常用快捷键
  • 1.21SQLCipher 简介
  • Flutter Hero 组件详解及应用
  • window显示驱动开发—输出合并器阶段
  • 企业级权限按钮高效实现方案
  • JS红宝书笔记 8.4 类
  • Spring Boot自动配置原理
  • 三种经典算法无人机三维路径规划对比(SMA、HHO、GWO三种算法),Matlab代码实现
  • 新能源汽车换电站需求大爆发,光储充微电网解决方案为换电运维提供“智慧大脑”
  • 一个用于记录和存储 H.264 视频帧的工具类
  • 【精选】基于SpringBoot的宠物互助服务小程序平台开发 微信小程序宠物互助系统 宠物互助小程序平台设计与实现 支持救助发布+领养申请+交流互动功能
  • 基于微信小程序的美食点餐订餐系统
  • OPENGLPG第九版学习 - 纹理与帧缓存 part1
  • .docx 和 .doc 都是 Word 文档格式的区别
  • el-table复杂表头(多级表头行或列的合并)