shell脚本基础知识整理

shell脚本

文章目录

shell系统环境

[root@shellenv ~]# cat /etc/redhat-release 
CentOS Linux release 7.9.2009 (Core)

[root@shellenv ~]# uname -rs
Linux 3.10.0-1160.71.1.el7.x86_64

[root@shellenv ~]# echo $SHELL
/bin/bash

[root@shellenv ~]# bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.

1 shell脚本语言的基本结构

1.1 shell脚本用途

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

1.2 shell脚本基本结构

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

编程语言结构:

  • 系统命令的组合

  • 数据存储: 变量 / 数组

  • 表达式 a + b

  • 控制语句 if

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

格式要求:首行sheBang机制

#! /bin/bash			##申明使用的shell
#! /usr/bin/python
#! /usr/bin/perl

1.3 创建shell脚本过程

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

第一行必须使用shell申明

#! SHELL

# 注释

第二步: 添加执行权限 chmod +x TARGET.sh

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

第三步: 脚本运行和debug

直接运行解释器,将脚本作为解释器程序的参数执行

1.4 脚本注释规范

1、第一行一般为调用使用的程序语言

2、程序名

3、版本

4、更改时间

5、作者相关信息

可通过 [ .vimrc]进行预定义及相关操作

文件存放至用户家目录即可

1.4.1 vimrc示例
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab
set ignorecase
set cursorline
set autoindent
autocmd BufNewFile *.sh exec ":call SetTitle()"
func SetTitle()
	if expand("%:e") == 'sh'
	call setline(1,"#!/bin/bash")
	call setline(2,"#")
	call setline(3,"#**********************************************************************")
	call setline(4,"#Auther:			Benjemin"                  )
	call setline(5,"#Date:				".strftime("%Y-%m-%d")     )
	call setline(6,"#Filename:			".expand("%")                     )            
	call setline(7,"#Description:		Test script for learning"                      )	
	call setline(8,"#Copyright (C) 		".strftime("%Y")." All rights reserved"   ) 
	call setline(9,"#**********************************************************************")
	call setline(10,""                                                             )   
	endif
endfunc
autocmd BufNewFile * normal G

1.5 脚本案例

输出 hello world

#!/bin/bash
#
#**********************************************************************
#Auther:			Benjemin
#Date:				2023-12-19
#Filename:			print.sh
#Description:		Test script for learning
#Copyright (C) 		2023 All rights reserved
#**********************************************************************

echo hello world

1.6 脚本调试

1.6.1 脚本的执行方式

1、添加文件执行权限直接执行

[root@shellenv shellscript]# chmod +x get_sysinfo.sh

[root@shellenv shellscript]# ll get_sysinfo.sh
-rwxr-xr-x. 1 root root 830 Jul  2 13:15 get_sysinfo.sh

[root@shellenv shellscript]# get_sysinfo.sh
-bash: get_sysinfo.sh: command not found
## 此时提示命令无法找到  需要配置path变量提供命令的执行路径
解决方式 /etc/profile.d/env.sh
[root@shellenv ~]# cat /etc/profile.d/env.sh
PATH='/data/shellscript':$PATH

export PATH;

2、添加文件执行权限后本地目录下执行

[root@shellenv shellscript]# chmod +x get_sysinfo.sh
[root@shellenv shellscript]# ./get_sysinfo.sh

3、 bash程序执行

[root@shellenv shellscript]# bash print.sh
#	该执行方式不需要授予文件执行权限

[root@shellenv shellscript]# cat print.sh | bash
#	以管道形式执行

[root@shellenv shellscript]# curl -s 'TARGET_SOURCE/*.sh' | bash
#	调用远程设备的sh脚本执行
1.6.2 debug

脚本错误常见的有三种:

  • 语法错误,会导致后续的命令不继续执行,可以用bash -n 检查错误,提示的出错行数不一定是准
    确的

  • 命令错误,默认后续的命令还会继续执行,用bash -n 无法检查出来 ,可以使用 bash -x 进行观察

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

查看shell脚本文件中的特殊格式符

1 [root@shellenv shellscript]# cat -A get_sysinfo.sh

2 vim命令模式下 :set list		# 见vim编辑文档中 单一进程使用的特性设置

调试而不执行bash -n

[root@shellenv shellscript]# bash -n get_sysinfo.sh

调试并执行 bash -x

[root@shellenv shellscript]# bash -x get_sysinfo.sh

1.7 变量

1.7.1 变量

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

# 变量引用   ${name} 或者 $name
[root@shellenv shellscript]# name=benjemin
[root@shellenv shellscript]# echo $name;
benjemin
[root@shellenv shellscript]# echo ${name};
benjemin
1.7.2 变量类型

变量类型:

  • 内置变量

    # man 1 bash 搜索/Variables 
    PATH
    SHELL
    USER
    UID
    PID
    BASHPID / $$ 
    $?
    HOME
    PWD
    SHLVL 		#代表SHELL的嵌套深度
    LANG		#语言(系)
    MAIL
    HOSTNAME
    HISTSIZE
    _				# '_'下划线;代表上一次命令执行的参数
    
  • 用户自定义变量

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

  1. 数据存储方式

  2. 参与的运算

  3. 表示的数据范围

变量的数据类型:

  • 字符

  • 数值: 整形(int) / 浮点型 [bash不支持]

1.7.3 编程语言分类

静态与动态语言

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

  • 动态编译语言:不用事先声明,可随时改变类型,如:bash,Python

强类型与弱类型语言

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

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

1.7.4 shell中变量命名规范
1.7.4.1 命名要求
  • 区分大小写
  • 不能使程序中的保留字和内置变量:如:if, for
  • 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”,和主机名相反
1.7.4.2 命名习惯
  • 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
  • 变量名大写
  • 局部变量小写
  • 函数名小写
  • 大驼峰StudentFirstName
  • 小驼峰studentFirstName
  • 下划线: student_name
1.7.5 变量的定义和引用

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

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

变量赋值:

一般变量的赋值临时生效,当终端退出时,变量会自动删除,无法持久保存。

在脚本编写执行的过程中,建议脚本的最后释放变量unset <VARIABLE_NAME>,避免冲突

name='value'

## VALUE可以是以下多种形式
直接字串:name='root'
变量引用:name="$USER"
命令引用:name=`COMMAND` 或者 name=$(COMMAND)

变量引用:

“$NAME” 弱引用,其中的变量引用会被替换成定义的变量值

‘$NAME’ 强引用,其中的变量引用不会被替换为变量值,而是保持原先的字符串

[root@shellenv ~]# ROLE=ceo
[root@shellenv ~]# echo "$ROLE"
ceo
[root@shellenv ~]# echo '$ROLE'
$ROLE

范例:变量的引用

需要注意变量定义和引用时输出结果的变化

[root@shellenv ~]# seq 10
2
3
4
5
6
7
8
9
10
[root@shellenv ~]# NUM=`seq 10`
[root@shellenv ~]# echo $NUM		#直接引用
1 2 3 4 5 6 7 8 9 10
[root@shellenv ~]# echo "$NUM"		#弱引用
1
2
3
4
5
6
7
8
9
10

[root@shellenv ~]#NAMES="wang
> zhang
> zhao
> li"
[root@shellenv ~]#echo $NAMES
wang zhang zhao li
[root@shellenv ~]#echo "$NAMES"
wang
zhang
zhao
li


显式定义的所有变量:set

set
# set显示内容过多 建议分页显示 set | less

删除变量: unset

unset <NAME>	#可追加多个变量
1.7.5.1 一些说明

有些命令的执行会占用会话的标准输出(例如sleep 100),当在窗口下执行 /bin/bash时不会有任何反馈的

可以通过pstree -p命令查看 可以查看对应的父子进程(含pid)

父子进程之间的普通变量是不能继承的,子进程不能继承父进程的变量

通过变量$BASHPID或者$$可以看到当前会话所处的bash

