Linux_5_Shell脚本编程

目录

  • 1 基础
    • 1.1 程序组成
    • 1.2 程序编程风格
    • 1.3 编程语言
    • 1.4 编程逻辑处理方式
  • 2 shell 脚本语言的基本结构
    • 2.1 shell脚本的用途
    • 2.2 shell脚本基本结构
    • 2.3 创建shell脚本过程
    • 2.4 脚本注释规范
    • 2.5 第一个脚本
    • 2.6 脚本调试
    • 2.7 变量
      • 2.7.1 变量
      • 2.7.2 变量类型
      • 2.7.3 编程语言分类
      • 2.7.4 shell中变量命名法则
      • 2.7.5 变量定义和引用
      • 2.7.6 环境变量
      • 2.7.7 只读变量
      • 2.7.8 位置变量
      • 2.7.9 退出状态码变量
      • 2.7.10 展开命令行
      • 2.7.11 脚本安全和 set 命令
    • 2.8 格式化输出 printf
    • 2.9 算术运算
    • 2.10 逻辑运算
    • 2.11 条件测试命令
      • 2.11.1 变量测试
      • 2.11.2 数值测试
      • 2.11.3 字符串测试
      • 2.11.4 文件测试
    • 2.12 关于()和{}
    • 2.13 组合测试条件
      • 2.13.1 方法 1 []
      • 2.13.2 方法 2 [[]]
    • 2.13 使用read命今来接受输入
  • 3 bash的配置文件
    • 3.1 按生效范围划分两类
    • 3.2 shell登录两种方式分类
      • 3.2.1 交互式登录
      • 3.2.2 非交互式登录
    • 3.3 按功能划分分类
      • 3.3.1 profile类
      • 3.3.2 Bashrc类
    • 3.4 编辑配置文件生效
    • 3.5 Bash 退出任务
  • 4 流程控制
    • 4.1 条件选择
      • 4.1.1 选择执行 if 语句
      • 4.1.2 条件判断 case 语句
    • 4.2 循环
      • 4.2.1 循环执行介绍
      • 4.2.2 for循环
        • **格式1:**
        • **格式2:**
      • 例:加入参数实现三角形
      • 4.2.3 while循环
      • 4.2.4 until循环
      • 4.2.4 循环控制语句 continue
      • 4.2.5 循环控制语句 break
      • 4.2.6 循环控制 shift 命令
      • 4.2.7 while read 特殊用法
      • 4.2.8 select 循环与菜单
  • 5 函数介绍
    • 5.1 管理函数
      • 5.1.1 定义函数
      • 5.1.2 查看函数
      • 5.1.3 删除函数
    • 5.2 函数调用
      • **5.2.1 交互式环境调用函数**
      • 5.2.2 在脚本中定义及使用函数
      • 5.2.3 使用函数文件
    • 5.3 函数返回值
    • 5.4 环境函数
    • 5.5 函数参数
    • 5.6 函数变量
    • 5.7 函数递归
  • 6 其它脚本相关工具
    • 6.1 信号捕捉 trap
    • 6.2 创建临时文件 mktemp
    • 6.3 安装复制文件 install
    • 6.4 交互式转化批处理工具 expect
  • 7数组
    • 7.1 数组介绍
    • 7.2 声明数组
    • 7.3 数组赋值
    • 7.4 显示所有数组
    • 7.5 引用数组
    • 7.6 删除数组
    • 7.7 数组数据处理
    • 7.8 关联数组
    • 7.9 范例
  • 8 字符串处理
    • 8.1 字符串切片
    • 8.2 查找替换、
    • 8.3 查找并删除
    • 8.4 字符大小写转换
  • 9 高级变量
    • 9.1 高级变量赋值
    • 9.2 高级变量用法-有类型变量
    • 9.3 变量间接引用
      • 9.3.1 eval命令
      • 9.3.2 间接变量引用
      • 9.3.3变量引用reference

1 基础

1.1 程序组成

  • 程序:算法+数据结构
  • 数据:是程序的核心
  • 算法:处理数据的方式
  • 数据结构: 数据在计算机中的类型和组织方式

1.2 程序编程风格

面向过程语言

  • 做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理
  • 问题规模小,可以步骤化,按部就班处理
  • 以指令为中心,数据服务于指令
  • C,shell

面向对象语言

  • 一种认识世界、分析世界的方法论。将万事万物抽象为各种对象
  • 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
  • 对象是类的具象,是一个实体
  • 问题规模大,复杂系统
  • 以数据为中心,指令服务于数据
  • java,C#,python,golang等

1.3 编程语言

计算机:运行二进制指令

编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言

  • 低级编程语言:

    • 机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写

    • 汇编:用一些助记符号替代机器指令,称为汇编语言

      ​ 如:ADDAB 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中

      ​ 汇编语言写好的程序需要汇编程序转换成机器指令

      ​ 汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言

  • 高级编程语言:

    • 编译:高级语言–>编译器–>机器代码文件->执行,如: C,C++
    • 解释:高级语言–>执行–>解释器–>机器代码,如: shell,python,php,JavaScript,perl

编译和解释型语言
Linux_5_Shell脚本编程_第1张图片
Linux_5_Shell脚本编程_第2张图片

1.4 编程逻辑处理方式

Linux_5_Shell脚本编程_第3张图片
Linux_5_Shell脚本编程_第4张图片

三种处理逻辑

  • 顺序执行
  • 选择执行
  • 循环执行

2 shell 脚本语言的基本结构

2.1 shell脚本的用途

  • 自动化常用命令
  • 执行系统管理和故障排排除
  • 创建简单的应用程序
  • 处理文本或文件

2.2 shell脚本基本结构

shell脚本编程:是基于过程式、解释执行的语言

编程语言的基本结构:

  • ​ 各种系统命令的组合
  • ​ 数据存储:变量、数组
  • ​ 表达式:a+b
  • ​ 控制语句: if

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

格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

2.3 创建shell脚本过程

第一步:使用文本编辑器来创建文本文件

  • 第一行必须包括shell声明序列: #!
  • 示例:
  • #!/bin/bash
  • 添加注释,注释以#开头

第二步:加执行权限

  • 给予执行权限,在命令行上指定脚本的绝对或相对路径

第三步:运行脚本

  • 直接运行解释器,将脚本作为解释器程序的参数运行