通过变量$PPID查看上级父进程的进程编号

(COMMAND EXECUTION ENVIRONMNET)

()内的针对变量赋值和内部命令的操作,不会对环境保留影响,即单次临时性的命令操作 ——可以理解为开启了一个子进程

{COMMAND EXECTUTION ENVIRONMENT}

{}内针对变量和命令的操作,会产生影响–可以理解为在同一个子进程执行的操作

要使得变量在父子进程中能够继承,需要修改变量类型为环境变量

# 父程序
[root@shellenv shellscript]# cat parent.sh
#!/bin/bash

NAME=father
echo "MY NAME IS FATHER"
echo "current PID is $BASHPID"
echo "Variable NAME IS $NAME"
/data/shellscript/son.sh

# 子程序
[root@shellenv shellscript]# cat son.sh
#!/bin/bash

echo "MY NAME IS SON"
echo "Current PID is $BASHPID"
echo "Current PPID is $PPID"
echo "Variable NAME IS $NAME"

执行 parent.sh
[root@shellenv shellscript]# bash parent.sh
MY NAME IS FATHER
current PID is 6331
MY NAME IS father
MY NAME IS SON
Current PID is 6332
Current PPID is 6331
Variable NAME IS				#子进程中未定义NAME变量
1.7.6 环境变量

环境变量:

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

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

  • 环境变量一般应用于系统配置环境,在shell脚本中以使用普通变量居多。

变量的声明与赋值:

export name=VALUE
declare -x name=VALUE

范例:

# 父程序
[root@shellenv shellscript]# cat parent.sh
#!/bin/bash

NAME=father
export NAME
echo "MY NAME IS FATHER"
echo "current PID is $BASHPID"
echo "Variable NAME IS $NAME"
/data/shellscript/son.sh

# 子程序保持不变

[root@shellenv shellscript]# bash parent.sh
MY NAME IS FATHER
current PID is 6343
MY NAME IS father
MY NAME IS SON
Current PID is 6344
Current PPID is 6343
Variable NAME IS father
# 可以看到export声明后NAME变量被子程序继承

如果子程序自身定义了同名的变量NAME=SON;程序输出时结果为son

显示所有的环境变量

env / printenv / export / declare -x

bash内建的环境变量

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

查看进程中的环境变量

[root@shellenv 972]# cat /proc/PID/environ
1.7.7 只读变量

只能声明定义,后续不能修改和删除

声明只读变量:

readonly NAME
declare -r name

查看只读变量

readonly [-p]
declare -r
1.7.8 位置变量

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

$1 $2	对应第1,第2等参数,shift[n]换位置
$0		命令本身
$*		传递给脚本的所有参数,全部参数合为一个字符串
$@		传递给脚本的所有参数,每个参数作为独立的字符串
$#		传递给脚本的参数的个数

$@ $*只有在被双引号引用时才会有差异 

范例

[root@shellenv shellscript]# cat arg_deliver.sh 
#!/bin/bash
echo "1st Arg is $1"
echo "2nd is $2"
echo "3rd is $3"
echo "Arg_for_all is $*"
echo "Arg for_each is $@"
echo "Numbers of Arg is $#"
echo "the scriptname is $0"
***************************************************************************************
[root@shellenv shellscript]# arg_deliver.sh arg1 arg2 arg3 arg4
1st Arg is arg1
2nd is arg2
3rd is arg3
Arg_for_all is arg1 arg2 arg3 arg4
Arg for_each is arg1 arg2 arg3 arg4
Numbers of Arg is 4
the scriptname is /data/shellscript/arg_deliver.sh
***************************************************************************************
[root@shellenv shellscript]# arg_deliver.sh "arg1 arg2 arg3 arg4"
1st Arg is arg1 arg2 arg3 arg4
2nd is 
3rd is 
Arg_for_all is arg1 arg2 arg3 arg4
Arg for_each is arg1 arg2 arg3 arg4
Numbers of Arg is 1
the scriptname is /data/shellscript/arg_deliver.sh
***************************************************************************************

清空所有位置变量

set --	
1.7.9 退出状态码

进程执行后,使用变量“$?”保存最近的命令退出状态

echo $?			
#	0代表成功 1-255代表失败

用户可以自定义退出状态码 exit [NUMBER]

exit 100;
## 脚本中一旦遇到exit命令,脚本就会立即停止;终止退出状态取决于exit命令后n的数字

## 如果脚本中没有指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
1.7.10 命令的执行展开与拓展
1.7.10.1 展开命令的执行顺序[优先级]
1.把命令行分成单词
2.展开别名
3.展开大括号的申明    : {}
4.展开波浪符申明      : ~
5.命令替换$()和``
6.再次把命令行分成命令词
7.展开文件通配符(*,?,[abc])
8.准备I/O重定向
9.执行	
1.7.10.2 防止拓展
'\ 进行转义'
	echo \$5

'加引号防止扩展'
	单引号('') 防止所有扩展
	双引号("") 可以防止扩展,但是存在特殊情况
{	
	$	变量扩展
	`` 	命令替换
	\	禁止单个字符扩展
	!	历史命令替换
}
1.7.11 脚本安全和set

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

$-变量

[root@shellenv shellscript]# echo $-
himBH
***************************************************************************************

h: hashall 打开选项后,shell会将命令所在的路径hash下来,避免每次都要查询。
## 通过 set  +h将h选项关闭
i: interactive-comments 具备该选项说明当前shell是一个交互式的shell。
## 在脚本中,i选项是关闭的
m: monitor 打开监控模式,可以通过Job control控制进程
B: braceexpand 大括号拓展
H: history
##打开时可以展开历史列表中的命令,通过!来完成 '!!'返回最近的一个历史命令, '!n'返回第n个命令

set命令实现脚本安全

编写脚本时建议首行set -ue

-u 在拓展一个没有设置的变量时,显示错误信息,等同于 set -o nounset

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

-o option显示 打开或者关闭选项
​ 显示选项 set -o
​ 打开选项 set -o 选项参数
​ 关闭选项 set +o 选项参数

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

-n 在拓展一个没有设置的变量时,显示错误信息
​ 等同于 set -o nounset

范例

[root@shellenv shellscript]# set -o
allexport      	off
braceexpand    	on
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@shellenv shellscript]# set -o errexit 
[root@shellenv shellscript]# ll a
ls: cannot access a: No such file or directory

Connection closed.
Disconnected from remote host(ShellEnv-192.168.10.101-root) at 13:58:24.

set -o errexit  # 命令执行失败,直接中断后续的执行

1.8 格式化输出 printf

格式

printf "FORMAT"  "item1" "item2"

常用格式替换符

替换符功能
%s字符串
%f浮点格式
%b相对应的参数中包含转义字符时,可用此替换符进行替换,对应的转义字符会被转义
%cASCII字符,显示对应参数的第一个字符
%d,%i十进制整数
%o八进制数
%u不带符号的十进制值
%x十六进制(a-f)
%X十六进制(A-F)
%%表示%自身

%s中数字代表替换夫中的输出字符宽度,不足补空格,默认为右对齐,%-10s表示10个字符宽度,输出为左对齐格式。

常用的转义字符

转义符功能
\a报警
\b后退
\f换页
\n换行
\r回车
\t水平制表符
\v垂直制表符
\表示\本身

范例

[root@shellenv ~]# printf "%s\n" 1 2 3 4
1
2
3
4

[root@shellenv ~]# printf "%-10s\t%-10s\n" TableName Function
TableName 	Function  

# 制表格式
[root@shellenv ~]# printf "|%-10s\t|%-10s|\n" TableName Column;printf "|%-10s\t|%-10s|\n" Convert Function
|TableName 	|Column    |
|Convert   	|Function  |

#.2f 保留两位小数输出
[root@shellenv ~]# printf "%.2f\n" 1 2 3 4
1.00
2.00
3.00
4.00