autocmd BufNewFile *.sh exec ":cal SetTitle()"
func SetTitle()
	if expand("%:e") == 'sh'
	call setline(1,"#!/bin/bash")
	call setline(2,"#")
	call setline(3,"#*****************************************************")
	call setline(4,"#Author:			minNexus")
	call setline(5,"#QQ:				1299575088")
	call setline(6,"#Date:				".strftime("%Y-%m-%d"))
	call setline(7,"#FileName:			.expand("%"))
	call setline(8,"#URL:				https://blog.csdn.net/qq_24472227?type=blog")		
	call setline(9,"#Description:		The test script")
	call setline(10,"#Copyright (C):	".strftime("%Y")," All rights reserved")
	call Setline(11,"#****************************************************")
	call setline(12,"")
	endif
endfunc
autocmd BufNewFile * normal G

2.4 脚本注释规范

  1. 第一行一般为调用使用的语言

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

  3. 版本号

  4. 更改后的时间

  5. 作者相关信息

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

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

2.5 第一个脚本

[root@CentOS8 ~]#vim hello.sh
输入echo hello world
[root@CentOS8 ~]#./hello.sh	执行hello.sh
hello world

2.6 脚本调试

  • 语法错误:导致后续命令不再执行

  • 命令错误:后续命令仍然执行

  • 逻辑错误

检测脚本中的语法错误,bash后加-n,提示出错行不一定准确,不执行
bash -n /path/to/some_script

一定程度上检查逻辑出错 与 命令出错,调试并执行,加-x,逐行执行并显示
bash -x /path/to/some_script

注意:

  • bash ScriptName 开启一个子进程,在里面执行脚本
  • source ScriptName 在当前进程执行脚本,不开启子进程,如果在其中修改了变量,则修改了当前shell的变量,不推荐使用,除非配置文件

2.7 变量

2.7.1 变量

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

2.7.2 变量类型

类型:

  • 内置变量,如: PS1,PATH,UID,HOSTNAME,PPID,$ , , ?,BASHPID,HISTSIZE(使用$查看)
  • 用户自定义变量

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

  • 数据存储方式
  • 参与的运算
  • 表示的数据范围

变量数据类型:

  • 字符
  • 数值:整型、浮点型 (bash不支持浮点型)

2.7.3 编程语言分类

静态和动态语言

  • 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
  • 动态编译语言:不用事先声明,可随时改变类型,如: bash,Python

强类型和弱类型语言

  • 强类型语言: 不同类型数据操作,必须经过强制转换才同一类型才能运算,如java , c#,python

​ 如:以下python代码

print('magedu'+ 10) 提示出错,不会自动转换类型
print('magedu'+str(10)) 结果为magedu10,需要显示转换类型
  • 弱类型语言:语言的运行时会隐式类型转换。无须指定类型,默认均为字符型,参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用

    如: bash ,php,javascript

2.7.4 shell中变量命名法则

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

只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线“.",和主机名相反

见名知义,用英文单词命名,并体现出实际作用,不要用简写,如: ATM

统一命名规则:驼峰命名法studentname,大驼峰StudentName ,小驼峰studentName

  • 变量名大写:STUDENTNAME
  • 局部变量小写
  • 函数名小写

2.7.5 变量定义和引用

变量的生效范围等标准划分变量类型

  • 普通变量:生效范围为当前shel进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
  • 环境变量:生效范围为当前shell进程及其子进程
  • 本地变量:生效范围为当前shell进程中某代码片断,通常指函数

变量赋值:

name='value'

value可以是以下多种形式

  • 直接字串: name=‘root’
  • 变量引用: name=“$USER”
  • 命令引用: name=COMMAND 或者 name=$(COMMAND)

注意:变量赋值临时生效,终端退出将自动删除,不过为了安全起见最好使用完手动删除。脚本中的变量也随脚本结束自动删除
变量引用:

$name
${name}

弱引用和强引用

"$name"	双引号,弱引用,其中的变量引用会被替换为变量值
'$name'	单引号,强引用,其中的变量引用不会被替换为变量值,而保持原字符串

显示已定义的所有变量

set

删除变量

unset name

例:

1[root@CentOS8 ~]#ls	
f1.txt  f2.txt  f3.txt  issue.bak  passwd  scripts  test.img
[root@CentOS8 ~]#FILE=*	将当前文件夹所有文件名保存至变量FILE中
[root@CentOS8 ~]#echo $FILE
f1.txt f2.txt f3.txt issue.bak passwd scripts test.img

2[root@CentOS8 ~]#FILE="i am $HOSTNAME" 将hostname的变量名存入变量FILE中
[root@CentOS8 ~]#echo $FILE
i am CentOS8.Joyce.person1

3[root@CentOS8 ~]#seq 10
1
2
3
4
5
6
7
8
9
10
[root@CentOS8 ~]#NUM=`seq 10`
[root@CentOS8 ~]#echo $NUM
1 2 3 4 5 6 7 8 9 10
[root@CentOS8 ~]#echo "$NUM" 加双引号保留多行格式
1
2
3
4
5
6
7
8
9
10

2.7.6 环境变量

环境变量:可以使子进程(包括子子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
一旦子进程修改从父进程继承的变量,新的值将传递给子子进程
一般只在系统配置文件中使用,在脚本中较少使用

声明环境变量 并赋值

export name=VALUE
declare -x name=VALUE

或分2步
name=VALUE
export name

变量引用

$name
${name}

例:

[root@CentOS8 ~]#NAME=joyce
[root@CentOS8 ~]#AGE=21
[root@CentOS8 ~]#echo $NAME$AGE
joyce21
[root@CentOS8 ~]#echo $NAME_$AGE
21
[root@CentOS8 ~]#echo ${NAME}_$AGE
joyce_21

显示所有环境变量

env
printenv
export
declare -x

删除变量:

unset name

bash内建的环境变量

PATH
SHELL
USER
UID
HOME
PWD
SHLVL	当前所在shell的深度,使用bash进入下一shell(开子进程)后,深度+1,最低是1
LANG	当前设置语言与编码格式
MAIL	邮箱位置
HOSTNAME
HISTSIZE
_	下划线,表示前一命令的最后一个参数,前面记得加$

脚本:显示系统信息

RED="\E[1;31m"
GREEN="\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 'Model name'|tr -s ' '|cut -d : -f2`$END"
echo -e	"MEMORY:		$RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e	"DISK:			$RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
echo -e	"\E[1;32m-----------------------------------------------------------------------------------$END"

2.7.7 只读变量

只读变量:只能声明定义,但后续不能修改和删除,即常量,退出终端自动删除

声明只读变量: 加-r

readonly name
declare -r name

查看只读变量:

readonly [-p]
declare -r

2.7.8 位置变量

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

$1$2... 对应第1个、第2个等参数,shift [n]换位置
$0	命令本身,包括路径,想只显示名字可以写`basename $0`
$*	传递给脚本的所有参数,全部参数合为一个字符串
$@	传递给脚本的所有参数,每个参数为独立字符串
$#	传递给脚本的参数的个数

注意: $@ $ 只在被双引号包起来的时候才会有差异*

清空所有位置变量

set --

例:

[root@CentOS8 ~]#cat arg.sh 查看arg.sh文件
#!/bin/bash
#
#*****************************************************************************
#Author:                        minNexus
#QQ:                            1299575088
#Date:                          2023-07-25
#FileName:                      arg.sh
#URL:                           https://blog.csdn.net/qq_24472227?type=blog
#Description:                   The test script
#Copyright (C):                 2023 All rights reserved
#****************************************************************************

echo "lst arg is $l"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${l0}"
echo "1lst arg is ${11}"

echo "The number of arg is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"

[root@CentOS8 ~]#bash arg.sh {a..z} 运行脚本,加上a..z参数
lst arg is
2st arg is b
3st arg is c
10st arg is
1lst arg is k
The number of arg is 26
All args are 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
All args are 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
The scriptname is arg.sh

2.7.9 退出状态码变量

进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功或失败,$?取值范例 0-255

  • $?的值为0 代表成功
  • $?的值是1到255 代表失败

例:

[root@CentOS8 ~]#curl www.google.com &> /dev/null
[root@CentOS8 ~]#echo $?
7

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

exit [n]

注意:

  • 脚本中一旦遇到exit命令,脚本会立即终止,终止退出状态取决于exit命令后面的数字
  • 如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

2.7.10 展开命令行

展开命令执行顺序:

把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
命令替换$()和``
再次把命令行分成命令词
展开文件通配* ? [abc]等等
准备I/0重导向 <>
运行命令
防止扩展:
反斜线(\)会使随后的字符按原意解释

加引号来防止扩展:
单引号('')-防止所有扩展
双引号 ("")也可防止扩展,但是以下情况例外: $(美元符号)

变量扩展:
``	反引号,命令替换
\	反斜线,禁止单个字符扩展
!	叹号,历史命令替换

2.7.11 脚本安全和 set 命令

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

$- 变量
h	hashal,打开洗项后,Shell会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h洗项关闭
i	interactive-comments,包含这个选项说明当前的 shel 是一个交式的 shel。所谓的交式shell,在脚本中i选项是关闭的
m	monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等
B	braceexpand,大括号{}扩展,set +B关闭{}的功能
H	history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!"返回上最近的一个历史命令,“!n”返回第 n个历史命令

set 命今实现脚本安全	推荐打开-eu
-u	在扩展一个没有设置的变量时,显示错误信息,等同set -o nounset,避免出现删除整个目录
-e	如果一个命令返回一个非0退出状态值(失败)就退出,等同set -o errexit
-o	option 显示,打开或者关闭选项
	显示选项	set-o
	打开选项	set -o选项
	关闭选项	set +o选项
-x	当执行命令时,打印命令及其参数,类似bash -x

2.8 格式化输出 printf

格式

printf "指定的格式" "文本1" "文本2"...
常用格式替换符
%s		字符串
%f		浮点格式
%b		相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%c		ASCII字符,即显示对应参数的第一个字符
%d,%i	十进制整数
%o		八进制值
%u		不带正负号的十进制值
%x		十六进制值 (a-f)
%X		十六进制值 (A-F)
%%		

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

常用转义字符:

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

例:

[root@CentOS8 ~]#printf "(%.3f)\n" 1 2 3
(1.000)
(2.000)
(3.000)

[root@CentOS8 ~]#printf "%s\n%s" 1 2 3 4;echo
1
23
4

2.9 算术运算

shell 支持算术运算,但只支持整数,不支持小数
bash中的算术运算:

+
-
*
/
%	取模,即取余数,%5的结果在0-4
**	乘方

乘法符号有些场景中需要转义
实现算术运算:

let var=算术表达式
var=$[算术表达式]
var=$((算术表达式))
var=$(expr argl arg2 arg3 ...)	注意:expr里*可能被当作通配符,乘法要用\*
declare -i var = 数值
echo'算术表达式'|bc

例:

[root@CentOS8 ~]#echo -e "\E[1;$[RANDOM%7+31]mhello\e[0m"	打印随机颜色的hello,颜色编号31-37
hello

[root@CentOS8 ~]#expr 99 \* 3	
297

[root@CentOS8 ~]#echo "scale=3;3/7"|bc
.428

内建的随机数生成器变量
$RANDOM 取值范围 0-32767

增强型赋值:

+=	i+=10	相当于 i=i+10
-=	i-=j	相当于 i=i-j
*=
/=
%=
++	i++,++i		相当于 i=i+1
--	i--,--i		相当于 i=i-1

2.10 逻辑运算

true false
 1     0

与:&		与0为0,与1保留
	1与1=1
	1与0=0
	0与1=0
	0与0=0
或:|		同0为0
	1或1=1
	1或0=1
	0或1=1
	0或0=0
非:!
	!1 =0	! true
	!0 =1 	! false
异或:^	异或的两个值,相同为,假0;不同为真,非0。
	1^1=0
	1^0=1
	0^1=1
	0^0=0
		两个数字异或,将结果与任意一个数字异或,结果是另一数字
	12^8=4
	8^4=12
	12^4=8

例:

[root@CentOS8 ~]#i=i=10;j=20;echo i=$[i^(i^j)] j=$[j^(i^j)]	使用异或交换i和j的值
i=20 j=10
[root@CentOS8 ~]#i=10;j=20;i=$[i^j];j=$[i^j];i=$[i^j];echo i=$i,j=$j
i=20,j=10

短路运算

短路与
CMD1 短路与 CMD2

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

短路或
CMD1 短路或CMD2

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

2.11 条件测试命令

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

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

条件测试命令

test EXPRESSION	 等价于  [ EXPRESSION ]	推荐使用[ ] 
[[ EXPRESSION ]]

注意:EXPRESSION前后必须有空白字符

2.11.1 变量测试

-v VAR	变量VAR是否设置,即是否存在
-R VAR	变量VAR是否设置并引用

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

[root@CentOS8 ~]#[ -v name ] 或 test -v NAME
[root@CentOS8 ~]#echo $?
1

2.11.2 数值测试

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

例:

[root@CentOS8 ~]#x=33
[root@CentOS8 ~]#y=44
[root@CentOS8 ~]#[ $x -gt $y ]	必须假$
[root@CentOS8 ~]#echo $?
1
[root@CentOS8 ~]#[ $x -lt $y ]
[root@CentOS8 ~]#echo $?
0

2.11.3 字符串测试

-z "STRING"	字符串是否为空,未定义或空为真,不空为假
-n "STRING"	字符串是否不空,不空为真,空为假

=	是否等于
!=	是否不等于

​ ascii码是否大于ascii码

<	是否小于
==	左侧字符串是否和右侧的PATTERN相同
注意:此表达式用于[[ ]]中,PATTERN为通配符
=~	左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配

注意: 此表达式用于[[ ]]中;扩展的正则表达式

  • 即只在正则表达式才用双中括号[[]],其他都用[]
  • 且在[[ == ]] 右侧的*,单列则表示通配符,只作为字符需要加""或\转义
  • 字符串最好两边加"",否则可能出现问题
[root@CentOS8 ~]#name="hello babe"
[root@CentOS8 ~]#[ $name ]
-bash: [: hello: unary operator expected
[root@CentOS8 ~]#[ "$name" ]
[root@CentOS8 ~]#echo $?
0

例:

[root@CentOS8 ~]#title1=ceo
[root@CentOS8 ~]#title1=cto
[root@CentOS8 ~]#[ $title1 = $title2 ] 字符串之间必须加空格 ,不加空格则成赋值
[root@CentOS8 ~]#echo $?
1

2通配符
[root@CentOS8 ~]#FILE=test.log	
[root@CentOS8 ~]#[[ "$FILE == *.log" ]]
[root@CentOS8 ~]#echo $?
0

3正则表达式
[root@CentOS8 ~]#FILE=test.log
[root@CentOS8 ~]#[[ "$FILE" =~ \.log$ ]]
[root@CentOS8 ~]#echo $?
0

[root@CentOS8 ~]#IP=1.2.3.444	判断ip地址是否合法(在255.255.255.255之间)
[root@CentOS8 ~]#[[ $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@CentOS8 ~]#echo $?
1
[root@CentOS8 ~]#IP=1.2.3.4
[root@CentOS8 ~]#echo $?
0

2.11.4 文件测试

存在性测试

-a FILE	同 -e
-e FILE 文件存在性测试,存在为真,否则为假
-b FILE 是否存在且为块设备文件
-C FILE	是否存在且为字符设备文件
-d FILE 是否存在且为目录文件
-f FILE	是否存在且为普通文件,判断软链接则判断软链接指定的文件
-h FILE 或 -L FILE: 存在且为符号链接文件
-p FILE 是否存在且为命名管道文件
-S FILE	是否存在且为套接字文件

例:

[root@CentOS8 ~]#[[ -c  /dev/zero  ]]	判断是否为字符文件
[root@CentOS8 ~]#echo $?
0

文件权限测试.

-r FILE 是否存在且可读
-W FILE 是否存在且可写
-X FILE 是否存在且可执行
-u FILE 是否存在且拥有suid权限
-g FILE 是否存在且拥有sgid权限
-k FILE 是否存在且拥有sticky权限

例:

[root@CentOS8 ~]# [[ -x CHOOK_RABBIT.sh ]] 判断是否有执行权限
[root@CentOS8 ~]#echo $?
0
[root@CentOS8 ~]#chmod 0 CHOOK_RABBIT.sh   去除所有权限
[root@CentOS8 ~]# [[ -x CHOOK_RABBIT.sh ]] 判断是否有执行权限
[root@CentOS8 ~]#echo $?
1

[root@CentOS8 ~]# [[ -r CHOOK_RABBIT.sh ]] 判断是否有可读权限,因为我们是root,所有仍然可以读
[root@CentOS8 ~]#echo $?
0
[root@CentOS8 ~]# [[ -w CHOOK_RABBIT.sh ]] 判断是否有写入权限,因为我们是root,所有仍然可以写
[root@CentOS8 ~]#echo $?
0

文件属性测试

-S FILE	是否存在且非空
-t fd	fd 文件描述符是否在某终端已经打开
-N FILE 文件自从上一次被读取之后是否被修改过
-O FILE 当前有效用户是否为文件属主
-G FILE 当前有效用户是否为文件属组
FILE1 -ef FILE2	: FILE1是否是FILE2的硬链接
FILE1 -nt FILE2 : FILE1是否新于FILE2(mtime)
FILE1 -ot FILE2 : FILE1是否旧于FILE2

2.12 关于()和{}

(CMD1;CMD2;...)	都可以批量执行多个命令
{ CMD1;CMD2;...; }最后加;{ }内前后加空格
  • ()会开启子shell,继承父进程的变量。list中变量赋值及内部命令执行后,不影响后续的环境。帮助参看:man bash 搜索(list)

  • {;}不会启子shell,在当前shell中运行,会影响当前shell环境,帮助参看:man bash 搜索{list;}

例:

[root@CentOS8 ~]#name=babe;(echo $name;name=hello;echo $name);echo $name	使用()
babe
hello
babe
[root@CentOS8 ~]#name=babe;{ echo $name;name=hello;echo $name; };echo $name	使用{}
babe
hello
hello

2.13 组合测试条件

2.13.1 方法 1 []

EXPRESSION1 -a EXPRESSION2	并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
EXPRESSION1 -o EXPRESSION2	或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[ ! EXPRESSION ]![ exp ]			取反

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

2.13.2 方法 2 [[]]

COMMAND1 && COMMAND2	#并且,短路与,代表条件性的AND	THEN

如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2

COMMAND1 || COMMAND2	#或者,短路或,代表条件性的OR	ELSE

如果COMMAND1 成功,将不执行COMMAND2否则,将执行COMMAND2

!COMMAND	非,取反

例:

[root@CentOS8 ~]#NAME=joycee;id $NAME &> /dev/null && echo "$NAME is exist" || echo "$NAME is not exist"
joycee is not exist

[root@CentOS8 ~]#NANAME=joycee;id $NAME &> /dev/null && echo "$NAME is exist" || (useradd $NAME;echo $NAME is created)
joycee is created

注意:&& 如果和 || 混用,则&& 要放前,|| 放后

2.13 使用read命今来接受输入

使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
格式

read [options] [name ...]

常见选项:

-p	指定要显示的提示
-s	静默输入,一般用于密码
-n	N 指定输入的字符长度N
-d	'字符',输入结束符
-t	N TIMEOUT为N秒
[root@CentOS8 ~]#read
joyce
[root@CentOS8 ~]#echo $REPLY
joyce

[root@CentOS8 ~]#read NAME TITLE
joyce babe
[root@CentOS8 ~]#echo $NAME $TITLE
joyce babe

[root@CentOS8 ~]#read -p "are u ok?" ANSWER
are u ok?not bad
[root@CentOS8 ~]#echo $ANSWER
not bad

[root@CentOS8 ~]#echo 1 2 > test.txt	重定向
[root@CentOS8 ~]#read a b < test.txt;echo $a; echo $b
1
2

注意:

[root@CentOS8 ~]#echo 1 2 | read a b | echo $a $b
1 2
[root@CentOS8 ~]#echo 1 2 | (read a b; echo $a $b)
1 2
[root@CentOS8 ~]#echo 1 2 | read a b; echo $a $b	管道里,如果不加括号或|,则中间和后面是两个独立的shell,因此二者的ab不等
1 2

3 bash的配置文件

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

3.1 按生效范围划分两类

全局配置

/etc/profile
/etc/profile.d/*.sh
/etc/bashrc

个人配置:

~/.bash_profile
~/.bashrc

3.2 shell登录两种方式分类

3.2.1 交互式登录

  • 直接通过终端输入账号密码登录
  • 使用su - UserName切换用户

配置文件执行顺序:

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

3.2.2 非交互式登录

su UserName

图形界面下打开的终端
执行脚本
任何其它的bash实例

执行顺序:

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

3.3 按功能划分分类

profile类和bashrc类

3.3.1 profile类

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

全局: /etc/profile,/etc/profile.d/*.sh
个人:~/.bash_profile

作用:

  1. 用于定义环境变量
  2. 运行命令或脚本

3.3.2 Bashrc类

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

全局: /etc/bashrc
个人:~/.bashrc

作用:

  1. 定义命令别名和函数
  2. 定义本地变量

3.4 编辑配置文件生效

修改profile和bashrc文件后有生效两种方法

  1. 重新启动shell进程
  2. source 或 . 配置文件

3.5 Bash 退出任务

保存在~/.bash_logout文件中 (用户),在退出登录shell时运行

功能

  • ​ 创建自动备份
  • ​ 清除临时文件

4 流程控制

4.1 条件选择

条件判断:

if
case
&& ||

4.1.1 选择执行 if 语句

格式:

if CMDs; then CMDs;[ elif CMDs; then CMDs; ]... [ else CMDs; ] fi

单分支:

if 判断条件;then
	条件为真的分支代码
fi

双分支:

if 判断条件; then
	条件为真的分支代码
e1se
	条件为假的分支代码
fi

多分支:

if 判断条件1; then
	条件1为真的分支代码
elif 判断条件2; then
	条件2为真的分支代码
... 
e1se
	以上条件都为假的分支代码
fi

说明:

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

4.1.2 条件判断 case 语句

格式:

case WORD in [PATTERN [| PATTERN]...) CMDs ;;]...esac

此处PATTERN指通配符的模式

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

case支持glob风格的通配符:

*	任意长度任意字符
?	任意单个字符
[]	指定范围内的任意单个字符
|	或,如 a或b
  1. G R E E N " 清理日志 " GREEN"清理日志" GREEN"清理日志"END
    ;;

4.2 循环

4.2.1 循环执行介绍

Linux_5_Shell脚本编程_第5张图片

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

重复运行次数

  • 循环次数事先已知
  • 循环次数事先未知

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

Linux_5_Shell脚本编程_第6张图片

4.2.2 for循环

格式1:

for NAME [in WORDS ...] ; do COMMANDS; done
						(WORD之间使用空白符(空格、Tab键、回车)分割)for	变量名 in 列表;do
		循环体
donefor	变量名 in 列表
do
		循环体
done

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

for循环列表生成方式:

  • 直接给出列表
  • 整数列表
{start..end}
$(seq [start [step]] end)
  • 返回列表的命令:
$(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@,$#,$*等位置参数 ( ∗ 将变量视为单个, *将变量视为单个, 将变量视为单个,@将变量视为整体)

死循环:

for((;;)) ; do CMDs;done

例:

[root@CentOS8 ~]#for i in {001..8..2};do echo $i;  done
001
003
005
007
[root@CentOS8 ~]#for i in `seq 5`;do echo $i;  done
1
2
3
4
5
[root@CentOS8 ~]#for i in $(seq 5);do echo $i;  done
1
2
3
4
5

例:计算1+2+3+…+100的和

[root@CentOS8 ~]#seq -s+ 100|bc
5050[root@CentOS8 ~]#sum=0;for i in {1..100};do let sum+=i;done;echo $sum
5050

例:

[root@CentOS8 scripts]#cat sum.sh
#!/bin/bash

sum=0
for i in $@ ;do
        let sum+=i
done

echo $sum

[root@CentOS8 scripts]#chmod +x sum.sh
[root@CentOS8 scripts]#./sum.sh 1  3 4 5 6
19

例:九九乘法表的实现

[root@CentOS8 scripts]#cat ./9x9.sh
#!/bin/bash

for j in {1..9};do
        for i in `seq $j`;do
                echo -e "${i}x$j=$((i*j))\t\c"
        done
        echo
done
[root@CentOS8 scripts]#./9x9.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

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

#1 yyyy-mm-dd10.sh ,准备数据
#创建YYYY-MM-DD,当前日期一年前365天到目前共365个目录,里面有10个文件,$RANDOM.Tog

[root@centos8 ~]#cat for_dir20.sh
#!/bin/bash
for i in {1..365};do
    DIR=`date -d "-$i day" +%F`
    mkdir /data/test/$DIR
    cd /data/test/$DIR
    for n in {1..10};do
    		touch $RANDOM.log
    done
done

#2 移动到YYYY-MM/DD/下
[root@centos8 ~]#cat for_mv20.sh
#!/bin/bash
#
DIR=/data/test
cd $DIR
for DIR in *;do
    YYYY_MM=`echo $DIR |cut -d"-" -f1,2`	#将年月和日期拆分成2部分,这里是年月
    DD=`echo $DIR | cut -d"-" -f3`			  #这里是日期
    [ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
    mv $DIR/*	$YYYY_MM/$DD
done

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

NET=10.0.0
for ID in {1..254};do
        {
        ping -c1 -W1 $NET.$ID &> /dev/null && echo $NET.$ID is up  || echo $NET.$ID is down
        }&      #实现并行
done
wait	#默认并行不会自动退出,加上wait使其自动执行下一个命令

格式2:

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

|=10;((l++))

Linux_5_Shell脚本编程_第7张图片

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

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次
  • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

例:实现1累加到100

for ((sum=0;i<=100;i++));do
        let sum+=i
done
echo $sum

例:九九乘法表的实现_2

for ((i=1;i<10;i++));do
        for((j=1;j<=i;j++));do
                echo -e "${j}x$i=$((j*i))\t\c"
        done
        echo
done

例:实现国际象棋的棋盘(较垃圾)

for ((i=1;i<=8;i++));do
        if((i%2));then
                for((x=0;x<4;x++));do
                        for ((j=0;j<8;j++));do
                                if ((j%2));then
                                        echo -e '\E[47m        \E[0m\c' ;
                                else
                                        echo -e '\E[40m        \E[0m\c' ;
                                fi
                        done
                        echo
                done
        else
                for((x=0;x<4;x++));do
                        for ((j=0;j<8;j++));do
                        if ((j%2));then
                        echo -e '\E[40m        \E[0m\c' ;
                else
                        echo -e '\E[47m        \E[0m\c' ;
                fi
                done
                        echo
                done
        fi
done

Linux_5_Shell脚本编程_第8张图片

例:实现三角形

for((i=1;i<=10;i++));do
        #for((z=0;z<=10-i;z++));do
        for((z=10;z-i>0;z--));do
                echo -e ' \c';
        done
                for((j=1;j<=2*i-1;j++));do
                        echo -e '*\c'
                done
                echo
done

Linux_5_Shell脚本编程_第9张图片

例:加入参数实现三角形

read -p "请输入三角形的行数:" line
for((i=1;i<=$line;i++));do
        #for((z=0;z<=10-i;z++));do
        for((z=$line;z-i>0;z--));do
                echo -e ' \c';
        done
                for((j=1;j<=2*i-1;j++));do
                        echo -e '*\c'
                done
                echo
done

Linux_5_Shell脚本编程_第10张图片

4.2.3 while循环

格式:

while COMMANDs; do COMMANDS; done

while CONDITION; do
		循环体
done

说明:

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

  • 进入条件: CONDITION为true
  • 退出条件: CONDITION为false

死循环:

#方法1
while true; do
		循环体
done

#方法2
while :; do
		循环体
done

范例:使用while实现磁盘报警

WARNING=10	#阈值
while : ;do
        USE=`df | sed -rn '/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p' |sort -nr | head -n1`
        if [ $USE -gt $WARNING ] ; then
                echo DISK will be full from `hostname -I` | mail -s "Disk Warning" 1299575088@qq.com
        fi
        sleep 10	#10秒循环一次
done

例:死循环

[root@CentOS8 scripts]#while :;do echo ok ;sleep 1 ;done
ok
ok
ok
ok
ok

4.2.4 until循环

格式:

until COMMANDS; do COMMANDS; done

until CONDITION; do
	循环体
done

说明:

  • 进入条件:CONDITION为false
  • 退出条件:CONDITION为true

死循环

until false; do
	循环体
done

4.2.4 循环控制语句 continue

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

格式:

while CONDITION1; do
	CMD1
	...
	if CONDITION2; then
		continue
		CMDn
		...
done

例:

#1
for((i=0;i<10;i++));do
        for((j=0;j<10;j++));do
                [ $j -eq 5 ] && continue 1	#1可以不写,代表终止该层循环的本次循环
                echo $j
        done
        echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
#2
for((i=0;i<10;i++));do
        for((j=0;j<10;j++));do
                [ $j -eq 5 ] && continue 2	#2代表终止外层循环的本次循环,使外层循环进入下次循环
                echo $j
        done
        echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4

4.2.5 循环控制语句 break

break [N]:提前结束第N层整个循环,最内层为第1层
格式:

while CONDITION1; do
	CMD1
	...
	if CONDITION2; then
		break
	fi
	CMDn

例:

#1
for((i=0;i<10;i++));do
        for((j=0;j<10;j++));do
                [ $j -eq 5 ] && break 1 #1可以不写,表示结束内层循环,使外层循环进入下一轮
                echo $j
        done
        echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------

#2
for((i=0;i<10;i++));do
        for((j=0;j<10;j++));do
                [ $j -eq 5 ] && break 2 #2表示结束外层循环,即结束所有循环
                echo $j
        done
        echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4

例:菜品选择

sum=0
COLOR1='echo -e \033[1;31m'
COLOR2='echo -e \033[1;32m'
END="\033[0m"

while :;do
        echo -e "\033[33;1m\c"
        cat <<-EOF
1)2)3)4)5)6)肉
        EOF
        echo -e "\033[0m"

read -p "请选择菜品:" MENU
case $MENU in
1|4)
        $COLOR1'菜价:$10'$END
        let sum+=10
        ;;
3|5)
     $COLOR1'菜价:$20'$END
     let sum+=20
     ;;
2)
     $COLOR1'菜价:$888'$END
     let sum+=888
     ;;
6)
    $COLOR2"你点的菜品总价是:$sum"$END
     break
     ;;
*)
     echo "输入错误!"

     ;;
        esac
        $COLOR2"你点的菜品总价是:$sum"$END
done

4.2.6 循环控制 shift 命令

shift[n]用于将参量列表list 左移指定次数,缺省为左移一次。

参量列表list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

例:shift批量创建用户

PASS=110420
while [ "$1" ];do
        useradd $1 && echo $1 is created || echo $1 is exists
        echo $PASS | passwd --stdin $1
        shift	#shift 1 一次只跳过1个,2则跳过2个
done
[root@CentOS8 scripts]#bash shift_user.sh Tom Alice
Tom is created
Changing password for user Tom.
passwd: all authentication tokens updated successfully.
Alice is created
Changing password for user Alice.
passwd: all authentication tokens updated successfully.

例:判断某个ip访问的次数

[root@CentOS8 data]#sed -En '/^ESTAB/s#.*[: ]([^:]+):[0-9]+ $#\1#p' ss.log | sort |uniq -c
		1	10.0.0.1
	200	10.0.0.8

4.2.7 while read 特殊用法

while循环的特殊用法,遍历文件或文本的每一行,即逐行处理

支持stdin

格式

while read line; do
	循环体
done < /PATH/FROM/SOMEFILE

说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

例:

[root@centos8 ~]#echo joyce | read X ; echo $X	# 管道|开启子进程

[rootcentos8 ~]#echo joyce | while read X ; do echo $X;done
joyce

[root@centos8 ~]#echo joyce | { read X ; echo $X; }
joyce

[root@centos8 ~]#echo joyce | ( read X ; echo $X )
joyce

[root@centos8 ~]#echo joyce wang zhang | ( read X Y Z; echo $X $Y $Z )
joyce wang zhang

  [root@centos8 ~]#echo joyce wang zhang | while read X Y Z; do echo $X $Y $Z;done
joyce wang zhang

例:使用while read实现磁盘报警

WARNING=10
MAIL=1299575088@qq.com
df |sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p" |while read DEVICE USE;do
        if [ $USE -gt $WARNING ] ;then
                echo "$DEVICE will be full,use:$USE" |  mail -s "DISK WARNING" $MAIL
        fi
done

例:查看/sbin/nologin的shell类型的用户名和UID

while read line ;do
                if [[ "$line" =~ /sbin/nologin$  ]] ; then
                        echo $line | cut -d: -f1,3
                fi
done < /etc/passwd

例:存放大量单词文件

[root@CentOS8 scripts]#wc -l /usr/share/dict/linux.words
479828 /usr/share/dict/linux.words

4.2.8 select 循环与菜单

格式:

select NAME [in WORDS ... ;] do COMMANDS; done

select variable in list ;do
	循环体命令
done

说明

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示 PS3 提示符,等待用户输入
  • 用户输入菜单列表中的某个数字,执行相应的命令
  • 用户输入被保存在内置变量REPLY中
  • select 是个无限循环,因此要记住用 break 命令退出循环,或用exit 命令终止脚本。也可以按 ctrl+c 退出循
  • select经常和 case 联合使用
  • 与for 循环类似,可以省略 in list,此时使用位置参量

5 函数介绍

5.1 管理函数

函数由两部分组成:函数名和函数体

帮助参看:help function

5.1.1 定义函数

function name { COMMANDS ; } or name () { COMMANDS ; }
 
#语法一:
func_name () {
	...函数体...
}

#语法二:
function func_name {
	...函数体...
}

#语法三:
function func_name () {
	...函数体...
}

例:创建disable_firewall_selinux函数以关闭firewalld和selinux

[root@centos8_3 ~]#systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2023-08-12 22:57:44 CST; 6min ago
[root@centos8_3 ~]#getenforce
Enforcing

# 函数主体:
[root@centos8_3 ~]#disable_firewall_selinux () {
> systemctl stop firewalld
> systemctl disable firewalld
> sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
> setenforce 0
> }

[root@centos8_3 ~]#disable_firewall_selinux
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@centos8_3 ~]#systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
   Active: inactive (dead)
[root@centos8_3 ~]#getenforce
Permissive

注意:函数操作后确实修改了文件,因此重启后仍然保存。但是函数本身一次性使用,重启后消失,因此想要保存函数需要写入到文件

例:分装函数与调用于2个文件,可直接调用函数

#funtions文件,保存函数主体
[root@centos8_3 scripts]#cat functions
#函数1:disable_firewall_selinux()
disable_firewall_selinux () {
 systemctl stop firewalld
 systemctl disable firewalld
 sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
 setenforce 0
}

#函数2:yum_repo()
yum_repo() {

        cd /etc/yum.repos.d/
        mkdir backup
        mv *.repo backup
        cat > base.repo <<EOF

[BaseOS]
name=aliyun BaseOS
baseurl = https://mirrors.aliyun.com/centos/8/BaseOS/x86_64/os/
                  https://repo.huaweicloud.com/centos/8-stream/BaseOS/x86_64/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial

[AppStream]
name=aliyun AppStream
baseurl = https://mirrors.aliyun.com/centos/8/AppStream/x86_64/os/
                  https://repo.huaweicloud.com/centos/8-stream/AppStream/x86_64/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial

[epel]
name=Extra Packages for Enterprise Linux \$releasever - \$basearch
# It is much more secure to use the metalink, but if you wish to use a local mirror
# place it's address here.
#baseurl=https://download.example/pub/epel/\$releasever/Everything/\$basearch
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-\$releasever&arch=\$basearch&infra=\$infra&content=\$contentdir
enabled=1
gpgcheck=1
countme=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8

[epel-debuginfo]
name=Extra Packages for Enterprise Linux \$releasever - \$basearch - Debug
# It is much more secure to use the metalink, but if you wish to use a local mirror
# place it's address here.
#baseurl=https://download.example/pub/epel/\$releasever/Everything/\$basearch/debug
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-\$releasever&arch=\$basearch&infra=\$infra&content=\$contentdir
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8
gpgcheck=1

[epel-source]
name=Extra Packages for Enterprise Linux \$releasever - \$basearch - Source
# It is much more secure to use the metalink, but if you wish to use a local mirror
# place it's address here.
#baseurl=https://download.example/pub/epel/\$releasever/Everything/SRPMS
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-source-\$releasever&arch=\$basearch&infra=\$infra&content=\$contentdir
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8
gpgcheck=1
EOF
}

#函数3:install_package()
install_package() {
packages="
vim
tree
autofs
net-tools
apache
httpd
"
for i in $packages;do
        rpm -q $i &> /dev/null || yum -q -y install $i
done
}

#disable_firewall_selinux.sh文件,调用disable_firewall_selinux函数
[root@centos8_3 scripts]#cat disable_firewall_selinux.sh
#!/bin/bash
. functions	#引用functions文件

disable_firewall_selinux

#yum_repo.sh文件,调用yum_repo函数
[root@centos8_3 scripts]#cat yum_repo.sh
#!/bin/bash
. functions

yum_repo

#install_package.sh文件,调用install_package函数
[root@centos8_3 scripts]#cat install_package.sh
#!/bin/bash
. functions

install_package

5.1.2 查看函数

#查看当前已定义的函数名
declare -F

#查看当前已定义的函数定义
declare -f

5.1.3 删除函数

格式:

unset func_name

5.2 函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止。

不过调用函数时,如果函数内外有同名变量,调用函数后会修改函数内外的变量,类似于全局变量,如果要使函数只修改函数内部的变量而不影响函数外的同名变量,可以在函数内声明时加上local,使其成为本地变量,如local NAME=1

5.2.1 交互式环境调用函数

交互式环境下定义和使用函数

5.2.2 在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用其函数名即可

5.2.3 使用函数文件

例:调用系统函数

[root@CentOS8 ~]#. /etc/init.d/functions
[root@CentOS8 ~]#action "you an me"
you an me                                                  [  OK  ]
[root@CentOS8 ~]#action "you an medsadsad" false
you an medsadsad                                           [FAILED]

5.3 函数返回值

默认情况下,我们使用exit 100,会返回100,但用在函数里,会使整个脚本退出而不执行下面的代码。

因此使用return #返回值,可只退出当前函数而不退出脚本,不过只在函数中使用

函数的执行结果返回值

  • 使用echo等命令进行输出
  • 函数体中调用命令的输出结果

函数的退出状态码

  • 默认取决于函数中执行的最后一条命令的退出状态码

  • 自定义退出状态码,其格式为

    • return 从函数中返回,用最后状态命令决定返回值

    • return 0 无错误返回

    • return 1-255 有错误返回

5.4 环境函数

类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数

定义环境函数:

export -f function_name
declare -xf function_name

查看环境函数:

export -f
declare -xf

5.5 函数参数

函数可以接受参数:

  • 传递参数给函数,在函数名后面以空白分隔给定参数列表即可,如: testfunc arg1 arg2…
  • 在函数体中当中,可使用$1, 2 , . . . 调用这些参数,还可以使用 2,...调用这些参数,还可以使用 2...调用这些参数,还可以使用@, ∗ , *, #等特殊变量

5.6 函数变量

变量作用域:

  • 普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
  • 环境变量:当前shell和子shell有效
  • 本地变量:函数的生命周期;函数结束时变量被自动销毁

注意:

  • 如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
  • 由于普通变量和局部变量会冲突,建议在函数中只使用本地变量

在函数中定义本地变量的方法

local NAME=VALUE

5.7 函数递归

函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环

  • 基例:确定值
  • 链条:规律

递归示例:阶乘

阶乘是基斯顿·卡曼于1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!

n!=1x2x3x…xn

阶乘亦可以递归方式定义: 0!=1,n!=(n-1)!xn

n!=n(n-1)(n-2)…1

n(n-1)!=n(n-1)(n-2)!

例:递归实现阶乘

[root@CentOS8 functions]#cat fact.sh
fact() {
        if [ $1 -eq 1  ];then
                echo 1
        else
                echo $[`fact $[$1-1]`*$1]
        fi
}
fact $1

[root@CentOS8 functions]#bash fact.sh 5
120

fork炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

参考:https://en.wikipedia.org/wiki/Fork_bomb

函数实现

:(){ :|:& };:	#无基例
bomb () {} bomb | bomb & }; bomb

脚本实现

cat Bomb.sh
#!/bin/bash
./$0|./$0&

Linux_5_Shell脚本编程_第11张图片

OOM:内存溢出

6 其它脚本相关工具

6.1 信号捕捉 trap

  • trap ‘触发指令’ 信号

    ​ 进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作

  • trap" 信号

    ​ 忽略信号的操作

  • trap ‘-’ 信号

    ​ 恢复原信号的操作

  • trap -p

    ​ 列出自定义信号操作

  • trap finish EXIT

    ​ 当脚本退出时,执行finish函数

例:

[root@CentOS8 functions]#cat signal_trap1.sh
trap 'echo "Press ctrl+c"' int quit	#将int 和quit信号改为echo "Press ctrl+c"
trap -p
for((i=1;i<=10;i++))
do
        sleep 1
        echo $i
done

trap '' int		#将int信号改为空,即忽略
trap -p
for((i=11;i<21;i++ ))
do
        sleep 1
        echo $i
done

trap '-' int	#恢复int信号,接下来遇到int信号将执行ctrl+c
trap -p
for(( i=21;i<31;i++))
do
        sleep 1
        echo $i
done

[root@CentOS8 functions]#bash signal_trap1.sh
trap -- 'echo "Press ctrl+c"' SIGINT
trap -- 'echo "Press ctrl+c"' SIGQUIT
1
^CPress ctrl+c
2
^CPress ctrl+c
3
^CPress ctrl+c
4
^CPress ctrl+c
5
^CPress ctrl+c
6
^CPress ctrl+c
7
^CPress ctrl+c
8
^CPress ctrl+c
9
^CPress ctrl+c
10
trap -- '' SIGINT
trap -- 'echo "Press ctrl+c"' SIGQUIT
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^[[A11
^[[A12
^[[A^[[A^[[A^C13
^C^C^C^C^C^C^C^C^C^C^C14
15
16
17
18
19
20
trap -- 'echo "Press ctrl+c"' SIGQUIT
21
22
23
24
25
26
^C

6.2 创建临时文件 mktemp

mktemp命令用于创建并显示临时文件,可避免冲突

格式

mktemp [OPTION]... [TEMPLATE]

说明:TEMPLATE:filenameXXX,X至少要出现三个

常见选项:

-d	创建临时目录
-p 	DIR或--tmpdir=DIR	指明临时文件所存放目录位置

例:

#默认放在tmp目录下
[root@CentOS8 functions]#mktemp
/tmp/tmp.B9CHlgxwCL
[root@CentOS8 functions]#mktemp
/tmp/tmp.ur2MYKCZhU

[root@CentOS8 ~]#mktemp -d tmpdirXXXX	#在当前目录下生成临时目录
tmpdirqWDe
[root@CentOS8 ~]#ll tmpdirqWDe -d
drwx------ 2 root root 6 Aug 13 14:51 tmpdirqWDe

例:实现rm的进阶写法

[root@CentOS8 scripts]#cat rm2.sh
DIR=`mktemp -d /tmp/trash-$(date +%F_%H-%M-%S)XXXXX`
mv $* $DIR
echo $* is move to $DIR

[root@CentOS8 scripts]#bash  rm2.sh 123 321
123 321 is move to /tmp/trash-2023-08-13_14-59-58BDIIO
[root@CentOS8 scripts]#ls /tmp/trash-2023-08-13_14-59-58BDIIO/
123  321
[root@CentOS8 scripts]#alias rm=/data/scripts/rm2.sh
[root@CentOS8 scripts]#touch 1
[root@CentOS8 scripts]#chmod +x rm2.sh
[root@CentOS8 scripts]#rm 1
1 is move to /tmp/trash-2023-08-13_15-05-24kBeQl

6.3 安装复制文件 install

功能相当于cp、chmod、chown、chgrp等相关工具的集合

install命令格式

install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
insta11 [OPTION]... -d DIRECTORY...创建空目录

选项:

-m MODE,默认755	#改权限
-o OWNER				#改所有者
-g GROUP				#改所属组
-d DIRNAME

例:

[root@CentOS8 ~]#ll cal.txt
-rw-r--r-- 1 root root 0 Jul 28 19:29 cal.txt
[root@CentOS8 ~]#install -m 666 -o joyce -g bin cal.txt /data/test.txt
[root@CentOS8 ~]#ll /data/test.txt
-rw-rw-rw- 1 joyce bin 0 Aug 13 15:12 /data/test.txt

#创建空文件夹
[root@CentOS8 ~]#install -m 700 -o joyce -g daemon -d /data/testdir/
[root@CentOS8 ~]#ll -d /data/testdir/
drwx------ 2 joyce daemon 6 Aug 13 15:13 /data/testdir/

6.4 交互式转化批处理工具 expect

expect 是由 Don Libes 基于 TcI ( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助 expect处理交互的命令,可以将交互过程如:ssh登录,tp登录等写在一个脚本上,使之自动化完成尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率

expect 语法:

expect [选项] [ -c cmds ] [ [ -[fIb] ] cmdfile ] [ args ]

常见选项:

-c	从命令行执行expect脚本,默认expect是交互地执行的
-d	可以输出输出调试信息

示例:

expect -c 'expect "\n" {send "pressed enter\n"}'
expect -d ssh.exp

expect中相关命令

spawn		启动新的进程
expect	从进程接收字符串
send		用于向进程发送字符串
interact	允许用户交互
exp_continue 匹配多个字符串在执行动作后加此命令

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

单一分支模式语法:只生效一次,遇到hi输出后即结束

[root@centos8 test]#expect
expect1.1> expect "hi" {send "You said hi\n"}
hahahixixi
You said hi

例:非交互式发送文件至第二个主机,自动输入yes和密码

#!/usr/bin/expect	#注意,不是bash
spawn scp /etc/fstab 10.0.0.7:/data
expect {
			"yes/no" { send "yes\n",exp_continue }
			"password" { send "110420\n" }
}
expect eof

例:非交互式自动登录至第二个ip主机,自动输入yes和密码

[root@centos8 scripts]#cat expect2
#!/usr/bin/expect
spawn ssh 10.0.0.209
expect {
			"yes/no" { send "yes\n";exp_continue }
			"password" { send "110420\n" }
}	
interact
[root@CentOS8 scripts]#chmod +x expect1.sh
[root@CentOS8 scripts]#./expect1.sh
spawn ssh 10.0.0.209
[email protected]'s password:
hello Joyce!
Nna
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sun Aug 13 15:48:51 2023 from 10.0.0.201
[root@centos8_2 ~]#hostname
centos8_2

例:带有参数的自动登录其他ip主机的脚本,自动输入yes和密码

[root@CentOS8 scripts]#cat ./expect2.sh
#!/usr/bin/expect
set ip 10.0.0.209
set user root
set password 110420
set timeout 10
spawn ssh $user@$ip
expect {
                "yes/no" { send "yes\n";exp_continue }
                "password" {  send "$password\n" }
}
interact	#代表可以进行交互

[root@CentOS8 scripts]#chmod +x expect2.sh
[root@CentOS8 scripts]#./expect2.sh
spawn ssh [email protected]
[email protected]'s password:
hello Joyce!
Nna
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sun Aug 13 15:49:06 2023 from 10.0.0.201

例:使用位置参数实现自动登录其他ip的主机

[root@CentOS8 scripts]#cat ./expect3.sh
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
			"yes/no" { send "yes\n";exp_continue }
			"password" { send "$password\n" }
}
interact
[root@CentOS8 scripts]#chmod +x expect3.sh
[root@centos8 scripts]#./expect3 10.0.0.209 root 110420
spawn ssh roota10.0.0.7
roota10.0.0.209's password:
Last login: wed Apr 29 15:34:14 2020 from 10.0.0.8

例:远程登陆主机并创建账号设置密码,完了退出

#!usr/bin/expect
set	ip [lindex $argv 0]
set	user [lindex $argv 1]
set	password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
			"yes/no" { send "yes\n";exp_continue }
			"password"{ send"$password\n" }
}
#  ]#即登陆后输入内容前面的标识符
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo 110420 |passwd --stdin haha\n" }
send "exit\n"
expect eof

例:在bash脚本里调用expect以远程登录主机并创建账号设置密码

[root@CentOS8 scripts]#cat ./expect5.sh
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 10
spawn ssh $user@$ip
expect {
			"yes/no" { send "yes\n";exp_continue }
			"password"{ send"$password\n" }
}
#  ]#即登陆后输入内容前面的标识符
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo 110420 |passwd --stdin haha\n" }
send "exit\n"
expect eof
EOF

[root@CentOS8 scripts]#bash expect5.sh 10.0.0.209 root 110420

例:批量登录不同主机并创建用户设置密码

#!/bin/bash
NET=10.0.0
user=root
password=110420
for ID in 209 210;do	#循环遍历不同IP的主机
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
			"yes/no" { send "yes\n";exp_continue }
			"password" { send "$password\n" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

例:批量登录不同主机并修改selinux为disabled

#!/bin/bash
NET=10.0.0
user=root
password=110420
for ID in 209 210;do	#循环遍历不同IP的主机
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
			"yes/no" { send "yes\n";exp_continue }
			"password" { send "$password\n" }
}
expect "#" { send "sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/confiig\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

7数组

7.1 数组介绍

变量:存储单个元素的内存空间

数组:存储多个元素的连续的内存空间,相当于多个变量的集合

数组名和索引

  • 索引的编号从0开始,属于数值索引
  • 索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
  • bash的数组支持稀疏格式(索引不连续)

7.2 声明数组

#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME

#关联数组必须先声明,再使用
declare -A ARRAY_NAME

注意:两者不可相互转换

7.3 数组赋值

数组元素的赋值

  1. 一次只赋值一个元素

    ARRAY_NAME[INDEX]=VALUE
    

    例:

    [root@CentOS8 ~]#title[0]=ceo
    [root@CentOS8 ~]#echo ${title}
    ceo
    
  2. 一次赋值全部元素

    ARRAY_NAME=("VAL1" "VAL2" "VAL3"...)
    

    例:

    title=("ceo""coo""cto")
    num=({0..10})
    alpha=({a..g})
    file=( *.sh )
    
  3. 只赋值特定元素

    ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
    
  4. 交互式数组值对赋值

    read -a ARRAY
    
    [root@CentOS8 ~]#read -a number
    1 3 5 7 9
    [root@CentOS8 ~]#echo ${number[*]}
    1 3 5 7 9
    [root@CentOS8 ~]#echo ${number[2]}
    5
    [root@CentOS8 ~]#echo ${number}
    1
    

7.4 显示所有数组

显示所有数组

declare -a

7.5 引用数组

引用数组元素

${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素

引用数组所有元素

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

例:

[root@CentOS8 ~]#NUM=({1..5})
[root@CentOS8 ~]#echo ${NUM[3]}
4
[root@CentOS8 ~]#echo ${NUM[2]}
3
[root@CentOS8 ~]#echo ${NUM[*]}
1 2 3 4 5
[root@CentOS8 ~]#echo ${NUM[@]}
1 2 3 4 5

例:

[root@CentOS8 ~]#read -a number
1 3 5 7 9
[root@CentOS8 ~]#echo ${number[*]}
1 3 5 7 9
[root@CentOS8 ~]#echo ${number[2]}
5
[root@CentOS8 ~]#echo ${number}
1

7.6 删除数组

删除数组中的某元素,会导致稀疏格式

unset ARRAY[INDEX]

例:

[root@centos8 ~]#echo $title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo $ftitle[*]]
ceo cto

删除整个数组

unset ARRAY

例:

[root@centos8 ~]#unset title
[root@centos8 ~]#echo ${title[*]}

7.7 数组数据处理

数组切片

${ARRAY[@]:offset:number}
offset	#要跳过的元素个数
number	#要取出的元素个数

{ARRAY[@]:offset}	#取偏移量之后的所有元素

例:

[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 10

向数组中追加元素:

ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

例:

[root@CentOS8 ~]#num=({0..10})
[root@CentOS8 ~]#echo ${num[2]}
2
[root@CentOS8 ~]#echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10
[root@CentOS8 ~]#num[11]=11
[root@CentOS8 ~]#echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10 11
[root@CentOS8 ~]#echo ${#num[*]}
12
[root@CentOS8 ~]#num[${#num[*]}]=12
[root@CentOS8 ~]#echo ${#num[*]}
13
[root@CentOS8 ~]#echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10 11 12

7.8 关联数组

declare -A ARRAY_NAME
ARRAY_NAME=([idx_namel]='val1' [idx_name2]='val2'...

注意:关联数组必须先声明再调用

例:

[root@CentOS8 ~]#declare -A name
[root@CentOS8 ~]#name[first]=Tom
[root@CentOS8 ~]#name[second]=Jack
[root@CentOS8 ~]#name[third]=Case
[root@CentOS8 ~]#echo ${name[first]}
Tom
[root@CentOS8 ~]#echo ${name[*]}
Case Jack Tom
[root@CentOS8 ~]#echo ${name[third]}
Case

7.9 范例

例:生成包含10个随机数的数组,并显示其中的最大值MAX和最小值MIN

[root@CentOS8 test]#cat random1.sh
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
        nums[$i]=$RANDOM
        [ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]}&& continue
        [ ${nums[$i]} -gt $max ] && max=${nums[$i]}
        [ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo min is $min

[root@CentOS8 test]#bash random1.sh
All numbers are 23203 6083 14719 18022 31925 25880 9039 10908 30266 23092
Max is 31925
min is 6083

8 字符串处理

8.1 字符串切片

基于偏移量取字符串

#返回字符串变量var的长度
${#var}

#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0到${#var}-1 之间(bash4.2后,允许为负值)
${var:offset]

#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}

#取字符串的最右侧几个字符,取字符串的最右侧几个字符,注意: 冒号后必须有一空白字符
${var: -length}

#从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var:offset:-length}

#先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意: -length前空格
${var: -length:-offset}

例:

[root@CentOS8 test]#str=123abc哇哦
[root@CentOS8 test]#echo ${#str}
8
[root@CentOS8 test]#echo ${str:5}
c哇哦
[root@CentOS8 test]#echo ${str:5:2}
c哇
[root@CentOS8 test]#echo ${str: -2}
哇哦
[root@CentOS8 test]#echo ${str:3:-2}
abc
[root@CentOS8 test]#echo ${str: -3:-1}	#取倒数3个和倒数1个之间的2个
c哇

基于模式取子串

从左往右:

#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word,删除字符串开头至第一次出现word字符串 (含) 之间的所有字符
${var#*word}:

#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var##*word}:

例:

[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file#*/}
log/messages
[root@centos8 ~]#echo ${file##*/}
messages

从右往左:

#其中word可以是指定的任意字符,功能,自右而左,查找var变量所存储的字符串中,第一次出现的worc,删除字符串最后一个字符向左至第一次出现word字符串 (含) 之间的所有字符
${var%word*}

#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}

例:

[root@centos8 ~]#file="/var/log/messages"
[root@centos8 ~]#echo ${file%/*}
var/log
[root@centos8 ~]#echo ${file%%/*}
var

8.2 查找替换、

#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}

#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}

#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}

#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}

8.3 查找并删除

#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}

#删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}

#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}

#删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}

8.4 字符大小写转换

#把var中的所有小写字母转换为大写
${var^^}

#把var中的所有大写字母转换为小写
${var,,}

例:

[root@CentOS8 test]#echo $str
123abc哇哦
[root@CentOS8 test]#echo ${str^^}
123ABC哇哦

9 高级变量

9.1 高级变量赋值

变量配置方法 str 没有配置 str 为空字符串 str 已配置非为空字符串
var=${str-expr} var=expr var= var=$str
var=${str:-expr} var=expr var=expr var=$str
var=${str+expr} var= var=expr var=expr
var=${str:+expr} var= var= var=expr
var=${str=expr} str=expr
var=expr
str 不变
var=
str 不变
var=$str
var=${str:=expr} str=expr
var=expr
str=expr
var=expr
str 不变
var=$str
var=${str?expr} expr输出至stderr var= var=sstr
var=${str:?expr} expr输出至stderr expr输出至stderr var=sstr

例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title-oops}
[root@centos8 ~]#echo $name
ceo

[root@centos8 ~]#title=
[root@centos8 ~]#name=${title-oops}
[root@centos8 ~]#echo $name


[root@centos8 ~]#unset title
[rootacentos8 ~]#name=$ftitle-oops}
[root@centos8 ~]#echo $name
oops

9.2 高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的

declare [选项] 变量名
选项:
-r	声明或显示只读变量
-i	将变量定义为整型数
-a	将变量定义为数组
-A	将变量定义为关联数组
-f	显示已定义的所有函数名及其内容
-F	仅显示已定义的所有函数名
-x	声明或显示环境变量和函数,相当于export
-l	声明变量为小写字母 declare -l var=UPPER
-u	声明变量为大写字母 declare -u var=lower

9.3 变量间接引用

9.3.1 eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描,因此后面必须跟命令

例1:

[root@CentOS8 test]#CMD=hostname
[root@CentOS8 test]#$CMD
CentOS8.Joyce.person1
[root@CentOS8 test]#echo $CMD
hostname
[root@CentOS8 test]#eval $CMD
CentOS8.Joyce.person1

例2:

[root@CentOS8 test]#n=10
[root@CentOS8 test]#for i in {1..$n};do echo $i;done
{1..10}
[root@CentOS8 test]#for i in `eval echo {1..$n}`;do echo $i;done
1
2
3
4
5
6
7
8
9
10

9.3.2 间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用

variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获

得变量值value的行为

variablel=variable2
variable2=value

bash Shell提供了两种格式实现间接变量引用

eval tempvar= \$Svariable1
tempvar=${!variable1}

例:

[root@CentOS8 test]#ceo=name
[root@CentOS8 test]#name=Me
[root@CentOS8 test]#echo $ceo
name
[root@CentOS8 test]#echo $$ceo
7487ceo
[root@CentOS8 test]#echo \$$ceo
$name
[root@CentOS8 test]#eval echo \$$ceo
Me
#或:
[root@CentOS8 test]#echo ${!ceo}
Me

9.3.3变量引用reference

[root@centos8 ~]#cat test.sh
#!/bin/bash
ceo=ME
title=ceo
declare -n ref=$title
[ -R ref ] && echo reference
echo $ref
ceo=Joyce
echo $ref

[root@centos8 ~]#bash test.sh
reference
ME
Joyce

你可能感兴趣的:(Linux,linux,运维)