# 颜色填充以及变量输出
[root@shellenv ~]# VAR=HOSTNAME;printf "\033[31m%s\033[0m\n" $VAR
HOSTNAME		字符输出为红色

1.9 算术运算

shell支持算术运算,只支持整数型,不支持浮点数

bash中的算数运算

+ 
- 
* 
/ 			
%	取模,求余数,M/N	输出的值的范围为(0,N-1)
**(乘方)

乘法符号有的时候需要进行转义

算术运算的实现

*	let var= 算数表达式
* 	var=$[算术表达式]
*	var=$((算术表达式))
*	var=$(expr arg1 arg2 arg3..)
* 	declare -i var = 数值
		-i integer[整数数据]
* 	echo '算术表达式' |bc

随机数的生成$RANDOM

$RANDOM #取值范围(0-32767)	bash中的内建变量

生成0-49之间的随机数
echo $[RANDOM%50]  %50代表取模 

假使需要随机生成一个31-37的数值
echo $[RANDOM%7+31]
1.9.1 赋值

增强型赋值

+=
-=
*=
/=
%=
***************************************************************************************
## let命令
[root@shellenv ~]# id=0
[root@shellenv ~]# echo $id
0
[root@shellenv ~]# let ++id
[root@shellenv ~]# echo $id
1
[root@shellenv ~]# let id++	
[root@shellenv ~]# echo $id
2
## ++id  优先执行++ 再取id值
## id++  先取id值,再进行++
***************************************************************************************
[root@shellenv ~]# id=0
[root@shellenv ~]# echo $id
0
[root@shellenv ~]# let x=id++
[root@shellenv ~]# echo $id
1
[root@shellenv ~]# echo $x
0
***************************************************************************************
自增,自减
let var+=1		#	等同于var=var+1
let var++
let var-=1
let var--

1.10 逻辑运算

true 1 / false 0

[root@shellenv ~]# true 
[root@shellenv ~]# echo $?
0
[root@shellenv ~]# false
[root@shellenv ~]# echo $?
1

与运算 &

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

或运算 |

1 | 0 = 1 
1 | 1 = 1
0 | 1 = 1
0 | 0 = 0

非运算!

! 1 = 0		false
! 0 = 1		true

[root@shellenv ~]# ! false
[root@shellenv ~]# echo $?
0

异或运算 ^

两个值相同为 假; 不同为 真

范例

# 通过异或的特性,可以实现变量的值的互换

[root@shellenv ~]# x=10
[root@shellenv ~]# y=12
[root@shellenv ~]# echo "x=$x y=$y"
x=10 y=12
[root@shellenv ~]# x=$[x^y];y=$[x^y];x=$[x^y];echo "x=$x y=$y"
x=12 y=10
1.10.1 短路运算
  • 短路与 &&

​ CMD1 短路与 CMD2

如果第一个命令CMD1的结果为0 [false];则总的结果必然为0,因此不需要执行CMD2

如果第一个命令CMD1的结果为1 [true]; 则继续执行CMD2,输出的结果以CMD2的结果为准
  • 短路或 ||

​ CMD1 短路或 CMD2

如果第一个命令CMD1的结果为0 [false];则继续执行CMD2,输出的结果以CMD2的结果为准

如果第一个命令CMD1的结果为1 [true]; 则总的结果必然为1,因此不需要执行CMD2

1.11 条件测试命令

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

结果为真,状态码$?返回0

结果为假,状态码$?返回1

可以通过man test或者man bash下搜索CONDITIONAL EXPRESSIONS 查看所有测试表达式

条件测试的命令

  • test 表达式

  • [ 表达式 ]

  • [[ 表达式 ]]

表达式前后存在空格

# 上述测试命令中: [[ 表达式 ]] 

表达式可以支持正则形式的写法
********************************************************************
[root@shellenv ~]# str=gooogle;[[ $str =~ o{2,} ]] && echo "Variable str has more than 2‘o’ in strings" || echo "something wrong happens";
Variable str has more than 2‘o’ in strings

1.11.1 变量测试
  • -v VAR 判断变量是否定义

范例

[root@shellenv ~]# echo $var

[root@shellenv ~]# [ -v VAR ] && echo "Variable VAR exist" || echo "Variable Var is null";
Variable Var is null

-R VAR 判断变量是否定义并且是名称引用 bash4.4新特性

[root@shellenv ~]# test -R name						#bash4.2	不支持
-bash: test: -R: unary operator expected
1.11.2 数值测试
-gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne	是否不等于
-lt	是否小于
-le	是否小于等于

范例

[root@shellenv ~]# ten=10;nine=9;[ $ten -gt $nine ] && echo "$ten is greater than $nine";
10 is greater than 9

[root@shellenv ~]# ten=10;nine=9;[ $ten -ne $nine ] && echo "$ten is not equal to $nine";
10 is not equal to 9
1.11.3 字符串的测试
-z "string" 字符串是否为空
-n "string"	字符串是否为非空
'用于字符串比较时的用到的操作数都应该加双引号'

=	字符串相等,返回为真
> 	比较ascii码是否大于ascii码
< 	比较ascii码是否小于
!=	是否不等于

==	左侧字符串是否和右侧的PATTERN相同
	##  应用在[[ ]]中,PATTERN为支持glob(通配符)的表达式
=~	左侧字符串是否能够被右侧的PATTERN所匹配
	##	应用在[[ ]]中;PATTERN为支持拓展的正则表达式

范例

[root@shellenv ~]# name=
[root@shellenv ~]# [ -z $name ] && echo "string is  empty" || echo "string is not empty";
string is  empty
[root@shellenv ~]# name=benjemin
[root@shellenv ~]# [ -z $name ] && echo "string is  empty" || echo "string is not empty";
string is not empty


[root@shellenv ~]# [ 'A' > 'a' ] && echo "string 'A' is larger than string 'a' in ascii" || echo "string 'A' is smaller than string 'a' in ascii"
string 'A' is larger than string 'a' in ascii


[root@shellenv ~]# [[ 'f.txt' == *.txt ]] && echo "target is a txt file " || echo "something wrong";
target is a txt file 

[root@shellenv ~]# [[ 'gooooglee' =~ o{2,} ]] && echo "string has over 2 'o'" || echo "something wrong"
string has over 2 'o'
* 判断是否为合法ip
##ip地址的表示方式
##分段表示 
0-9			[0-9] 
10-99 		[1-9]{2}
100-199 	1[0-9]{3}
200-249		2[0-4][0-9]
250-254		25[0-4]
## [0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4]
 [1-9]?[0-9] 以此来表示0-99内的数值
## 以上即表示了0-254的数值,但是仅仅表示了一段IP
## IP地址是由4段组成的
## 以下使用拓展的正则表达式
[root@shellenv ~]#ifconfig ens | grep -Eo " (([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])"
 192.168.10.100
 255.255.255.0
 192.168.10.255
 ## 进一步优化
 [root@shellenv ~]#ifconfig ens33 | grep -Eo " (([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])"
 
##利用ip的表示方式判断合法性
[root@shellenv ~]#ip=255.255.255.254; [[ "$ip" =~ ^(([0-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$ ]] && echo  legal ip || echo illegal ip
legal ip
1.11.4 文件的测试
1.11.4.1 文件存在性测试
-a FILE: 同-e
-e FILE: 文件存在性测试,存在为真,否则为假

范例

[root@shellenv ~]# touch a.txt

[root@shellenv ~]# ll
total 12
-rw-------. 1 root root 1791 Jan 15 15:46 anaconda-ks.cfg
-rw-------. 1 root root 1791 Jan 15 15:46 anaconda-ks.cfg.bak
-rw-r--r--. 1 root root    0 Jul  9 18:14 a.txt
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Desktop
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Documents
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Downloads
-rw-r--r--. 1 root root 1839 Jan 15 15:54 initial-setup-ks.cfg
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Music
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Pictures
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Public
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Templates
drwxr-xr-x. 2 root root    6 Jan 15 18:05 Videos

[root@shellenv ~]# [ -a a.txt ] && echo 'target file exists'
target file exists

[root@shellenv ~]# [ -a b.txt ] && echo 'target file exists' || echo "can not find target file"
can not find target file
1.11.4.2 文件存在性及文件类别测试
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h/L FILE:是否存在且为链接文件
-p FILE:是否存在且为管道文件
-s FILE:是否存在且为套接字文件
1.11.4.3 文件权限测试
文件权限
-r FILE	是否存在且可读
-w FILE	是否存在且可写
-x FILE	是否存在且可执行
特殊权限
-u FILE	是否存在且拥有sid权限
-g FILE	是否存在且拥有sgid权限
-s FILE	是否存在且拥有sticky权限
1.11.4.4 文件属性测试
文件大小测试;
-s FILE:是否存在且非空

文件是否打开:
-t fd:fd	文件描述符是否在某终端已经打开
-N FILE		文件自从上一次被读取后是否被修改
-O FILE		当前有效用户是否为文件属主
-G FILE 	当前有效用户是否为文件属组

双目测试
FILE1 -ef FILE2	// FILE1是否为FILE2的硬链接
FILE1 -nt FILE2 // FILE1是否新于FILE2(mtime)		##newer
FILE1 -ot FILE2	// FILE1是否旧于FILE2				##older
1.11.4.5 组合测试条件

方式1

# 单[] 
EXPRESSION1 -a EXPRESSION2	并且
EXPRESSION1 -o EXPRESSION2	或者
!EXPRESSION

## 必须使用测试命令,不支持 " [[ ]] "的方式

方式2

COMMAND1 && COMMAND2	并且,短路与
COMMAND1 || COMMAND2	或者,短路或
!COMMAND	非

[root@shellenv ~]#file=print.sh;[ -f "$file" ] && [[ "$file" =~ .*\.sh$ ]] && echo $file is a sh fileprint.sh is a sh file

1.12 关于()和{}

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

{ list; }不会开启子shell,在当前shell中运行,会影响当前shell环境

详细帮助查看 man bash 搜索(list) / { list; }

(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell’s environment do not remain in effect after the command completes. The return status is the exit status of list.

{ list; }
​ list is simply executed in the current shell environment. list must be terminated with a newline or semicolon. This is known as a group command. The return status is the exit status of list. Note that unlike the metacharacters ( and ), { and } are reserved words and must occur where a reserved word is permitted to be recognized. Since they do not cause a word break, they must be separated from list by whitespace or another shell metacharacter.

范例

[root@shellenv ~]# echo $BASHPID;(name=benjemin;echo "$name";echo $BASHPID;echo $PPID;sleep 10)
2081
benjemin
2520
2077

另起一个终端查看进程状态pstree -p 
─sshd(2077)───bash(2081)─┬─bash(2471)───sleep(2472)
                         └─bash(2549)───sleep(2550)

***************************************************************************************
[root@shellenv ~]# echo $BASHPID;{ name=benjemin;echo "$name";echo $BASHPID;echo $PPID;sleep 10; }

另起一个终端查看进程状态pstree -p 
——sshd(2077)───bash(2081)─┬─bash(2471)───sleep(2472)
                          └─sleep(2563)

[root@shellenv ~]# echo $name				#定义的变量在当前会话生效
benjemin				

1.13 组合测试命令

  • 方式1
## 单中括号
[ EXPRESSION1 -a EXPRESSION2 ]	并且	#两个表达式均为真,输出结果为真
[ EXPRESSION1 -o EXPRESSION2 ]	或者	#两个表达式有一个为真,输出结果为真
[ !EXPRESSION ] 				取反

#	-a 和 -o 必须使用测试命令, [[ ]]不支持
  • 方式2
COMMAND1 && COMMAND2	并且,短路与
COMMAND1 || COMMAND2	或者,短路或
!COMMAND	非

1.14 控制键盘输入 read

read为bash内置命令 通过help read可以查看相关手册

使用read命令接受标准输入(STDIN),将输入值分配给一个或多个shell变量;如果变量不存在,就会产生一个REPLY变量

[root@shellenv ~]# read 
benjemin
[root@shellenv ~]# echo $REPLY
benjemin

##变量之间以空格符分割
[root@shellenv ~]# read a b c <<< "x y z"
[root@shellenv ~]# echo $a $b $c
x y z

命令格式 read [选项] / [ 输入内容 ]

常用选项

-p	指定要显示的提示
-s 静默输入;一般用于密码
-n N 指定输入的字符长度(达到目标长度后退出)
-d 'char' 指定输入结束符(第一个字符)
-t N 设置TIMEOUT时间为N秒

范例

[root@shellenv ~]# read -N 5
adsss[root@shellenv ~]# echo $REPLY
adsss

[root@shellenv ~]# read -p "Please input your args:"
Please input your args:a
[root@shellenv ~]# echo $REPLY
a
1.14.1 特殊说明
#	希望通过利用管道符进行参数的传递
[root@shellenv ~]# echo a b c | read x y z
[root@shellenv ~]# echo $x $y $z

#	此时发现并没有任何反应


#	管道符确实进行了值的传递,但是管道符后的命令是在子shell中运行的,read命令执行完成后,就会退出子shell;要使得保持子shell,利用' { } '
[root@shellenv ~]# echo a b c | { read x y z; echo $x $y $z; }
a b c

2 bash的配置文件

2.1 依据生效范围划分类别

全局范围:

/etc/profile

/etc/profile.d/*.sh

/etc/bashrc

用户范围

~/.bash_profile

~/.bashrc

2.2 shell登录方式的分类

2.2.1 交互式

通过终端输入账号密码登录

使用su - USERNAME 切换的用户 [完全切换]

配置文件的执行次序(从上至下)

  • /etc/profile

  • /etc/profile.d/*.sh

  • ~/.bash_profile

  • ~/.bashrc

  • /etc/bashrc

配置文件的生效 建议使用 source 或者 .来执行,可以使得直接在当前进程内生效

bash *.sh 执行脚本时会创建subshell,不会对当前环境产生影响

在配置文件时的生效次序

  • /etc/profile.d/*.sh
  • /etc/bashrc
  • /etc/profile
  • /etc/bashrc – 执行了两次
  • ~/.bashrc
  • ~/.bash_profile

注意: 文件之间存在调用关系,相同配置写入同一文件的不同位置,可能会影响文件的执行顺序

2.2.2 非交互式登录

使用su USERNAME

图形界面下的终端

执行脚本

其他的bash实例

执行顺序

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

2.3依据功能分类

2.3.1 profile类

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

全局:
/etc/profile
/etc/proifile.d/*.sh

用户:
~/.bash_profile

功能

  • 用于定义环境变量

  • 运行命令或脚本

2.3.2 bashrc类

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

全局:
/etc/bashrc

用户
~/.bashrc

2.4 配置文件的生效

修改后执行生效的方式:

  • 重启shell进程

  • source. 指定修改的配置文件

[root@shellenv ~]# source /etc/profile.d/env.sh 

2.5 logout任务

保存在~/.bash_logout文件中,在用户登出shell时执行

实现的功能:

  • 创建自动备份

  • 清理临时文件

3 流程处理

3.1 条件选择

3.1.1 if语句

格式

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

单分支结构

if 判断条件; then
	条件为'真'的执行代码
fi

双分支结构

if 判断条件; then
	条件为'真'的执行代码
else
	条件为'假'的执行代码
fi

多分支结构

if	条件1; then
	执行代码1
elif 条件2; then
	执行代码2
...

else 条件N;then
	执行代码N
fi

范例 bmi.sh

[root@shellenv shellscript]# cat bmi.sh
#!/bin/bash
#
#**********************************************************************
#Auther:                        Benjemin.Kang
#Date:                          2024-07-12
#Filename:                      bmi.sh
#Description:           Test script for learning
#Copyright (C)          2024 All rights reserved
#**********************************************************************
read -p "请输入您的身高(m): " HEIGHT
if [[ ! $HEIGHT =~ ^[0-2].?([0-9]){,2}$ ]];then
    echo "非法的输入";exit 1;
fi

read -p "请输入您的体重(kg): " WEIGHT
if [[ ! $WEIGHT =~ ^[0-9]{1,3}$ ]];then
    echo "非法的输入";exit 1;
fi

BMI=`echo ${WEIGHT}/${HEIGHT}^2 | bc`

if [ "$BMI" -le 18 ]; then
    echo "You should gain weight";
elif [ "$BMI" -le 24 ];then
    echo "You are healthy!"
else
    echo "You need more exercise"
fi

unset BMI
3.1.2 case语句

格式

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

写法:

case 变量引用[$var] in
PAT1)
	分支1
	;;
PAT2
	分支2
	;;
*)
	默认分支
	;;
esac

case的书写风格

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

范例

[root@shellenv shellscript]# cat yesno_case.sh
#!/bin/bash
#
#**********************************************************************
#Auther:                        Benjemin.Kang
#Date:                          2024-07-12
#Filename:                      yesno_case.sh
#Description:           Test script for learning
#Copyright (C)          2024 All rights reserved
#**********************************************************************
read -p "Please comfirm yes or no:" INPUT

case $INPUT in
[Yy]|[Yy][Ee][Ss])
    echo "you agree"
    ;;
[Nn]|[Nn][Oo])
    echo "you disagree"
    ;;
*)
    echo "wrong input"
esac

unset $INPUT

3.2 循环

3.2.0 死循环

格式1:while

while : ; do COMMANDS ; DONE

格式2:for

for ((;;));do COMMAND; done

格式3:until

until false;do COMMANDS;done
3.2.1 for

格式

for NAME [in WORDS ... ] ; do COMMANDS; done
3.2.1.1 写法格式1:
for 变量名 in 列表;do
	循环体;
done

执行机制:

依次将列表中的元素赋给"变量名";每次赋值后执行以此循环体;直到循环结束。

for 循环列表生成方式:

  • 直接给出列表

  • 整数列表

{M..N}

$(seq [start [step]] end)
  • 返回列表的命令:
$(COMMAND)
  • 使用通配符glob: *

  • 变量引用: $@,$*,$#

范例: 求 sum{1…100}

[root@shellenv shellscript]# seq -s+ 100 | bc
5050

[root@shellenv shellscript]# sum=0;for i in {1..100};do let sum=sum+i;done; echo $sum;
5050

9*9乘法表

[root@shellenv ~]# cat arithmatic99.sh 
#!/bin/bash

for i in {1..9};do
    for j in $(seq 1 $i);do
        echo -ne "${j}x${i}=`echo "$i*$j" | bc `\t";
    done
    echo
done


***************************************************************************************
[root@shellenv ~]# ./arithmatic99.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	

3.2.1.2 并行脚本的写法

格式:{ COMMAND }&

将需要执行的命令,放入花括号” { } “中,使用 &符号

范例

# 网络扫描
[root@shellenv shellscript]# cat scan_ip.sh
#!/bin/bash
#
#**********************************************************************
#Auther:                        Benjemin.Kang
#Date:                          2024-07-12
#Filename:                      scan_ip.sh
#Description:           Test script for learning
#Copyright (C)          2024 All rights reserved
#**********************************************************************
NET=192.168.10

for i in {1..254};do
    {
        ping -c1 -W1 ${NET}.$i &> /dev/null && echo "${NET}.$i is alive" || echo $NET.$i can not connect

    }&
done

unset NET

'wait'

使用{}&可以实现并行快速执行,但是 程序不会主动的退出,需要输入才能退出进程状态

可以在脚本的最后输入一个’wait’;从而返回到终端窗口

3.2.1.3 写法格式2:

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

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done

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

范例 9*9

[root@shellenv shellscript]# cat 99_2.sh
#!/bin/bash
#
#**********************************************************************
#Auther:                        Benjemin.Kang
#Date:                          2024-07-13
#Filename:                      99_2.sh
#Description:           Test script for learning
#Copyright (C)          2024 All rights reserved
#**********************************************************************
for((i=1;i<10;i++));do
    for((j=1;j<=$i;j++));do
        echo -e "${j}x${i}=`echo $j*$i|bc`\t\c"
    done
    echo
done

求和1…100

[root@shellenv shellscript]# cat for_sum_2.sh
#!/bin/bash
#
#**********************************************************************
#Auther:                        Benjemin.Kang
#Date:                          2024-07-13
#Filename:                      for_sum_2.sh
#Description:           Test script for learning
#Copyright (C)          2024 All rights reserved
#**********************************************************************
for ((sum=0,i=1;i<=100;i++));do
    let sum=$sum+$i
done

echo sum = $sum
unset sum
3.2.2 while

格式:

while CONDITIONS; do COMMANDS; done

说明:

CONDITIONS:循环控制条件;进入循环之前,先做一次判断;每次循环以后会再次做判断;条件为true,则执行一次循环;知道条件测试状态为false。因此: CONDITION一般存在循环控制变量;变量的值会在循环体中不断被修正

进入循环条件: CONDITION为true

特殊字符 : help :

返回永远为真的空命令,不做任何操作

#	获取/dev/sd*设备的分区利用率
[root@shellenv ~]# df -h | sed -rn '/\/dev\/sd/s#.* ([0-9]+)%.*#\1#p'
3.2.3 until

格式:

until CONDITION;do
	COMMANDS;
done

说明:

进入循环条件:CONDITON=false

退出循环条件:CONDITION=true

3.2.4 循环控制语句 continue

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

不追加数字时:默认退出最内层循环 即 continue = continue 1W

help continue

continue: continue [n]
Resume for, while, or until loops.

Resumes the next iteration of the enclosing FOR, WHILE or UNTIL loop.
​ If N is specified, resumes the Nth enclosing loop.

Exit Status:
​ The exit status is 0 unless N is not greater than or equal to 1.

格式范例:

while CONDITON1;do
	COMMANDS;
	if CONDITION2;then
		continue 
	fi
	COMMANDS...;
done
3.2.5 循环控制语句 break

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

help break

break: break [n]
​ Exit for, while, or until loops.

Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing loops.

Exit Status:
The exit status is 0 unless N is not greater than or equal to 1.

格式:

while CONDITON1;do
	COMMANDS;
	if CONDITION2;then
		break
	fi
	COMMANDS...;
done
3.2.6 循环控制语句 shift

shift[n] 用于将参量列表list 左移指定次数,默认为左移1次

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

shift: shift [n]
​ Shift positional parameters.

​ Rename the positional parameters N + 1 , N+1, N+1,N+2 … to $1,$2 … If N is not given, it is assumed to be 1.

Exit Status:

​ Returns success unless N is negative or greater than $#.

范例

[root@shellenv shellscript]# cat user_add.sh 
#!/bin/bash
PASS=passwd

while [ "$1" ];do
    useradd $1 && echo User $1 create success || echo User exists;
    echo "$PASS" | passwd $1 --stdin &> /dev/null;
    shift
done

3.2.7 while read 特殊用法

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

格式:

while read line;do 
	COMMANDS
done < /PATH/FROM/TARGET_FILE

说明: 以此读取/PATH/FROM/TARGET_FILE文件中的每一行,然后将行赋值给变量line

范例

cat while_read_diskcheck.sh
#!/bin/bash
WARNING=80
MAIL=root@wangxiaochun.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
cat
3.2.8 select 循环与菜单

格式:

select variable in list;do 
	循环体
done
  • select 循环主要用于创建菜单,按数字顺序的菜单项显示在标准错误上,并显示PS3提示符,等待用户输入

  • 用户输入菜单列表中的某个数字,执行相应命令

  • 用户输入被保存在内置变量REPLY

  • select是个无限循环,需要配合break或exit退出脚本。也可以ctrl+c退出

  • 常与case联合使用

  • 与 for 循环相似,可以省略in list,而使用位置参量

范例:

select ANIMAL in TIGER LION DOG ZEBRA EXIT;do
case $REPLY in
1)
    echo You choose $ANIMAL;
    ;;
2)
    echo You choose $ANIMAL;
    ;;
3)
    echo You choose $ANIMAL;
    ;;
4)
    echo You choose $ANIMAL;
    ;;
*)
    echo "end"
    exit                                                                                                                                                                     
esac
done

4 函数 function

  • 系统自带的函数 /etc/init.d/functions

    可以自行研究一下 action

4.1 管理函数

函数的构成: [函数名 / 函数体]

相关帮助 help function

4.1.1 函数定义

函数定义后,设备reboot完成后就会消失

要使的函数永久保存,需要写入文件。文件过程中,需要先定义函数,才能够执行函数

  • 语法1
Function_Name (){
    BODY
}	

**比较常用
  • 语法2
function Function_name {
    BODY
}
  • 语法3
function Function_Name () {
    BODY
}

范例

[root@shellenv shellscript]# disable_firewalld () {
> systemctl stop firewalld;
> systemctl disable firewalld;
> }
[root@shellenv shellscript]# declare -F
declare -f disable_firewalld

#	使用函数
[root@shellenv shellscript]# disable_firewall
#	直接作为一个别名alias调用即可

范例 脚本中定义函数

[root@shellenv shellscript]# cat function.sh
#!/bin/bash

disable_firewalld () {
    systemctl stop firewalld
    systemctl disable firewalld
}


disable_firewalld
4.1.1.1 函数的简单调用

对于写好的function list (function.sh)

通过使用source function.sh. function.sh 使得function.sh中的函数生效

#	重启设备后,function失效
[root@shellenv ~]# declare -F

#	生效文件
[root@shellenv shellscript]# pwd
/data/shellscript
[root@shellenv shellscript]# . function.sh

#	查看声明的函数后发现了 function_list中的内容
[root@shellenv shellscript]# declare -F
declare -f disable_firewalld

4.1.2 查看函数

declare -f [ Function_Name ] : 查看当前已定义的函数,包括函数体的具体内容

declare -F [ Function_Name ] : 查看当前已定义的函数名清单

4.1.3 删除函数

unset Function_Name : 删除函数

4.2 函数调用原则

4.2.1 交互式环境中调用
[root@shellenv shellscript]# disable_firewalld () {
> systemctl stop firewalld;
> systemctl disable firewalld;
> }
[root@shellenv shellscript]# declare -F


#	使用函数
[root@shellenv shellscript]# disable_firewall
#	直接作为一个别名alias调用即可
4.2.2 脚本中定义并调用函数

函数再使用前必须先进行定义,在函数定义后通过使用函数名Function_Name完成调用即可

[root@shellenv shellscript]# cat function.sh
#!/bin/bash

disable_firewalld () {
    systemctl stop firewalld
    systemctl disable firewalld
}


disable_firewalld
4.2.3 通过函数文件调用

通过将常用的函数存放在单独的function_list这样的一个函数文件中,接着将函数文件载入shell,然后再进行函数的调用。

将函数文件载入shell进程中,就可以通过命令行或者在脚本中调用函数。

通过 declare -f 或set命令查看已定义的所有函数

如果需要改动函数,需要使用unset命令先从shell中删除函数。修改完成后,再重新载入函数文件。

创建函数文件,只存放函数的定义

通过使用. function_listsource function_list实现函数文件的调用

范例

[root@shellenv ~]# declare -f NAME_TOM
NAME_TOM () 
{ 
    NAME=TOM;
    echo $NAME
}
[root@shellenv ~]# NAME_TOM 
TOM
[root@shellenv ~]# echo $NAME
TOM
***************************************************************************************
[root@shellenv ~]# NAME=kira
[root@shellenv ~]# NAME_TOM () { local NAME;NAME=TOM;echo $NAME;}
[root@shellenv ~]# NAME_TOM 
TOM
[root@shellenv ~]# echo $NAME
kira

#	对于bash进程内直接调用的函数,函数定义的变量会对bash进程造成影响。也就是说,函数不会生成一个子进程。
#	如果要限制函数中定义的变量的作用范围,则需要通过local声明变量的作用范围为函数体内

4.3 函数的返回值 return

函数的执行结果返回值:

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

函数的退出状态码:

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

  • 自定义退出状态码

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

    return 0 无错误返回

    return 1-255 有错误返回

4.4 环境函数

类似于环境变量,可以通过定义环境函数,使得子进程可以使用父进程定义的函数

定义:

export -f 	Function_NAME
declare -xf Function_NAME

查看:

export -f
declare -xf

4.5 函数的参数 ARGS

函数可以接收参数

参数的传递: Function_Name ARG1 ARG2 …

在函数体中,可以使用$1,$2调用参数,也可以使用$@,$*,$#这些特殊变量

4.6 函数变量

作用范围:

普通变量:尽在当前shell进程内有效,执行脚本会启动专用子进程;因此,本地变量的作用范围时当前shell脚本,包含脚本中的函数

环境变量:当前shell和子进程shell范围有效

本地变量:函数体内部,函数执行完成后自动销毁

注意点:

如果函数中定义了普通变量,且名称与局部变量相同,输出时使用本地变量

由于普通变量和局部变量会产生上述的冲突,在函数体中建议使用本地变量local

4.7 函数递归

函数递归:函数直接或间接调用自身,需要注意递归层数,有出现死循环的可能性

特点:

  • 函数内部自己调用自己

  • 必须存在结束函数的出口语句,防止死循环。

范例 阶乘n!

[root@shellenv shellscript]# cat fact.sh 
#!/bin/bash

fact () {
    if [ $1 -eq 0 -o $1 -eq 1 ]; then
        echo 1 
    else 
        echo $[$1*$(fact $[$1-1])]
    fi
}

fact $1
4.7.1 死循环(无出口的函数调用)
  • 危险操作
:(){ :|:& };:

bomb() { bomb | bomb & }; bomb

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

5 其他脚本工具

5.1 信号捕捉 trap

help trap 查看相关帮助信息

man bash 然后输入/SIGNALS 查看信号的相关信息

kill -l 可以查看信号

  • trap ‘触发指令’ 信号

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

  • trap ‘’ 信号

忽略信号的操作

  • trap ‘-’ 信号

恢复原信号的操作

  • trap -p

列出自定义信号操作

  • trap finish EXIT

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

可以执行脚本输出的log采集,备份等操作

范例

[root@shellenv shellscripts]# cat trap.sh
#! /bin/bash

trap 'echo "Press ctrl+c"' int quit			
#	当发送int和quit信号时,将执行'触发指令 echo Press ctrl+c '
trap -p
#	打印信号的定义
for ((i=0;i<=10;i++))
do
        sleep 1
        echo $i
done

trap '' int
trap -p
for ((i=11;i<=20;i++))
do
        sleep 1
        echo $i
done

trap '-' int
trap -p

for ((i=21;i<=30;i++))
do
        sleep 1
        echo $i
done
*****************************************************************************************
[root@shellenv shellscripts]# ./trap.sh
trap -- 'echo "Press ctrl+c"' SIGINT
trap -- 'echo "Press ctrl+c"' SIGQUIT
^CPress ctrl+c
0
1
2
^CPress ctrl+c
3
#	在第一段循环过程中,使用"ctrl+c"尝试退出时,就会输出 Press ctrl+c;因为将QUIT信号,指定为了"echo Press ctrl+c "
*****************************************************************************************
trap -- '' SIGINT
trap -- 'echo "Press ctrl+c"' SIGQUIT
11
^C12
13
14
15
16
17
#	在第二段循环过程中,使用"ctrl+c"尝试退出时,操作被忽略
*****************************************************************************************
trap -- 'echo "Press ctrl+c"' SIGQUIT
21
22
^C
#	在第二段循环过程中,使用"ctrl+c"尝试退出时,正常退出

5.2 创建临时文件mktemp

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

格式:mktemp [option] ... [TEMPLATE]

TEMPLATE: FILENAMEXXX, X至少要出现3个

mktemp --help 查看帮助

option:

-d 创建临时目录

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

范例

mktemp /tmp/testXXX
tmpdir=`mktemp -d /tmp/testdirXXX`
mktemp --tempdir=/testdir testXXXXX

****************************************************************************************
[root@shellenv shellscripts]# mktemp XXX.tmp
Txm.tmp
[root@shellenv shellscripts]# ll Txm.tmp
-rw-------. 1 root root 0 Jul 29 15:38 Txm.tmp

5.3 安装复制文件install

install功能相当于cp/chmod/chown等相关工具的集合

install --help 查看帮助

命令格式

install [option] ... [-T] SOURCE DEST 单文件

install [option] ... SOURCE ... DIRECTORY

install [option] ... -t DIRECTORY SOURCE ...

install [option] ... -d DIRECTORY ... 创建空目录

option

-m MODE  /MODE默认为755

-o OWNER

-g GROUP

-d DIR

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

expect主要应用于自动化交互式操作的场景,借助expect处理交互的命令,可以将交互过程写在一个脚本上,使其自动化完成。适用于多服务器相同操作的环境,提高管理人员的工作效率

命令可能需要自己手动安装 yum install expect

通过man expect / info expect可以查看相关帮助信息

语法: expect [OPTION] [-c COMMANDS] [ [ -[f|b] ] CMDFILE ] [ ARGS ]

OPTION:

-c 从命令行执行expect脚本,默认为交互式执行

-d 输出调试信息

范例

[root@shellenv shellscripts]# expect -c 'expect "\n" {send "press enter\n"}'
***************************************************************************************
[root@shellenv shellscript]# expect -c 'expect "\n" {send "press enter\n"}'
das asdk 
press enter
#	在例子中,标准输出可以显示任意输入任意字符串,当输入换行符后,终端发出了'PRESS ENTER'信号
5.4.1 expect中的相关命令:

spawn 启动新的进程

expect 从进程接收字符串

send 用于向进程发送字符串

interact 允许用户交互

exp_continue 匹配多个字符串在执行动作后添加该命令

5.4.2 常用语法:

单分支结构语法:

[root@shellenv ~]# expect
expect1.1> expect "hi" {send "hello\n"}		# 输入命令
hi
hello
expect1.2>									# 提示继续交互,可以通过exit退出交互模式

多分支结构语法

[root@shellenv ~]# expect
expect1.1> expect "hi" {send "hello\n"}
hi
hello
expect1.2> expect "hi" {send "hello\n"} "bye" {send "see you later\n"}	
bye
see you later								# 可以匹配输入的某个分支内容,然后执行对应的命令
expect1.3>		

***************************************************************************************
[root@shellenv ~]# expect
expect1.1> expect {
+> "hi" {send "hello\n"}
+> "bye" {send "see you\n"}
+> }
bye
see you		
expect1.2>									# 不同的书写风格

范例:expect变量

#!/usr/bin/expect
set ip 192.168.10.X
set user root
set password PASSWD
set timeout 10
spawn ssh $user@$ip
expect {
	"yes/no" { send "yes\n";exp_continue }
	"password" { send "$password\n" }
}
interact

范例 expect 位置参数 书写风格存在差异性

#!/usr/bin/expect
set ip [lindex $argv 0]				# 这里的 "[lindex $argv 0]" 等效 常规脚本的"$1"
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

shell脚本调用expect

#!/bin/bash
ip=$1
user=$2
password=$3
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 "echo magedu |passwd --stdin hehe\n" }
	expect "]#" { send "exit\n" }
	expect eof
EOF

更多范例不做列举,建议多多查询手册,多多训练

6 数组

6.1 介绍

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

数组: 存储多个元素的连续的内存空间,可以理解为多个变量的集合

数组名与索引:

  • 索引的编号从0开始,属于数值索引

  • 索引可支持使用自定义的格式,而不仅是数值格式,称之为关联索引,

    bash4.0版本以后开始支持

  • bash的数组支持稀疏格式(索引不连续)

6.2 数组声明

普通数组 declare -a ARRAY_NAME

可以不提前声明,直接使用

关联数组 declare -A ARRAY_NAME

必须先声明,再使用

两种数组之间不能进行相互转换

[root@shellenv ~]# declare -a array
[root@shellenv ~]# declare -A array
-bash: declare: array: cannot convert indexed to associative array
## 提示报错

6.3 数组赋值

数组元素的赋值方式:

  • 单次单个数组元素赋值 ARRAY_NAME[INDEX]=VALUE
[root@shellenv ~]# declare -a weekday
[root@shellenv ~]# weekday[0]="Sunday"
[root@shellenv ~]# weekday[1]="Monday"
  • 单次全部数组元素赋值 ARRAY_NAME=("VALUE1" "VALUE2" "VALUE3")
[root@shellenv ~]# workday=("Monday" "Tuesday" "Wedsday" "Thursday" "Friday")

[root@shellenv ~]# number=({1..10})

*****************************************************************************************
[root@shellenv shellscripts]# htmlfile=( *.html )	
[root@shellenv shellscripts]# echo ${htmlfile[*]}
10.html 1.html 2.html 3.html 4.html 5.html 6.html 7.html 8.html 9.html
#	赋值时应当注意当前的目录位置
[root@shellenv shellscripts]# pwd
/data/shellscripts
  • 单次特定数组元素赋值 ARRAY_NAME=([INDEX1]="VALUE1" [INDEX3]="VALUE2" ...)
[root@shellenv ~]# happynumber=([0]="7" [3]="5" [4]="9")
_________________________________________________________________________________________
[root@shellenv ~]# echo ${happynumber[0]}
7
[root@shellenv ~]# echo ${happynumber[1]}		# 空值

[root@shellenv ~]# echo ${happynumber[3]}
5

交互式赋值 read -a ARRAY_NAME

[root@shellenv ~]# read -a WORKDAY
MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
[root@shellenv ~]# echo ${WORKDAY[1]}
TUESDAY

6.4 数组查看

显示所有数组:

[root@shellenv ~]# declare -a
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="1")'
declare -a WORKDAY='([0]="MONDAY" [1]="TUESDAY" [2]="WEDNESDAY" [3]="THURSDAY" [4]="FRIDAY")'
declare -a array='()'
declare -a happynumber='([0]="7" [3]="5" [4]="9")'
declare -a numb='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")'
declare -a workday='([0]="Monday" [1]="Tuesday" [2]="Wedsday" [3]="Thursday" [4]="Friday")'

6.5 数组引用

引用单个数组元素 ${ARRAY_NAME[INDEX]}

如果index不进行填写,默认输出的内容为index=0的元素

引用全部数组元素 ${ARRAY_NAME[*]} 或者 ${ARRAY_NAME[@]}

[root@shellenv ~]# echo ${workday[*]}
Monday Tuesday Wedsday Thursday Friday

[root@shellenv ~]# echo ${workday[@]}
Monday Tuesday Wedsday Thursday Friday

6.6 删除数组 unset

删除单个数组元素 unset ARRAY_NAME[INDEX]

该操作会导致数组索引不连续,造成稀疏格式

删除整个数组 unset ARRAY_name

6.7 数组数据处理

  • 切片:${ARRAY_NAME[@]:OFFSET:NUMBER}
OFFSET:		要跳过的元素个数
NUMBER:		要取出的元素个数

取出偏移量后的所有个数,则只需要填写OFFSET内容

范例

[root@shellenv ~]# NUMBER=({1..10})
declare -a NUMBER='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")'

[root@shellenv ~]# echo ${NUMBER[@]:2}
3 4 5 6 7 8 9 10

[root@shellenv ~]# echo ${NUMBER[@]:3:3}
4 5 6
  • 向数组追加元素ARRAY_NAME[${#ARRAY_NAME[*]}]=VALUEARRAY_NAME[${#ARRAY_NAME[@]}]=VALUE
[root@shellenv ~]# NUMBER[${#NUMBER[*]}]=11
declare -a NUMBER='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10" [10]="11")'

*****************************************************************************************
[root@shellenv ~]# NUMBER[${#NUMBER[@]}]=12
declare -a NUMBER='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10" [10]="11" [11]="12")'

6.8 关联数组

关联数组必须先申明,然后再进行调用

declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)

范例

[root@shellenv ~]# declare -A JOB
[root@shellenv ~]# JOB[zhang]=doctor
[root@shellenv ~]# JOB[wang]=operatir
[root@shellenv ~]# JOB[li]=teacher
[root@shellenv ~]# echo ${JOB[*]}
doctor operatir teacher
[root@shellenv ~]# echo ${JOB[wang]}
operatir
[root@shellenv ~]# echo ${JOB[zhang]}
doctor
[root@shellenv ~]# echo ${JOB[li]}
teacher

7 字符串处理

7.1 字符串切片

  • 基于偏移量取字符串

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

[root@shellenv ~]# length=123456
[root@shellenv ~]# echo ${#length}
6

${VAR:offset} : 返回字符串变量VAR中第OFFSET个字符后(不包含第offset字符串)的字符开始,到最后的部分。offset的取值在0 -${#VAR} -1 之间

[root@shellenv ~]# length=123456
[root@shellenv ~]# echo ${length:3}
456
[root@shellenv ~]# echo ${length:7}

[root@shellenv ~]# echo ${length:-1}
123456
[root@shellenv ~]# echo ${length:0}
123456

${VAR:OFFSET:NUMBER} : 返回字符串变量VAR中从第OFFSET个字符串后(不包含第OFFSET个字符)的字符开始,长度为NUMBER的部分

[root@shellenv ~]# length=123456
[root@shellenv ~]# echo ${length:3:2}
45
[root@shellenv ~]# echo ${length:1:6}
23456

${VAR: -LENGTH} : 取字符串的最右侧的LENGTH个长度的字符;冒号后存在空格

[root@shellenv ~]# length=123456
[root@shellenv ~]# echo ${length: -4}
3456

${VAR:OFFSET:-LENGTH} : 跳过OFFSET个字符,一直取到最右侧LENGTH个字符前的内容 “掐头去尾”

[root@shellenv ~]# length=123456
[root@shellenv ~]# echo ${length:2:-3}
3
[root@shellenv ~]# echo ${length:1:-1}
2345

${VAR: -LENGTH:-OFFSET} : 从最右侧向左取到LENGTH个字符开始,向右取到距离最右侧OFFSET个字符之间的内容; LENGTH前存在空格

[root@shellenv ~]# length=123456
[root@shellenv ~]# echo ${length: -4:2}
34
  • 基于模式取字符串内容

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

${VAR##*word} : 同上述内容,不同的是删除的是字符串开头到最后一次word字符之间的所有内容

[root@shellenv ~]# str=abcddefghddeeeeffg
[root@shellenv ~]# echo ${str#*d}
defghddeeeeffg
[root@shellenv ~]# echo ${str##*d}
eeeeffg

${VAR%word*} : word为指定的任意字符串;自右向左,查找var变量中,第一次出现的word,删除字符串最后一个字符向左到第一次出现word字符串之间(包含word)的所有字符

${VAR%%word*} : 同上述内容,不同的是删除字符串最后一个字符向左到最后一次word字符的所有内容

[root@shellenv ~]# str=abcddefghddeeeeffg
[root@shellenv ~]# echo ${str%d*}
abcddefghd
[root@shellenv ~]# echo ${str%%d*}
abc

7.2 查找替换

${VAR/pattern/substr} : 查找VAR字符串中,第一次被PATTERN所匹配的字符串,用substr替换它

[root@shellenv ~]# str=abcddefghd4de4e3ee1ffg
[root@shellenv ~]# echo ${str/[1-9]/A}
abcddefghdAde4e3ee1ffg

## 将第一个匹配到的数字用字母A进行替换

${VAR//pattern/substr} : 查找VAR 字符串中,所有能被pattern匹配的字符串,用substr替换它

[root@shellenv ~]# echo ${str//[1-9]/A}
abcddefghdAdeAeAeeAffg

## 将所有匹配到的数字用字母A进行替换

${VAR/#pattern/sbustr} : 查找VAR字符串中,行首被pattern所匹配的字符串,用substr替换它

${VAR/%pattern/substr} : 查找VAR字符串中,行尾被pattern所匹配道德字符串,用substr替换它

7.3 查找并删除

${VAR/pattern} : 删除VAR字符串中第一次被pattern匹配到的字符串

${VAR//pattern} : 删除VAR字符串中所有被pattern匹配到的字符串

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

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

7.4 大小写转换

${VAR^^} : 将VAR表示的字符串中所有小写字母转换为大写

${VAR,,} : 将VAR表示的字符串中所有大写字母转换为小写

8 高级变量

8.1 高级变量赋值

变量配置方式str没有配置str为空字符串str配置且为非空
var=${str-expr}var=exprvar=var=$str
var=${str:-expr}var=exprvar=exprvar=$str
var=${str+expr}var=var=exprvar=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输出到stderrvar=var=$str
var=${str:?expr}expr输出到stderrexpr输出到stderrvar=$str

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

shell变量一般为无类型变量,bash提供了declaretypeset 两个命令用于指定变量的类型

declare options VARIABLES

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

8.3 变量的间接引用

8.3.1 eval命令

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

范例

[root@shellenv ~]# CMD="cat /etc/hosts"
[root@shellenv ~]# echo $CMD
cat /etc/hosts
[root@shellenv ~]# eval $CMD
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6


[root@shellenv ~]# n=10
[root@shellenv ~]# echo {1..$n}
{1..10}
[root@shellenv ~]# eval echo {1..$n}
1 2 3 4 5 6 7 8 9 10


[root@shellenv ~]# i=1;j=a
[root@shellenv ~]# echo $i$j
1a
[root@shellenv ~]# eval $i$j
-bash: 1a: command not found

8.3.2 间接变量引用

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

VAR1,VAR2均为变量名,VAR2的值为VALUE / 即为 VAR1获得变量值VALUE的操作

VAR1=VAR2

VAR2=value

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

eval TEMPVAR=\$$VAR1

TEMPVAR=${!VAR1}

***************************************************************************************

[root@shellenv ~]# VAR1=VAR2
[root@shellenv ~]# VAR2=hello
[root@shellenv ~]# echo ${!VAR1}
hello

[root@shellenv ~]# eval TMPVAR=\$$VAR1
[root@shellenv ~]# echo $TMPVAR
hello

8.3.3 变量引用reference

本机的操作系统centos7.9环境不支持declare -n ,所以直接引用视频中的内容

[root@centos8 ~]#cat test.sh
#!/bin/bash
title=ceo
ceo=mage
#声明引用变量ref
declare -n ref=$title
[ -R ref ] && echo "reference" 			# 	[ -R var ] 是bash4.4新特性
echo ref=$ref
ceo=wang
echo ref=$ref

[root@centos8 ~]#bash test.sh
reference
ref=mage
ref=wang

注意

文章由博主Benjemin (linux小白)自行整理,书写风格和内容不一定由大家所接受,期待多多交流。

文章知识内容源自bilibili的王晓春老师的shell脚本教程,视频链接:linux shell脚本最全编程

整理不易,希望多多关注点赞。

关于shell脚本学习的一些建议:应当多学多练,熟悉基础命令,才能做到更好的使用。高级脚本的编写会涉及到一些更深层的知识(包括不限于内存,算法等)

推荐一些脚本练习的网站:leetcode / 牛客网等。大家可以自行评论交流。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值