一、Shell 脚本的基本用法

1.1 Shell 脚本的用途

  • 将简单的命令进行组合完成复杂的工作,能够自动化执行,从而提高工作效率
  • 减少命令的重复输入
  • 将软件或应用的安装及配置实现标准化
  • 方便日常性、重复性的运维工作实现,如:文件的打包压缩备份、监控系统的运行状态等

1.2 Shell 脚本的基本结构

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

Shell 脚本:包含一些命令或声明,符合一定格式的文本文件,首行需shebang机制

1
2
3
4
# shebang机制如下类型
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

1.3 Shell 脚本注释规范

(1)首行为调用使用的语言,即shebang机制

(2)程序名,避免因更改文件名而无法找到正确的文件

(3)该脚本的版本号

(4)更改后的时间

(5)作者的相关信息

(6)该程序的作用及注意事项

(7)各版本的更新简要说明

1.4 第一个简单Shell脚本示例

示例:第一个脚本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@centos7 ~]# vim /shell/hello.sh
#!/bin/bash
#----------------------------------------------
# Filename: hello.sh
# Version: 1.0
# Date: 2022/10/01
# Author: Herbert
# Email: wuhaolam@163.com
# Website: wuhaolam.github.io
# Description: This is the first script
#----------------------------------------------
echo 'Hello, world!'

# 执行方式1
[root@centos7 ~]# bash /shell/hello.sh

# 执行方式2
[root@centos7 ~]# cat /shell/hello.sh | bash

# 执行方式3
[root@centos7 ~]# bash < /shell/hello.sh

# 执行方式4
[root@centos7 ~]# chmod +x /shell/hello.sh
## 绝对路径
[root@centos7 shell]# /shell/hello.sh
## 相对路径
[root@centos7 ~]# cd /shell
[root@centos7 shell]# ./hello.sh

1.5 脚本调试

只检测脚本中的语法错误,不检查命令错误,并不执行脚本

1
bash -n /path/script

调试并执行

1
2
3
4
5
bash -x /path/script

[root@centos7 shell]# bash -x hello.sh
+ echo 'Hello, world!'
Hello, world!

注:常见的三种脚本错误方式

  • 语法错误,后续命令将无法执行,使用 bash -n 检查错误,提示的出错行数不一定准确
  • 命令错误,默认后续的命令还会执行,使用 bash -x 进行观察
  • 逻辑错误,只能使用 bash -x 进行观察

1.6 变量

1.6.1 变量

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

1.6.2 Shell 中变量的命名法则

1、命名要求

  • 区分大小写
  • 不能使用程序中的保留字和内置变量
  • 只能使用字母、数字、下划线,不能以数字开头,不支持短横线 “-”

2、命名习惯

  • 见名知义,用英文命名,体现出实际作用,不可用简写,如:ATM
  • 变量名要大写
  • 局部变量小写
  • 函数名小写
  • 大驼峰形式:StudentFirstName
  • 小驼峰形式:studentFirstName
  • 下划线:student_name

1.6.3 变量的定义和引用

变量的类型

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

变量赋值

1
2
3
4
5
6
7
8
name='value'

value 形式如下:
直接字符串: name='root'
变量引用: name="$USER"
命令引用: name=`COMMAND` 或 name=$(COMMAND)

# 注:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量会随着脚本结束自动删除

变量引用

1
2
$name
${name}

弱引用和强引用

  • “$name” 弱引用,其中定义的变量会被替换为变量值
  • ‘$name’ 强引用,定义的变量不会被替换为变量值,引号中是什么就会输出什么

显示定义的变量和删除变量

1
2
set
unset <name>

示例:变量的各种赋值和引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[root@centos7 ~]# echo "I am $NAME"
I am Herbert
[root@centos7 ~]# echo 'I am $NAME'
I am $NAME

[root@centos7 ~]# NAME=$USER
[root@centos7 ~]# echo $NAME
root
[root@centos7 ~]# NAME=`whoami`
[root@centos7 ~]# echo $NAME
root

[root@centos7 ~]# FILE=`ls /run`
[root@centos7 ~]# echo $FILE
auditd.pid console crond.pid cron.reboot cryptsetup dbus dhclient-eth0.pid dmeventd-client dmeventd-server faillock httpd initramfs lock log lvm lvmetad.pid mount netreport NetworkManager plymouth sepermit setrans sshd.pid sudo syslogd.pid systemd tmpfiles.d tuned udev user utmp vmware

[root@centos7 ~]# FILE=/etc/*;echo $FILE
/etc/adjtime /etc/aliases /etc/aliases.db /etc/alternatives /etc/anacrontab

[root@centos7 ~]# NUM=`seq 3`;echo $NUM
1 2 3
[root@centos7 ~]# NUM=`seq 3`;echo "$NUM"
1
2
3

[root@centos7 ~]# NAMES="zhao
> qian
> sun
> li"
[root@centos7 ~]# echo $NAMES
zhao qian sun li
[root@centos7 ~]# echo "$NAMES"
zhao
qian
sun
li

[root@centos7 ~]# NAME=Herbert
[root@centos7 ~]# AGE=20
[root@centos7 ~]# echo $NAME_$AGE
20
[root@centos7 ~]# echo ${NAME}_$AGE
Herbert_20

# 利用变量实现动态命令
[root@centos7 ~]# CMD=hostname
[root@centos7 ~]# $CMD
centos7

# 删除定义的变量
[root@centos7 ~]# NAME=Herbert;echo $NAME
Herbert
[root@centos7 ~]# unset NAME;echo $NAME

练习

1、编写脚本 systeminfo.sh,显示当前主机系统信息,包括:主机名,IPv4地址,操作系统版本,内核
版本,CPU型号,内存大小,硬盘大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos7 shell]# cat systeminfo.sh 
#!/bin/bash
# **********************************************************
# * Filename : systeminfo.sh
# * Author : Herbert
# * Version : 2.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-03
# * Description : display information about the system
# **********************************************************
RED="\E[1;31m"
GREEN="\E[1;32m"
END="\E[0m"
echo -e "$GREEN---------------------Host Systeminfo-------------------------$END"
echo -e "HOSTNAME: $RED`hostname`$END"
echo -e "IPADDR: $RED`ifconfig eth0 | awk '/\<inet\>/{print $2}'`$END"
echo -e "OSVersion: $RED`cat /etc/redhat-release`$END"
echo -e "KERNAL: $RED`uname -r`$END"
echo -e "CPU: $RED`lscpu | grep 'Model name' | tr -s ' '|cut -d : -f2`$END"
echo -e "MEMORY: $RED`free -h | awk '/^Mem/{print $2}'`$END"
echo -e "DISK: $RED`lsblk | awk -v ORS=',' '/^sd/{print $1,$4}'`$END"
echo -e "$GREEN--------------------- END -------------------------$END"

2、编写脚本 backup.sh,可实现每日将 /etc/ 目录备份到 /backup/etcYYYY-mm-dd中

1
2
3
4
5
6
7
8
9
10
[root@centos7 shell]# cat backup.sh 
#!/bin/bash
echo -e "\E[1;32mStart Backup\E[0m"
[ -d /backup ] || mkdir /backup
cp -a /etc /backup/etc_`date +%F`
echo -e "\E[1;32mBackup finished\E[0m"

# 每天的凌晨2点运行脚本
[root@centos7 shell]# crontab -e
* 2 * * * "bash /shell/backup.sh"

3、编写脚本 disk.sh,显示当前硬盘分区中空间利用率最大的值

1
2
3
[root@centos7 shell]# vim disk_utilization_rate.sh
Most_Disk_Rate=`df -Th | awk '/^\/dev\/sd/{print $6}' | sort -nr | head -n1`
echo "磁盘空间利用率最大值为:$Most_Disk_Rate"

1.6.4 环境变量

环境变量的特点

  • 子进程可以继承父进程的变量,但父进程无法使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,新的值将会传递给孙子进程
  • 一般在系统配置文件中使用,脚本中使用较少

变量的声明和赋值

1
2
3
4
5
6
7
# 声明并赋值
export name=value
declare -x name=value

# 分步实现
name=value
export name

变量的引用

1
2
$name
${name}

显示所有环境变量

1
2
3
4
env
printenv
export
declare -x

查看指定进程的环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat /proc/$PID/environ

[root@centos7 ~]# cat /proc/1495/environ | tr '\0' '\n'
USER=root
LOGNAME=root
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
MAIL=/var/mail/root
SHELL=/bin/bash
SSH_CLIENT=192.168.119.1 12198 22
SSH_CONNECTION=192.168.119.1 12198 192.168.119.165 22
SSH_TTY=/dev/pts/0
TERM=xterm
XDG_SESSION_ID=1
XDG_RUNTIME_DIR=/run/user/0

删除变量

1
unset name

1.6.5 只读变量

​ 只能声明定义,后续不能修改和删除,即常量

声明只读变量

1
2
readonly name
declare -r name

查看只读变量

1
2
readonly
declare -r

示例:

1
2
3
4
5
6
7
8
9
[root@centos7 ~]# readonly PI=3.14159;echo $PI
3.14159
[root@centos7 ~]# PI=3.14
-bash: PI: readonly variable

[root@centos7 ~]# exit
[root@centos7 ~]# echo $PI

[root@centos7 ~]#

1.6.6 位置变量

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

1
2
3
4
5
6
7
8
9
10
$1,$2,$3,··· 对应第1、第2、第3个等参数,shift [n]换位置
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合并成一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数个数

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

清空所有位置变量
set --

示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 位置变量的简单使用
[root@centos7 ~]# cat /shell/arg.sh
#!/bin/bash
# **********************************************************
# * Filename : arg.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-03
# * Description : 位置变量的简单使用
# **********************************************************
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${10}"
echo "11st arg is ${11}"

echo "The number of arg is $#"
echo "All args are: $*"
echo "All args are: $@"
echo "The script path is $0"
echo "The scriptname is `basename $0`"
[root@centos7 shell]# bash /shell/arg.sh {a..z}
1st arg is a
2st arg is b
3st arg is c
10st arg is j
11st 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 script path is /shell/arg.sh
The scriptname is arg.sh

示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# $* 和 $@ 的区别
[root@centos7 shell]# cat f1.sh
#!/bin/bash
echo "f1.sh: all args are $@"
echo "f1.sh: all args are $*"

./file.sh "$*"
[root@centos7 shell]# cat f2.sh
#!/bin/bash
echo "f2.sh: all args are $@"
echo "f2.sh: all args are $*"

./file.sh "$@"
[root@centos7 shell]# cat file.sh
#!/bin/bash
echo "file.sh: 1st arg is $1"

## $*将所有的参数作为一个整体进行传递,而$@将所有参数当作独立的参数全部传递过去
[root@centos7 shell]# bash f1.sh a b c
f1.sh: all args are a b c
f1.sh: all args are a b c
file.sh: 1st arg is a b c
[root@centos7 shell]# bash f2.sh a b c
f2.sh: all args are a b c
f2.sh: all args are a b c
file.sh: 1st arg is a

示例3

1
2
3
4
5
6
7
8
9
10
11
# 利用软链接实现同一个脚本的不同功能
[root@centos7 shell]# cat test.sh
#!/bin/bash
echo $0
[root@centos7 shell]# ln -s test.sh a.sh
[root@centos7 shell]# ln -s test.sh b.sh

[root@centos7 shell]# bash a.sh
a.sh
[root@centos7 shell]# bash b.sh
b.sh

示例4

1
2
3
4
5
6
7
8
9
10
11
12
13
# 删库跑路命令安全实现
[root@centos7 shell]# cat rm.sh
#!/bin/bash
DIR=/tmp/`date +%F_%T`
mkdir $DIR
mv $* $DIR
echo "Move $* to $DIR"

[root@centos7 shell]# chmod +x rm.sh
[root@centos7 shell]# alias rm='/shell/rm.sh'
[root@centos7 ~]# touch {1..5}.txt
[root@centos7 ~]# rm *.txt
Move 1.txt 2.txt 3.txt 4.txt 5.txt to /tmp/2022-10-03_21:57:42

1.6.7 退出状态码变量

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

1
2
$? 为0,进程返回状态为成功 
$? 为1~255,失败

用户可以在脚本中自定义退出的状态码

1
exit [n]

注意

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

1.6.8 展开命令行

展开命令执行顺序

1
2
3
4
5
6
7
8
9
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配*、?、[abc]等等
准备I/0重导向 <、>
运行命令

1.6.9 脚本安全和set

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

$- 变量

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

[root@Rocky8-mini ~]# echo $-
himBHs
[root@Rocky8-mini ~]# set +h
[root@Rocky8-mini ~]# echo $-
imBHs
[root@Rocky8-mini ~]# hash
-bash: hash: hashing disabled

set 命令实现脚本安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-u	在扩展一个没有设置的变量时,显示错误信息,等同 set -o nounset
-e 如果一个命令返回一个非0退出状态值就退出,等同 set -o errexit
-o option 显示,打开或关闭选项
set -o 显示选项
set -o 选项 打开选项
set +o 选项 关闭选项
-x 当执行命令时,打印命令及其参数,类似 bash -x

[root@Rocky8-mini ~]# set -o
allexport off
braceexpand on
emacs on
errexit off
errtrace off
functrace off
hashall off
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

1.7 格式化输出 printf

常用格式替换符

替换符 功能
%s 字符串
%d,%i 十进制整数
%f 浮点格式
%c ASCII字符
%b 相对应的参数中包含转义字符,可对转义字符进行转义
%o 八进制值
%u 无符号十进制值
%x 小写的十六进制 a~f
%X 大写的十六进制 A~F
%% %本身

说明

1
2
3
%#s	中的数字代表此替换符中的输出字符宽度,不足补空格,默认右对齐。%-10s表示10个字符宽,- 表示左对齐
%03d 表示3为宽度,不足前面用0补全,超出位数原样输出
%.2f 表示保留2位小数

常用转义字符

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

示例:

1
2
3
4
5
6
7
8
9
10
[root@centos7 ~]# printf "%s" 1 2 3 4
1234[root@centos7 ~]#
[root@centos7 ~]# printf "%s %s %s %s \n" 1 2 3 4
1 2 3 4

[root@centos7 ~]# printf "%.2f\n" 2.458
2.46

[root@centos7 ~]# strvar="I love you";printf "\033[1;32m%s\033[0m\n" "$strvar"
I love you

1.8 算数运算

注:bash 只支持整数,不支持小数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ARITHMETIC EVALUATION
The shell allows arithmetic expressions to be evaluated, under certain circumstances (see the let and declare builtin commands and Arithmetic Expansion). Evaluation is done in fixed-width integers with no check for overflow, though division by 0 is trapped and flagged as an error.
The operators and their precedence, associativity, and values are the same as in the C language. The following list of operators is grouped into levels of equal-precedence operators. The levels are listed in order of decreasing precedence.

* / % multiplication, division, remainder, %表示取模,即取余数,示例:9%4=1,5%3=2
+ - addition, subtraction
i++ i-- variable post-increment and post-decrement
++i --i variable pre-increment and pre-decrement
= *= /= %= += -= <<= >>= &= ^= |= assignment
- + unary minus and plus
! ~ logical and bitwise negation
** exponentiation 乘方,即指数运算
<< >> left and right bitwise shifts
<= >= < > comparison
== != equality and inequality
& bitwise AND
| bitwise OR
^ bitwise exclusive OR
&& logical AND
|| logical OR
expr?expr:expr conditional operator
expr1 , expr2 comma

算数运算实现

1
2
3
4
5
6
7
8
9
10
11
12
13
(1)	let var=算术表达式
(2) ((var=算术表达式))
(3) var=$[算术表达式]
(4) var=$((算术表达式))
(5) var=$(expr arg1 arg2 arg3 ...)
(6) declare -i var = 数值
(7) echo '算术表达式' | bc

随机数生成器 $RANDOM
ooa random integer between 0 and 32767 is generated.

## 注:乘法符号在有些场景需要转义
# mul=$(expr $num1 \* $num2)

示例:随机字体颜色生成

随机字体颜色生成

增强型赋值

1
2
3
4
5
6
7
+=	i+=10 等同于 i=i+10
-=
*=
/=
%=
++ i++ ++i
-- i-- --i

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(1)
[root@Rocky8-mini ~]# num1=3
[root@Rocky8-mini ~]# num2=5
[root@Rocky8-mini ~]# let sum=$num1+$num2
[root@Rocky8-mini ~]# echo $sum
8
(2)
[root@Rocky8-mini ~]# echo "The sum is $[$num1+$num2]"
The sum is 8
[root@Rocky8-mini ~]# echo "The sum is $(($num1+$num2))"
The sum is 8
[root@Rocky8-mini test]# echo "$((num1+num2))"
8
(3)
[root@Rocky8-mini ~]# mul=$(expr $num1 \* $num2);echo $mul
15
[root@Rocky8-mini ~]# mul=$[$num1 * $num2];echo $mul
15
(4)
[root@Rocky8-mini ~]# echo $RANDOM
14098
[root@Rocky8-mini ~]# echo $RANDOM
3320

1.9 逻辑运算

1
2
3
4
5
6
与	&			0&0=0;0&1=0;1&1=1
或 | 0&0=0;0&1=1;1&1=1
非 ! 取反;!0=1
异或 ^ 相同为假,不同为真
短路与 &&
短路或 ||

示例:关于异或运算达成数值的交换

1
2
3
# 两个数字x,y异或得z;z和任意一个数字x或y做异或运算,得到另一个数字y或x
[root@centos7 ~]# x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo "x=$x,y=$y"
x=20,y=10

示例:关于短路运算

1
2
3
4
5
6
7
8
9
# 短路与 &&
CMD1 && CMD2
CMD1结果为真 (1),第二个CMD2必须要参与运算,才能得到最终的结果
CMD1结果为假 (0),总的结果必定为0,因此不需要执行CMD2

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

1.10 条件测试命令

条件测试:判断某需求是否满足;若条件测试为真,则返回的状态码$?为0;反之则不为0

条件测试命令

  • test EXPRESSION
  • [ EXPRESSION ]
  • [[ EXPRESSION ]] 支持扩展的正则表达式和通配符
1
2
3
4
[root@centos7 ~]# type [
[ is a shell builtin

# 注:括号中的 EXPRESSION 两边一定要有空白字符

bash 中的测试类型查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
[root@centos7 ~]# help test
test: test [expr]
Evaluate conditional expression.

Exits with a status of 0 (true) or 1 (false) depending on
the evaluation of EXPR. Expressions may be unary or binary. Unary
expressions are often used to examine the status of a file. There
are string operators and numeric comparison operators as well.

The behavior of test depends on the number of arguments. Read the
bash manual page for the complete specification.

File operators:

-a FILE True if file exists.
-b FILE True if file is block special.
-c FILE True if file is character special.
-d FILE True if file is a directory.
-e FILE True if file exists.
-f FILE True if file exists and is a regular file.
-g FILE True if file is set-group-id.
-h FILE True if file is a symbolic link.
-L FILE True if file is a symbolic link.
-k FILE True if file has its `sticky' bit set.
-p FILE True if file is a named pipe.
-r FILE True if file is readable by you.
-s FILE True if file exists and is not empty.
-S FILE True if file is a socket.
-t FD True if FD is opened on a terminal.
-u FILE True if the file is set-user-id.
-w FILE True if the file is writable by you.
-x FILE True if the file is executable by you.
-O FILE True if the file is effectively owned by you.
-G FILE True if the file is effectively owned by your group.
-N FILE True if the file has been modified since it was last read.

FILE1 -nt FILE2 True if file1 is newer than file2 (according to
modification date).

FILE1 -ot FILE2 True if file1 is older than file2.

FILE1 -ef FILE2 True if file1 is a hard link to file2.

String operators:

-z STRING True if string is empty.

-n STRING
STRING True if string is not empty.

STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.

Other operators:

-o OPTION True if the shell option OPTION is enabled.
-v VAR True if the shell variable VAR is set
! EXPR True if expr is false.
EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.

arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.

Arithmetic binary operators return true if ARG1 is equal, not-equal,
less-than, less-than-or-equal, greater-than, or greater-than-or-equal
than ARG2.

Exit Status:
Returns success if EXPR evaluates to true; fails if EXPR evaluates to
false or an invalid argument is given.

1.10.1 变量测试

1
2
3
4
5
6
7
8
9
# 判断变量是否定义 
[ -v NAME ]
# 判断变量是否定义并且是名称引用,bash 4.4新特性
[ -R NAME ]

[root@centos7 ~]# echo "$BASH_VERSION"
4.2.46(2)-release
[root@Rocky8-mini ~]# echo "$BASH_VERSION"
4.4.20(1)-release

示例

1
2
3
4
[root@Rocky8-mini ~]# unset x
[root@Rocky8-mini ~]# [ -v x ]
[root@Rocky8-mini ~]# echo $?
1

1.10.2 数值测试

1
2
3
4
5
6
7
8
9
-eq 是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于

算数表达式比较
== != <= >= < >

示例

1
2
3
4
5
6
7
8
9
10
[root@centos7 ~]# i=10
[root@centos7 ~]# j=8
[root@centos7 ~]# [ $i -lt $j ];echo $?
1
[root@centos7 ~]# [ i -gt j ];echo $?
-bash: [: i: integer expression expected
2

[root@centos7 ~]# (( i >= j ));echo $?
0

1.10.3 字符串测试

test 和 [] 用法

1
2
3
4
5
6
7
-z STRING        字符串是否为空,空为真
-n STRING 字符串是否不空,不空为真
STRING

STRING1 = STRING2 是否等于
STRING1 != STRING2 是否不等于
>、< 比较的是ASCII码的大小值

[[]] 用法

1
2
3
4
5
[[ expression ]]
== 左侧字符串是否和右侧的PATTERN相同
note: 此表达式使用于[[]],PATTERN支持通配符
=~ 左侧字符串是否能被右侧的正则表达式的PATTERN匹配
note: 此表达式使用于[[]]时,使用扩展的正则表达式

[ ] 示例

1
2
3
4
5
6
7
8
9
10
11
# 在比较字符串时,建议变量放在""中
[root@centos7 ~]# str1=Tom
[root@centos7 ~]# str2=Jerry
[root@centos7 ~]# [ "$str1" = "$str2" ];echo $?
1

[root@centos7 ~]# str3="I love you"
[root@centos7 ~]# [ $str3 ]
-bash: [: love: binary operator expected
[root@centos7 ~]# [ "$str3" ];echo $?
0

[[ ]] 和通配符示例

1
2
3
4
5
6
7
8
9
10
11
[root@centos7 ~]# strvar1="Rocky_linux"
[root@centos7 ~]# [[ "$strvar1" == Rocky* ]];echo $?
0

[root@centos7 ~]# [[ "$strvar1" == "Rocky*" ]];echo $?
1
[root@centos7 ~]# strvar1="Rocky*"
[root@centos7 ~]# [[ "$strvar1" == "Rocky*" ]];echo $?
0

# 右侧的模式作为通配符时不需要加"",只是表达字符本身,加""或转义

[[ ]]和扩展正则表达式示例

1
2
3
4
5
6
7
8
9
# 判断合理的考试成绩
[root@centos7 ~]# SCORE=101
[root@centos7 ~]# [[ $SCORE =~ ^(100|[0-9]{1,2})$ ]];echo $?
1

# 判断文件后缀
[root@centos7 ~]# FILE=abc.log
[root@centos7 ~]# [[ "$FILE" =~ \.log$ ]];echo $?
0

1.10.4 文件测试

存在性测试

1
2
3
4
5
6
7
8
9
10
11
-a FILE: 同 -e
-e FILE: 判断文件是否存在,存在则为真
-b FILE: 是否是块设备文件
-c FILE: 是否存在且为字符设备文件
-d FILE: 是否存在且为目录文件
-f FILE: 是否存在且为普通文件
-h FILE 或 -L FILE: 存在且为符号链接文件
-p FILE: 是否存在且为命名管道文件
-S FILE: 是否存在且为套接字文件

! 非 例: [ ! -a FILE ] 判断该文件是否不存在

文件权限测试

1
2
3
4
5
6
7
8
-r FILE: 是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
-u FILE: 是否存在且拥有suid权限
-g FILE: 是否存在且拥有sgid权限
-k FILE: 是否存在且拥有sticky权限

note: 最终结果由用户对文件的实际权限决定,而非文件属性决定

范例

1
2
3
4
5
6
7
8
# /etc/shadow 属性和root用户实际权限
# 判断文件是否具有某一权限,应根据该用户对文件的实际执行操作结果为准
[root@Rocky8-mini test]# ll /etc/shadow
---------- 1 root root 759 Mar 27 02:44 /etc/shadow
[root@Rocky8-mini test]# [ -w /etc/shadow ]; echo $?
0
[root@Rocky8-mini test]# [ -x /etc/shadow ]; echo $?
1

文件属性测试

1
2
3
4
5
6
7
8
-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

1.11 关于 () 与 {}

(CMD1;CMD2;···) 和 { CMD1;CMD2;···; } 都可以将多个命令组合在一起,批量执行

1
2
3
4
5
6
7
8
9
(list) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境

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

# 搜索(list)
[root@centos7 ~]# man bash
(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.

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@centos7 ~]# name=Tom;(echo $name;name=Jerry;echo $name);echo $name
Tom
Jerry
Tom

[root@centos7 ~]# name=Tom;{ echo $name;name=Jerry;echo $name; };echo $name
Tom
Jerry
Jerry

# 当我们需要临时切换到某个目录执行某些操作,之后在返回原先的目录,可使用小括号
[root@Rocky8-mini ~]# (cd /data/test/; rm -f f1.txt)
[root@Rocky8-mini ~]#

1.12 组合测试条件

1.12.1 第一种方式

1
2
3
4
5
6
[ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为

[ ! EXPRESSION ] 取反

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

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@centos7 ~]# ll /shell/test.sh 
-rw-r--r-- 1 root root 350 Oct 3 21:46 /shell/test.sh
[root@centos7 ~]# FILE=/shell/test.sh
[root@centos7 ~]# [ -f "$FILE" -a -x "$FILE" ];echo $?
1
[root@centos7 ~]# chmod +x /shell/test.sh
[root@centos7 ~]# [ -f "$FILE" -a -x "$FILE" ];echo $?
0

[root@centos7 ~]# chmod -x /shell/test.sh
[root@centos7 ~]# [ -f "$FILE" -o -x "$FILE" ];echo $?
0

[root@centos7 ~]# [ ! -x "$FILE" ];echo $?
0
[root@centos7 ~]# ! [ -x "$FILE" ];echo $?
0

1.12.2 第二种方式

1
2
3
4
5
6
7
COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2

COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2

! COMMAND #非,取反

示例

1
2
3
4
5
6
7
8
[root@centos7 ~]# [ "a" = "B" ] && echo "String is same."
[root@centos7 ~]# [ "a" = "a" ] && echo "String is same."
String is same.
[root@centos7 ~]# [ -f /etc/fstab -a -x /bin/cat ] && cat /etc/fstab

[root@centos7 ~]# id wh &> /dev/null || useradd wh
[root@centos7 ~]# getent passwd wh
wh:x:1000:1000::/home/wh:/bin/bash

示例:&& 和 || 组合使用

1
2
[root@centos7 ~]# NAME=Herbert;id $NAME &> /dev/null && echo "$NAME is exist" || echo "$NAME is not exist"
Herbert is not exist

练习

1、编写脚本 argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数

1
2
3
4
5
[root@Rocky8-mini test]# cat argsnum.sh 
#!/bin/bash

[ $# -lt 1 ] && echo "Please input a path!" && exit 1
echo "$1 has space characters: `grep -E '^[[:space:]]+$' $1 | wc -l`"

2、编写脚本 hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”

1
2
3
4
5
6
[root@Rocky8-mini test]# cat hostping.sh 
#!/bin/bash

IP=$1
fping $IP &> /dev/null && echo "$IP is reachable!" || { echo "$IP is unreachable!";exit; }
echo "Script is finished"

3、编写脚本 checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini test]# cat checkdisk.sh 
#!/bin/bash
Error=80
MAIL=wuhaolam@163.com

Disk=`df -Th | sed -n '/^\/dev\/sd*/p' | tr -s ' ' % | cut -d% -f6 | sort -r | head -1`
[ $Disk -ge $Error ] && (echo "`hostname -I ` insufficient disk space "| mail -s "Linux Disk Warning" wuhaolam@163.com )

Inode=`df -ih | sed -n '/^\/dev\/sd*/p' | tr -s ' ' % | cut -d% -f5 | sort -r | head -1`
[ $Inode -ge $Error ] && ( echo "`hostname -I ` insufficient inode space "| mail -s "Linux Inode Warning" $MAIL)

4、编写脚本 per.sh,判断当前用户的指定参数文件,是否不可读并且不可写

1
2
3
[wh@Rocky8-mini ~]$ [ ! -r /etc/gshadow -a ! -w /etc/gshadow ]   
[wh@Rocky8-mini ~]$ echo $?
0

5、编写脚本 excute.sh ,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件

1
2
3
4
5
6
[root@Rocky8-mini test]# cat excute.sh 
#!/bin/bash
Path=/root/*
for i in $Path; do
[[ "$i" == *"."sh ]] && chmod +x "$i" || echo "$i is not script file!"
done

6、编写脚本 nologin.sh和 login.sh,实现禁止和允许普通用户登录系统

1
2
3
4
5
6
7
[root@Rocky8-mini test]# cat nologin.sh 
#!/bin/bash
[ `id -u $1` -ge 1000 ] && usermod "$1" -s /sbin/nologin

[root@Rocky8-mini test]# cat login.sh
#!/bin/bash
[ `id -u $1` -ge 1000 ] && usermod "$1" -s /bin/bash

1.13 接收键盘的输入—read命令

​ 使用read命令将输入值分配给一个或多个shell变量。read 命令从标准输入读取值,给每个单词分配一个变量,当所有单词分配完成还剩余变量没有分配,则默认的值为系统内置变量REPLY

1
2
3
4
5
6
7
8
read [options] [name ...]

常见选项:
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d '字符' 输入结束符
-t N TIMEOUT为N秒

示例

1
2
3
4
5
6
7
8
9
10
11
[root@centos7 ~]# echo $REPLY

[root@centos7 ~]# read
TEST
[root@centos7 ~]# echo $REPLY
TEST

[root@centos7 ~]# read -p "Please input your name: " NAME
Please input your name: Tom
[root@centos7 ~]# echo $NAME
Tom

示例:read命令使用管道符时的注意点

1
2
3
4
5
6
7
8
Pipelines
A pipeline is a sequence of one or more commands separated by one of the control operators | or |&.

[root@Rocky8-mini ~]# echo Jerry | read NAME
[root@Rocky8-mini ~]# echo $NAME

[root@Rocky8-mini ~]# echo Jerry | { read NAME; echo $NAME; }
Jerry

实例:read和输入重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Rocky8-mini ~]# cat test.txt 
1 2
[root@Rocky8-mini ~]# read i j < test.txt;echo "i=$i,j=$j"
i=1,j=2

[root@Rocky8-mini ~]# echo 1 2 | read i j;echo "i=$i,j=$j"
i=,j=
[root@Rocky8-mini ~]# echo 1 2 | (read i j;echo "i=$i,j=$j")
i=1,j=2
[root@Rocky8-mini ~]# echo 1 2 | { read i j;echo "i=$i,j=$j"; }
i=1,j=2

# Each command in a pipeline is executed as a separate process (i.e., in a subshell).

实例:实现运维工作菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos7 ~]# cat /shell/work_menu.sh 
#!/bin/bash

echo -en "\E[1;$[RANDOM%7+31]m"
cat <<EOF
1) 备份数据库
2) 清理日志
3) 软件升级
4) 软件回滚
5) 删库跑路
EOF
echo -en '\E[0m'

read -p "Please input your choice(1-5): " MENU
[ $MENU -eq 1 ] && echo "备份数据库"
[ $MENU -eq 2 ] && echo "清理日志"
[ $MENU -eq 3 ] && echo "软件升级"
[ $MENU -eq 4 ] && echo "软件回滚"
[ $MENU -eq 5 ] && echo "删库跑路"

二、bash shell 配置文件

2.1 按照生效范围划分

全局配置:针对所有用户生效

1
2
3
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc

个人配置:仅对指定用户生效

1
2
~/.bash_profile
~/.bashrc

2.2 shell 登录两种方式分类

2.2.1 交互式登录

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

2.2.2 非交互式登录

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

2.3 按照功能划分

2.3.1 profile 类

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

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

功用:

(1)一般用于定义环境变量

(2)运行命令或脚本

2.3.2 bashrc 类

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

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

功用:

(1)定义命令别名和函数

(2)定义本地变量

2.4 编辑配置文件生效

修改profile和bashrc文件后使其生效的两种方法

(1)重新启动shell进程

(2)source | .

2.5 退出任务

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

功能:

(1)创建自动备份

(2)清除临时文件

练习

1、让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin

1
[root@centos7 ~]# echo "PATH=/usr/local/apache/bin:$PATH" >> /etc/profile

2、用户 root 登录时,将命令指示符变成红色,并自动启用如下别名:

rm=‘rm -i’
cdnet=‘cd /etc/sysconfig/network-scripts/’
editnet=‘vim /etc/sysconfig/network-scripts/ifcfg-eth0’
editnet=‘vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33 ’ (如果系统是CentOS7)

1
2
3
4
5
6
7
8
# 修改命令指示符颜色
[root@centos7 ~]#echo 'PS1="\[\e[1;31m\][\u@\h \W]\\$\[\e[0m\]"' >> ~/.bash_profile

# 定义别名
[root@centos7 ~]#vim ~/.bashrc
rm='rm -i'
cdnet='cd /etc/sysconfig/network-scripts/'
editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0'

3、任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”

1
2
3
4
5
6
# /etc/issue 登录前脚本提示
[root@Rocky8-mini ~]# vim /etc/issue
^[[1;31mHi, dangerous(login before)^[[0m
# /etc/motd 登录后脚本提示
[root@Rocky8-mini ~]# vim /etc/motd
^[[1;31mHi, dangerous!(login after)^[[0m

image-20221008224950634

4、编写生成脚本基本格式的脚本,包括作者,联系方式,版本,时间,描述等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 以下信息包括文件名、作者、版本、邮箱、站点、时间、描述
[root@centos7 ~]# cat .vimrc
autocmd BufNewFile *.sh exec ":call AddTitleForShell()"
function AddTitleForShell()
call append(0,"#!/bin/bash")
call append(1,"# **********************************************************")
call append(2,"# * Filename : ".expand("%:t"))
call append(3,"# * Author : Herbert Wu")
call append(4,"# * Version : 1.0")
call append(5,"# * Email : wuhaolam@163.com")
call append(6,"# * Website : wuhaolam.github.io")
call append(7,"# * Date : ".strftime("%Y-%m-%d"))
call append(8,"# * Description : ")
call append(9,"# **********************************************************")
endfunction

三、流程控制

3.1 条件选择

3.1.1 选择执行 if 语句

单分支

1
2
3
if 判断条件;then
条件成立时的代码
fi

多分支

1
2
3
4
5
if 判断条件;then
条件为真的代码
else
条件不成立时执行代码
fi

多分支

1
2
3
4
5
6
7
8
9
10
if 判断条件1;then
条件1成立时执行代码
elif 判断条件2;then
条件2成立时执行代码
elif 判断条件3;then
条件3成立时执行代码
···
else
以上条件都不成立时执行该语句
fi

3.1.2 条件判断 case 语句

语法格式

1
2
3
4
5
6
7
8
9
10
11
12
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
···
*)
默认分支
;;
esac

case 支持glob风格的通配符

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

练习

1、编写脚本 createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之。并设置初始密码为123456,显示添加的用户的id号等信息,在此新用户第一次登录时,会提示用户立即改密码,如果没有参数,就提示:请输入用户名

1
2
3
4
5
6
7
8
9
10
[root@Centos7-mini ~]# cat createuser.sh 
#!/bin/bash
USERNAME=$1

if [ $# -lt 1 ];then
echo "请输入用户名!"
exit 1
fi

id $USERNAME &> /dev/null && echo "该用户已经存在!" || { useradd $USERNAME;echo "123456" | passwd --stdin $USERNAME>/dev/null;echo "该用户信息为:`id $USERNAME`";echo "请修改您的密码对于第一次登录,若非第一次登录请忽略!" >> /home/$USERNAME/.bashrc; }

2、编写脚本 yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@Centos7-mini ~]# cat yesorno.sh 
#!/bin/bash

read -p "please input 'yes' or 'no': " INPUT


INPUT=`echo "$INPUT" | tr -s '[A-Z]' '[a-z]'`

case $INPUT in
y|yes)
echo "Your input is YES!"
;;
n|no)
echo "Your input is NO!"
;;
*)
echo "Input false! Please input yes or no!"
esac

3、编写脚本 filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,管道,其它文件类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Centos7-mini ~]# cat filetype.sh 
#!/bin/bash
read -p "Please input a file path: " ROUTE
TYPE=`ls -l -d "$ROUTE" | head -c1`
if [ "$TYPE" = "-" ];then
echo "$ROUTE is a common file!"
elif [ "$TYPE" = "d" ];then
echo "$ROUTE is a directory file!"
elif [ "$TYPE" = "p" ];then
echo "$ROUTE is a Pipe file!"
else
echo "$ROUTE is a other file!"
fi

4、编写脚本 checkint.sh,判断用户输入的参数是否为正整数

1
2
3
4
5
6
7
8
9
10
[root@Centos7-mini ~]# cat checkint.sh 
#!/bin/bash

read -p "请输入一个数字:" INTNUM

if [[ $INTNUM =~ ^[0-9]+$ ]];then
echo "$INTNUM 是一个正整数!"
else
echo "$INTNUM 不是一个正整数!"
fi

3.2 循环

3.2.1 循环 for

格式1
1
2
3
4
5
6
7
8
9
10
#方式1
for 变量名 in 列表;do
循环体
done

#方式2
for 变量名 in 列表
do
循环体
done

for 循环列表生成方式

  • 直接给出列表
  • 整数列表
1
2
{star..end}
$(seq [start [step]] end)
  • 返回列表的命令
1
$(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@, $*, $#
1
2
3
4
5
6
7
8
9
10
11
12
13
# 当不指定列表时,默认使用位置变量 $@
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
for NAME; do
echo $NAME
done
[root@Rocky8-mini ~]# bash -x test.sh Tom Jerry
+ for NAME in "$@"
+ echo Tom
Tom
+ for NAME in "$@"
+ echo Jerry
Jerry

示例:

1
2
3
4
5
# 计算 1+2+3+...+100 的结果
[root@Rocky8-mini ~]# sum=0;for i in {1..100};do sum=$[$sum+$i];done;echo "sum=$sum"
sum=5050
[root@Rocky8-mini ~]# seq -s+ 100 | bc
5050

示例:

1
2
3
4
5
# 100以内的奇数之和
[root@Rocky8-mini ~]# sum=0;for i in {1..100..2};do let sum=sum+i;done;echo "sum=$sum"
sum=2500
[root@Rocky8-mini ~]# seq -s+ 1 2 100 | bc
2500

实例:自定义输入数字计算之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos7 shell]#cat for_sum.sh 
#!/bin/bash
# **********************************************************
# * Filename : for_sum.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-09
# * Description : 接收命令行传来的参数计算之和
# **********************************************************
sum=0
for i in $*; do
sum=$[sum+i]
done
echo "sum=$sum"
[root@centos7 shell]#bash for_sum.sh 1 2 3 4 5
sum=15

实例:传递用户名参数自动创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos7 shell]#cat CreateUser.sh 
#!/bin/bash
# **********************************************************
# * Filename : CreateUser.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-09
# * Description : Create Users in Batchers
# **********************************************************
[ $# -eq 0 ] && { echo "USAGE: CreateUser.sh USERNAME ...";exit 1; }
for USERS;do
id $USERS &> /dev/null && echo "user $USERS already exists" || { useradd $USERS;echo "'$USERS' user created successfully!"; }
done
[root@centos7 shell]#bash CreateUser.sh Tom Jerry
'Tom' user created successfully!
'Jerry' user created successfully!

实例:批量创建用户并设置随机密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@centos7 shell]#cat UsersAndPasswords.sh 
#!/bin/bash
# **********************************************************
# * Filename : UsersAndPasswords.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-09
# * Description : Create Users in Branchers and Set Passwords
# **********************************************************
[ $# -eq 0 ] && { echo "USAGE: CreateUser.sh USERNAME ...";exit 1; }
for User;do
id $User &> /dev/null && { echo "user $User already exists";continue; }
useradd $User
PASS=`cat /dev/urandom | tr -dc '[:alnum:]' | head -c12`
echo "$PASS" | passwd --stdin $User &> /dev/null
echo "$User:$PASS" >> /backup/UsersAndPasswords.log
echo "'$User' user created successfully!"
done
[root@centos7 shell]#bash UsersAndPasswords.sh Tom Jerry
'Tom' user created successfully!
'Jerry' user created successfully!
[root@centos7 shell]#cat /backup/UsersAndPasswords.log
Tom:5OO4dZXKZSXl
Jerry:DFshykfzYUBL

实例:九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 方式一
[root@centos7 shell]#cat MultiplicationTable.sh
#!/bin/bash
for i in {1..9};do
for j in `seq $i`;do
echo -e "$j*$i=$[j*i]\t\c"
done
echo
done

# 方式二,printf 打印九九乘法表
[root@centos7 shell]#cat MultiplicationTable.sh
#!/bin/bash
for i in {1..9};do
for j in `seq $i`;do
printf "%s*%s=%d\t" $j $i $[i*j]
done
printf "\n"
done

# 方式三,打印多彩九九乘法表
[root@centos7 shell]#cat MultiplicationTable.sh
#!/bin/bash
for i in {1..9};do
for j in `seq $i`;do
echo -e "\E[1;$[RANDOM%7+31]m$j*$i=$[j*i]\t\c\E[0m"
done
echo
done

生产案例:将指定目录下文件所有文件后缀名改为bak后缀

1
2
3
4
5
6
7
8
[root@centos7 shell]#cat suffix.sh 
#!/bin/bash
DIR=/data/test
cd $DIR || { echo "Can not access to!";exit 1; }
for FILE in *;do
PRE=`echo $FILE | grep -Eo ".*\."`
mv $FILE ${PRE}bak
done

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@centos7 shell]#cat MoveFile.sh 
#!/bin/bash
# **********************************************************
# * Filename : MoveFile.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-10
# * Description : Directory file movement
# **********************************************************
PDIR=/data
for i in {1..365};do
DIR=`date -d "-$i day" +%F`
mkdir -p $PDIR/$DIR
cd $PDIR/$DIR/
for j in {1..10};do
touch ${RANDOM}.log
done
done

echo -e "\E[1;31mRelated directroy files have been created\nStart to move related directories and files\E[0m"

DIR=/data
cd $DIR || { echo "Can not access to!";exit1; }
for SUBDIR in *;do
YYYY_MM=`echo $SUBDIR | cut -d'-' -f1,2`
DD=`echo $SUBDIR | cut -d'-' -f3`
mkdir -p $DIR/$YYYY_MM/$DD
mv $SUBDIR ./$YYYY_MM/$DD
done

echo -e "\E[1;32mSuccessfully!\E[0m"

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

1
2
3
4
5
6
[root@centos7 shell]#cat ScanHost.sh 
#!/bin/bash
NET=192.168.0
for ID in {1..254};do
fping $NET.$ID &> /dev/null && echo "$NET.$ID is alived" | tee -a /tmp/ip_alived.log || echo "$NET.$ID is down"
done
格式2

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

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

示例:

1
2
3
4
5
6
7
8
# 1~100的和
[root@centos7 ~]#cat sum.sh
#!/bin/bash
for ((sum=0,i=1;i<=100;i++))
do
sum=$[sum+i]
done
echo "1~100的和为:$sum"

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 九九乘法表 
[root@centos7 ~]#cat /shell/MultiplicationTable.sh
#!/bin/bash
# **********************************************************
# * Filename : MultiplicationTable.sh
# * Author : Herbert
# * Version : 1.2
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-10
# * Description : 9*9 multiplication table
# **********************************************************
for ((i=1;i<=9;i++));do
for ((j=1;j<=i;j++));do
echo -e "\E[1;$[RANDOM%7+31]m$j*$i=$[j*i]\t\c\E[0m"
done
echo
done

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 打印等腰三角形
[root@centos7 shell]#cat EquicruralTriangle.sh
#!/bin/bash
# **********************************************************
# * Filename : EquicruralTriangle.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-11
# * Description : print equicrural triangle
# **********************************************************
read -p "Please enter the number of rows of triangle: " LINE

for ((i=1;i<=LINE;i++));do
for ((j=1;j<=LINE-i;j++));do
echo -e ' \c'
done
for ((k=1;k<=2*i-1;k++));do
echo -e "\E[1;$[RANDOM%7+31]m*\E[0m\c"
done
echo
done

image-20221011204213932

示例:

1
2
# 生成进度条
[root@centos7 shell]#for ((i=0;i<=100;i++));do printf "\e[4D%3d%%" $i;sleep 0.1s;done

练习(for实现)

1、判断/var/目录下所有文件的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
cd /var
for FILE in /var/*;do
TYPE=`ls -l -d "$FILE" | head -c1`
case "$TYPE" in
"-")
echo "$FILE is a test file!"
;;
"b")
echo "$FILE is a Block file!"
;;
"c")
echo "$FILE is a Character file!"
;;
"l")
echo "$FILE is a Link file!"
;;
"p")
echo "$FILE is a Pipe file!"
;;
"d")
echo "$FILE is a Directory file!"
;;
"s")
echo "$FILE is a Socket file!"
;;
*)
echo "$FILE is a other file!"
;;
esac
done

2、添加10个用户user1-user10,密码为8位随机字符

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
for i in {1..10};do
NewUser="user$i"
id "$NewUser" &> /dev/null && { echo "$NewUser is exist!";continue; }
useradd "$NewUser"
Password=`cat /dev/urandom | tr -dc [[:alnum:]] | head -c8`
echo "$Password" | passwd --stdin "$NewUser" > /dev/null
done
echo -e "\E[1;32mUsers create Successfully!\E[0m"

3、/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename start

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Centos6-mini ~]# cat test.sh 
#!/bin/bash
set -e
set -u
cd /etc/rc.d/rc3.d/
for FILE in `ls`;do
Character=`echo "$FILE" | head -c1`
if [ "$Character" = 'K' ];then
echo "$FILE stop"
else
echo "$FILE start"
fi
done

4、编写脚本,提示输入正整数n的值,计算1+2+…+n的总和

1
2
3
4
5
6
7
8
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
read -p "Please input a positive integer: " N
sum=0
for i in `seq $N`;do
let sum=$sum+$i
done
echo "1+..+$N equal is $sum"

5、计算100以内所有能被3整除的整数之和

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]#cat test.sh 
#!/bin/bash
sum=0
for i in echo {3..100};do
RESULT=$[$i%3]
if [ "$RESULT" -eq 0 ];then
let sum=$sum+$i
fi
done
echo -e "\E[1;32m100以内所有能被3整除的整数之和为: $sum\E[0m"

6、在/testdir目录下创建10个html文件,文件名格式为数字N(从1到10)加随机8个字母,如:1AbCdeFgH.html

1
2
3
4
5
6
7
8
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
for i in `seq 10`;do
[ ! -d /testdir ] && mkdir /testdir
cd /testdir
RandomLetters=`cat /dev/urandom | tr -dc [[:alpha:]] | head -c8`
touch "$i$RandomLetters.html"
done

7、猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半多一个。到第10天早上想再吃时,只剩下一个桃子了。求第一天共摘了多少?

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
sum=1
for (( i=2; i<=10; i++ ))
do
sum=$[($sum+1)*2]
done
echo "The monkey picked $sum peaches on the first day"
[root@Rocky8-mini ~]#bash test.sh
The monkey picked 1534 peaches on the first day

3.2.2 循环 while

格式:

1
2
3
4
5
6
7
8
9
10
11
12
while CONDITION; do
循环体
done

# 无限循环
while true;do
循环体
done

while :;do
循环体
done

实例:磁盘检查并发出邮件警告

1
2
3
4
5
6
7
8
9
10
11
# 实现报警功能需提前设置好报警功能
[root@centos7 ~]#cat while_check_disk.sh
#!/bin/bash
WARNING=80
while true;do
CAPACITY=`df -Th | awk '/\/dev\/sd/{print $6}' | cut -d"%" -f1 | sort -nr | head -n1`
if [ $CAPACITY -ge $WARNING ];then
echo "Disk will be full from `hostname -I`" | mail -s "Linux disk check" wuhaolam@163.com
fi
sleep 10
done

练习

1、编写脚本,求100以内所有正奇数之和

1
2
3
4
5
6
7
8
9
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
i=1
sum=0
while [ $i -le 100 ];do
sum=$[sum+i]
i=$[i+2]
done
echo "The sum of odd numbers(1~100) is $sum"

2、编写脚本,提示请输入网络地址,如:192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各多少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@Rocky8-mini ~]# cat host.sh
#!/bin/bash
NET=192.168.0
SUB=1
UP=0
DOWN=0
while [ $SUB -lt 255 ];do
IP=$NET.$SUB
if `fping $IP &> /dev/null`;then
echo "$IP is alived"
UP=$[$UP+1]
else
echo "$IP is down"
DOWN=$[$DOWN+1]
fi
SUB=$[$SUB+1]
done
echo "The online host has $UP;offline host has $DOWN"

3、编写脚本,打印九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
i=1
while [ $i -le 9 ];do
j=1
while [ $j -le $i ];do
echo -e -n "\E[1;$[$RANDOM%7+31]m$j*$i=$[$j*$i]\t\E[0m"
j=$[$j+1]
done
echo
i=$[$i+1]
done

4、编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
i=1
MAX=0
MIN=0
while [ $i -lt 11 ];do
NUM=$RANDOM
[ $i -eq 1 ] && MIN=$NUM
echo -n "$NUM "
if [ $NUM -gt $MAX ];then
MAX=$NUM
fi
if [ $NUM -lt $MIN ];then
MIN=$NUM
fi
i=$[$i+1]
done
echo
echo "The largest number is $MAX,smallest number is $MIN"

5、编写脚本,实现打印国际象棋棋盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
i=1
while [ $i -le 8 ];do
j=1
value1=$[$i%2]
if [ $value1 -ne 0 ];then
while [ $j -le 8 ];do
value2=$[$j%2]
if [ $value2 -ne 0 ];then
echo -e -n "\E[1;47m \E[0m"
else
echo -e -n "\E[1;41m \E[0m"
fi
j=$[$j+1]
done
echo
else
while [ $j -le 8 ];do
value2=$[$j%2]
if [ $value2 -eq 0 ];then
echo -e -n "\E[1;47m \E[0m"
else
echo -e -n "\E[1;41m \E[0m"
fi
j=$[$j+1]
done
echo
fi
i=$[$i+1]
done

6、后续六个字符串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63是通过对随机数变量RANDOM随机执行命令: echo $RANDOM|md5sum|cut -c1-10后的结果,请破解这些字符串对应的RANDOM值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
for words in efbaf275cd 4be9c40b8b 44b2395c46 f8c8873ce0 b902c16c8b ad865d2f63;do
while true;do
INIT_VALUE=$RANDOM
PW=`echo $INIT_VALUE | md5sum | cut -c1-10`
if [ "$words" = "$PW" ];then
echo "$words initial value is $INIT_VALUE"
break
fi
done
done
[root@Rocky8-mini ~]# bash test.sh
efbaf275cd initial value is 15000
4be9c40b8b initial value is 12000
44b2395c46 initial value is 9000
f8c8873ce0 initial value is 6000
b902c16c8b initial value is 3000
ad865d2f63 initial value is 1000

3.2.3 循环 until

格式:

1
2
3
4
5
6
7
8
9
10
11
12
until CONDITION; do
循环体
done

# 无限循环
until false; do
循环体
done

说明:
进入条件 CONDITION 为 false
推出条件 CONDITION 为 true

示例

1
2
[root@Rocky8-mini ~]# sum=0;i=1;until [ $i -gt 100 ];do let sum=sum+i; let i++;done;echo $sum
5050

3.2.4 continue 语句

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

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
for i in {1..5};do
if [ $i -eq 3 ];then
continue
echo $i
else
echo $i
fi
done
[root@Rocky8-mini ~]# bash test.sh
1
2
4
5

3.2.5 break 语句

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

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
for i in {1..5};do
if [ $i -eq 3 ];then
break
echo $i
else
echo $i
fi
done
[root@Rocky8-mini ~]# bash test.sh
1
2

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[root@centos7 shell]#cat menu.sh 
#!/bin/bash
# **********************************************************
# * Filename : menu.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-18
# * Description : order dishes
# **********************************************************
sum=0
COLOR1='echo -e \E[1;31m'
COLOR2='echo -e \E[1;32m'
END='\E[0m'

while true;do
echo -e "\E[1;33m\c"
cat << EOF
1) 小龙虾
2) 葱油鸡
3) 老母鸡汤
4) 蒸蛋
5) 烤肠
6) 结账
EOF
echo -e "\E[0m"

read -p "Please select your dishes: " MENU
case $MENU in
1|4)
$COLOR1'prices: $10'$END
let sum=sum+10
;;
3|5)
$COLOR1'prices: $20'$END
let sum=sum+20
;;
2)
$COLOR1'prices: $30'$END
let sum=sum+30
;;
6)
$COLOR2"All the prices are: \$$sum"$END
break
;;
*)
echo "Please input correct sequence!"
;;
esac
done

实例:猜大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
NUM=$[RANDOM%10]

while read -p "Please input your number[0~9]: " INPUT ;do
if [ $INPUT -eq $NUM ];then
echo "Congratulations on your correct guess!"
break
elif [ $INPUT -gt $NUM ];then
echo "Too big, again please!"
else
echo "Too small, again please!"
fi
done

3.2.6 shift命令

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

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

示例1

1
2
3
4
5
6
7
8
9
10
11
12
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
until [ -z "$1" ];do
echo "$1"
shift
done
[root@Rocky8-mini ~]# bash test.sh a b c d e
a
b
c
d
e

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# shift 批量创建用户
[root@Rocky8-mini ~]# cat batch_create_user.sh
#!/bin/bash
while [ "$1" ];do
if id $1 &> /dev/null;then
echo $1 user is exist
else
useradd $1
echo "$1 user create successfully!"
fi
shift
done
[root@Rocky8-mini ~]# bash batch_create_user.sh Tom Jerry
Tom user create successfully!
Jerry user create successfully!

练习

1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并退出脚本

1
2
3
4
5
6
[root@Rocky8-mini ~]#cat test.sh 
#!/bin/bash
while true;do
who | grep "hacker" >> /var/log/login.log && break
sleep 3
done

2、用文件名做为参数,统计所有参数文件的总行数

1
2
3
4
5
6
7
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
[ $# -eq 0 ] && echo "Please input at lease one parameter: '/etc/fstab ...'"
for FILE in "$@";do
Lines=`cat $FILE | wc -l`
echo "The number of lines for '$FILE' is $Lines."
done

3、用二个以上的数字为参数,显示其中的最大值和最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
[ $# -le 2 ] && { echo "Please input at least two parameters!";exit 1; }

MAX=$1
MIN=$1
for NUM in "$@";do
if [ $MAX -lt $NUM ];then
MAX=$NUM
fi
if [ $MIN -gt $NUM ];then
MIN=$NUM
fi
done
echo "'$*',the MAX number is $MAX; the MIN number is $MIN."

3.2.7 while 特殊用法 while read

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

1
2
3
4
5
while read LINE;do
loop body
done < /PATH/SOMEFILE

# 依次读取/PATH/SOMEFILE文件中的每一行,且将行赋值给变量LINE

示例

1
2
3
4
5
6
7
8
[root@Rocky8-mini ~]# echo Jerry | read x;echo $x

[root@Rocky8-mini ~]# echo Jerry | { read x;echo $x; }
Jerry
[root@Rocky8-mini ~]# echo Jerry | while read X;do echo $X;done
Jerry
[root@Rocky8-mini ~]# echo Tom Jerry Snoopy | while read X Y Z; do echo $X $Y $Z;done
Tom Jerry Snoopy

实例:磁盘检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos7 shell]#cat Check_Disk_Capacity.sh 
#!/bin/bash
# **********************************************************
# * Filename : Check_Disk_Capacity.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-18
# * Description : check disk capacity and send email warnings
# **********************************************************
WARNING=80
MAIL=wuhaolam@163.com

df | awk -F" +|%" '/^\/dev\/sd/{print $1,$5}' | while read DEVICE UTILIZATION;do
if [ $UTILIZATION -ge $WARNING ];then
echo "$DEVICE will be full, Utilization: $UTILIZATION%" | mail -s "Linux Disk Warning" $MAIL
fi
done

实例:检查DOS攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos7 shell]#cat CheckLink.sh
#!/bin/bash
# **********************************************************
# * Filename : CheckLink.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-23
# * Description : Check the number of attack connections
# **********************************************************
WARNING=10
[ ! -e /tmp/deny_hosts.txt ] && touch /tmp/deny_hosts.txt
while true;do
ss -nt | sed -nr 's#.* ([0-9.]+):[0-9]+ *#\1#p' | sort | uniq -c |
while read COUNT IP;do
if [ $COUNT -ge $WARNING ];then
echo "$IP will be denied"
grep -q "$IP" /tmp/deny_hosts.txt || { echo $IP >> /tmp/deny_hosts.txt;iptables -A INPUT -s $IP -j REJECT; }
fi
done
break
done

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

1
2
3
4
5
6
7
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
while read LINE;do
if [[ "$LINE" =~ /sbin/nologin$ ]];then
echo "$LINE" | cut -d: -f1,3 --output-delimiter=:
fi
done < /etc/passwd

3.2.8 循环与菜单 select

格式

1
2
3
4
5
select NAME [in WORDS ...;]do COMMANDS;done

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

说明:

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

实例:餐馆点菜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@centos7 shell]#cat select_menu.sh 
#!/bin/bash
# **********************************************************
# * Filename : select_menu.sh
# * Author : Herbert
# * Version : 1.0
# * Email : wuhaolam@163.com
# * Website : wuhaolam.github.io
# * Date : 2022-10-25
# * Description : Use 'select' command accomplish order function
# **********************************************************
sum=0
PS3="Here is the menu.Please take you order(1-6): "
select MENU in 老母鸡汤 紫薯饭 毛豆烧鸡 竹笋鸡翅 蒸蛋 点菜结束;do
case $REPLY in
1)
echo "$MENU prince is ¥16"
let sum=sum+16
;;
2)
echo "$MENU prince is ¥3"
let sum=sum+3
;;
3)
echo "$MENU prince is ¥15"
let sum=sum+15
;;
4)
echo "$MENU prince is ¥12"
let sum=sum+12
;;
5)
echo "$MENU prince is ¥6"
let sum=sum+6
;;
6)
echo "点菜结束"
break
;;
*)
echo "Input error,please reselect!"
;;
esac
done
echo "A total of consumption: ¥$sum"

四、函数 function

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程

函数与shell程序形式上相似,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分

函数与shell程序的区别

  • shell程序在子shell中运行
  • 函数在当前shell中运行。故函数可以对shell中的变量进行修改

4.1 管理函数

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

帮助查看:help function

4.1.1 定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 形式一
func_name (){
... body ...
}

# 形式二
function func_name{
... body ...
}

# 形式三
function func_name (){
... body ...
}

4.1.2 查看函数

1
2
3
4
5
6
7
8
9
10
11
# 查看当前已定义的函数名
declare -F

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

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

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

4.1.3 删除函数

1
unset func_name

4.2 函数调用

函数的调用方式

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

调用:函数只有被调用才会执行,通过给定函数名调用函数,执行函数中的代码

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

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

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

Example:

1
2
3
4
5
6
7
8
[root@Rocky8-client yum.repos.d]#dir(){
> ls -l
> }
[root@Rocky8-client yum.repos.d]#dir
total 12
drwxr-xr-x 2 root root 4096 Oct 30 05:05 backup
-rw-r--r-- 1 root root 2081 Oct 29 21:29 docker-ce.repo.bak
-rw-r--r-- 1 root root 1079 Oct 29 07:59 yum.repo

Example:判断当前系统的主版本

1
2
3
4
5
[root@Rocky8-client ~]#Rocky_version() {
> sed -nr 's#.* ([0-9]+)\..*#\1#p' /etc/redhat-release
> }
[root@Rocky8-client ~]#Rocky_version
8

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

函数在使用前必须定义,因此将函数定义放在脚本开始部分。

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]#cat test.sh 
#!/bin/bash
hello (){
echo "program execution functions"
}
hello
echo "finished"
[root@Rocky8-mini ~]#bash test.sh
program execution functions
finished

Example:简单的初始化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
disable_selinux (){
sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
echo "SElinux has disabled!"
}
disable_firewalld (){
systemctl disable --now firewalld &> /dev/null
echo "Firewalld has disabled!"
}
set_PS1 (){
echo "PS1=\[\e[1;32m\][\u@\h \W]\\$\[\e[0m\]" > /etc/profile.d/ps1.sh
echo "Prompt has been modified!"
}

PS3="Select the operation you want to perform(1-5): "
MENU='禁用SELinux 关闭防火墙 修改提示符 以上都实现 退出'

select M in $MENU;do
case $REPLY in
1)
disable_selinux
;;
2)
disable_firewalld
;;
3)
set_PS1
;;
4)
disable_selinux
disable_firewalld
set_PS1
;;
5)
break
;;
*)
echo "Please enter correct number!"
;;
esac
done

4.2.3 使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数

函数文件名可以任意选取,但最好与相关任务有某种联系

当函数文件载入shell,就可以再命令行或脚本中调用函数

若要改动函数,首先要unset命令从shell中删除函数,改动完毕后,再重新载入此文件

函数文件实现的过程:

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

(2)在shell脚本或交互式shell中调用函数文件

示例

1
2
3
4
5
6
7
8
[root@Rocky8-mini ~]#cat functions 
#!/bin/bash
hello (){
echo "hello"
}
[root@Rocky8-mini ~]#. functions
[root@Rocky8-mini ~]#hello
hello

4.3 函数返回值

函数的执行结果返回值:

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

函数的退出状态码:

  • 默认取决于函数中执行的最后一条命令的退出状态码
  • 自定义退出状态码
    • return 从函数中返回,用最后状态命令决定返回值
    • return 0 无错误返回
    • return 1-255 有错误返回

4.4 环境函数

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

定义环境函数:

1
2
export -f function_name
declare -xf function_name

查看环境函数:

1
2
export -f
declare -xf

4.5 函数参数

函数可以接受参数

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

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 打印一个进度条
[root@Rocky8-mini ~]#cat test.sh
#!/bin/bash
function progress_bar() {
local char="$1"
local number="$2"
local c
for ((c=0; c<number; c++)); do
printf "$char"
done
}

COLOR=32
# 声明整数型变量end,赋值为50
declare -i end=50
for ((i=1; i<=end; i++)); do
printf "\e[1;${COLOR}m\e[80D["
progress_bar "#" $i
progress_bar " " $[end-i]
printf "] %3d%%\e[0m" $((i*2))
sleep 0.1s
done

image-20221031191515701

4.6 函数变量

变量的作用域:

  • 普通变量:只在当前shell进程中有效
  • 环境变量:当前shell和子shell有效
  • 本地变量:函数的生命周期;函数结束时变量自动销毁

注意:

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

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

1
local NAME=VALUE

4.7 函数递归

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

递归特点:

  • 函数内部自己调用自己
  • 必须有结束函数的出口语句,防止死循环

Example:给定一个整数参数,求它的阶乘

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]#cat test.sh 
#!/bin/bash
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1

练习

1、编写函数,实现OS的版本判断

1
2
3
4
5
6
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
os_version() {
sed -nr '/PRETTY_NAME/s#.*"(.*)"#\1#p' /etc/os-release
}
os_version

2、编写函数,实现取出当前系统eth0的IP地址

1
2
3
4
5
6
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
IPaddress() {
ifconfig ens33 | awk '/\<inet\>/{print $2}'
}
IPaddress

3、编写函数,实现打印绿色OK和红色FAILED

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
Green_ok() {
echo -e "\E[1;32mOK\E[0m"
}
Red_failed() {
echo -e "\E[1;31mFAILED\E[0m"
}
Green_ok
Red_failed

4、编写函数,事先判断是否无位置参数,若没有,提示错误

1
2
3
4
5
6
7
8
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
Determine_positional_parameters() {
if [ $1 -eq 0 ];then
echo -e "\E[1;31mERROR! No positional parameters.\E[0m";exit
fi
}
Determine_positional_parameters $#

5、编写函数,实现两个数字做参数,返回最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
Determine_positional_parameters() {
if [ $1 -lt 2 ];then
echo -e "\E[1;31mERROR! Please input two positional parameters.\E[0m";exit
fi
}

MAX() {
if [ $1 -gt $2 ];then
echo "MAX=$1"
else
echo "MAX=$2"
fi
}

Determine_positional_parameters $#
MAX $@

6、编写服务脚本 ‘/root/bin/testsrv.sh’,完成如下要求

(1)脚本可接收参数:start,stop,restart,status

(2)如果参数非四者之一,提示使用格式后报错退出

(3)如果是 start:则创建 /var/lock/subsys/SCRIPT_NAME,并显示“启动成功”

​ 考虑:如果事先已经启动过一次,该如何处理?

(4)如果是 stop:则删除 /var/lock/subsys/SCRIPT_NAME,并显示“停止完成”

​ 考虑:如果已经停止了,该如何处理?

(5)如果是restart,则先stop,在start

​ 考虑:如果本来没有start,如何处理?

(6)如果是 status,当 /var/lock/subsys/SCRIPT_NAME 文件存在,则显示 “SCRIPT_NAME is running …”;当 /var/lock/subsys/SCRIPT_NAME 文件不存在,则显示“SCRIPT_NAME is stopped …”

(7)在所有模式下禁止启动该服务,可用 chkconfig 和 service 命令管理

说明:SCRIPT_NAME 为当前脚本名

1
2


7、编写脚本 /root/bin/copycmd.sh

(1)提示用户输入一个可执行命令名称
(2)获取此命令所依赖到的所有库文件列表
(3)复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下

​ 如:/bin/bash ==> /mnt/sysroot/bin/bash

​ /usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
(4)复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2

(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

1
2


8、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,所以又被称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2),利用函数,求n阶斐波那契数列

1
2


9、汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤

1
2


五、其它脚本相关工具

5.1 信号捕捉 trap

trap 命令可以捕捉信号,修改信号原来的功能,实现自定义功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@Rocky8-mini ~]# trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
## '信号'可写全称;也可不写前三个字母,写简称;或写数字法描述(例:SIGINT INT 2)
# 进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '触发指令' 信号

# 忽略信号操作
trap '' 信号

# 列出自定义信号操作
trap -p

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

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ctrl+c 即2信号: SIGINT; ctrl+\ 即3信号: SIGQUIT
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
trap "echo 'Press ctrl+c or ctrl+\'" int quit
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

示例:当脚本正常或异常退出时,会执行finish函数来确保一定会执行某些关键性的操作

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
finish() {
echo "finish_`date +%F_%T`" | tee -a /root/finish.log
}
trap finish exit
while true;do
echo running
sleep 1
done

5.2 创建临时文件 mktemp

mktemp 命令用于创建并显示临时文件,避免文件冲突导致源文件被覆盖

1
2
3
4
mktemp [OPTION]... [TEMPLATE]

Note:
TEMPLATE: filenameXXX, 至少三个X

常用选项:

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

示例:

1
2
3
4
5
6
7
[root@Rocky8-mini ~]# mktemp 
/tmp/tmp.YTJUtsVWbC
[root@Rocky8-mini ~]# mktemp /tmp/testXXXXX
/tmp/test4FWuR

[root@Rocky8-mini ~]# mktemp -d /tmp/testXXX
/tmp/testz2i

示例:实现文件垃圾箱

1
2
3
4
5
6
[root@Rocky8-mini ~]# cat test.sh
#!/bin/bash
DIR=`mktemp -d /tmp/trash-$(date +%F_%H)XXXXX`
mv $* $DIR
echo $* is move to $DIR
[root@Rocky8-mini ~]# alias rm=/root/test.sh

5.3 安装复制文件 install

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

选项:

1
2
3
4
-m MODE, 默认755
-o OWNER
-g GROUP
-d DIRNAME 目录

示例

1
2
3
4
5
6
7
[root@Rocky8-mini ~]# install -m 700 -o wh -g wh /etc/issue /tmp/issue
[root@Rocky8-mini ~]# ll /tmp/issue
-rwx------ 1 wh wh 23 Nov 5 09:23 /tmp/issue

[root@Rocky8-mini ~]# install -m 700 -o wh -g wh -d /tmp/testdir
[root@Rocky8-mini ~]# ll -d /tmp/testdir/
drwx------ 2 wh wh 6 Nov 5 09:24 /tmp/testdir/

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

expect 主要应用于自动化交互式操作的场景,通过expect处理交互的命令,可以将交互过程,如 ssh 登录,ftp 登录等写在脚本上,使之自动化完成。

示例:安装 expect 及 mkpasswd 工具使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@Rocky8-mini ~]# dnf -y install expect
[root@Rocky8-mini ~]# rpm -ql expect | head
/usr/bin/autoexpect
/usr/bin/dislocate
/usr/bin/expect
/usr/bin/ftp-rfc
/usr/bin/kibitz
/usr/bin/lpunlock
/usr/bin/mkpasswd
/usr/bin/passmass
/usr/bin/rftp
/usr/bin/rlogin-cwd

# -l 指定密码的长度 -d 指定生成的密码中数字出现的次数 -C 指定大写字母出现的次数
# 其余可参看 man mkpasswd
[root@Rocky8-mini ~]# mkpasswd -l 15 -d 3 -C 5
cT$jf76y1GGCAfu

expect 相关命令

  • spawn 启动新进程
  • expect 从进程接收字符串
  • send 用于向进程发送字符串
  • interact 允许用户交互
  • exp_continue 匹配多个字符串

单一分支模式语法

1
2
3
4
[root@Rocky8-mini ~]# expect
expect1.1> expect "Tom" {send "I am Jerry!"}
hi,Tom
I am Jerry!

多分支语法

1
2
3
4
5
6
7
[root@Rocky8-mini ~]# expect
expect1.1> expect "hi" {send "hi\n"} "hello" {send "hello\n"}
hihellohihello
hi
expect1.2> expect "hi" {send "hi\n"} "hello" {send "hello\n"}
hlhello
hello

示例1:非交互式复制文件

1
2
3
4
5
6
7
#!/usr/bin/expect
spawn scp /etc/os-release 192.168.119.166:/data
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "wuhaolam\n" }
}
expect eof

示例2:自动登录

1
2
3
4
5
6
7
#!/usr/bin/expect
spawn ssh 192.168.119.166
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "wuhaolam\n" }
}
interact

示例3:expect 变量

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Rocky8-mini ~]# cat expect 
#!/usr/bin/expect

set ip 192.168.119.166
set user root
set password wuhaolam
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n"; exp_continue }
"password" { send "$password\n" }
}
interact

示例4:expect 位置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@Rocky8-mini ~]# cat expect 
#!/usr/bin/expect

set user [lindex $argv 0]
set ip [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@Rocky8-mini ~]# ./expect root 192.168.119.166 wuhaolam

示例5:expect 执行多个命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@Rocky8-mini ~]# cat expect 
#!/usr/bin/expect

set user [lindex $argv 0]
set ip [lindex $argv 1]
set password [lindex $argv 2]

spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n"; exp_continue }
"password" { send "$password\n" }
}

expect "]#" { send "useradd Tom\n" }
expect "]#" { send "id Tom\n" }
send "exit\n"
expect eof

示例6:shell 脚本调用 expect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
user=$1
ip=$2
password=$3
expect <<EOF
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n"; exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "userdel -r Tom\n" }
expect "]#" { send "id Tom\n" }
expect "]#" { send "exit\n" }
expect eof
EOF

六、数组 array

6.1 数据介绍

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

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

数组名和索引

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

6.2 声明数组

1
2
3
4
5
6
7
# 普通数组可不用事先声明,直接使用
declare -a ARRAY_NAME

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

注:普通数组和关联数组不可相互转换

6.3 数组赋值

1、一次只赋值一个元素

1
2
3
4
ARRAY_NAME[INDEX]=VALUE

[root@Rocky8-mini ~]# name[0]=Tom
[root@Rocky8-mini ~]# name[1]=Jerry

2、一次性赋值全部元素

1
2
3
4
5
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

[root@Rocky8-mini ~]# name=("Tom" "Jerry" "Snoopy")
[root@Rocky8-mini ~]# nums=({0..10})
[root@Rocky8-mini ~]# file=( *.sh )

3、只赋值特定元素

1
ARRAY_NAME=([0]="VAL1" [2]="VAL2" ...)

4、交互式赋值

1
2
3
4
5
6
read -a ARRAY

[root@Rocky8-mini ~]# read -a names
wang wu zhou zheng
[root@Rocky8-mini ~]# echo ${names[*]}
wang wu zhou zheng

6.4 显示所有数组

显示所有数组

1
2
3
4
5
6
7
8
9
10
11
12
13
declare -a

[root@Rocky8-mini ~]# declare -a
declare -a BASH_ARGC=()
declare -a BASH_ARGV=()
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="7")
declare -a BASH_LINENO=()
declare -a BASH_SOURCE=()
declare -ar BASH_VERSINFO=([0]="4" [1]="4" [2]="20" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
declare -a DIRSTACK=()
declare -a FUNCNAME
declare -a GROUPS=()
declare -a PIPESTATUS=([0]="2")

6.5 引用数组

1
2
3
4
5
6
7
8
${ARRAY_NAME[INDEX]}
# 如果省略[INDEX],表示引用下标为0的元素

[root@Rocky8-mini ~]# name=("Tom" "Jerry" "Snoopy")
[root@Rocky8-mini ~]# echo ${name}
Tom
[root@Rocky8-mini ~]# echo ${name[1]}
Jerry

引用数组中所有元素

1
2
3
4
5
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

[root@Rocky8-mini ~]# echo ${name[*]}
Tom Jerry Snoopy

数组的长度,即数组中所有元素的个数

1
2
3
4
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
[root@Rocky8-mini ~]# echo ${#name[*]}
3

6.6 删除数组

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

1
2
3
4
5
6
7
8
9
unset ARRAY[INDEX]

[root@Rocky8-mini ~]# unset name[1]
[root@Rocky8-mini ~]# echo ${name[*]}
Tom Snoopy
[root@Rocky8-mini ~]# echo ${name[1]}

[root@Rocky8-mini ~]# echo ${name[2]}
Snoopy

删除整个数组

1
2
[root@Rocky8-mini ~]# unset name
[root@Rocky8-mini ~]# echo ${name[*]}

6.7 数组数据处理

数组切片

1
2
3
4
5
6
7
8
9
10
${ARRAY[@]:offset:number}
offset # 要跳过的元素个数
number # 要取出的元素个数
${ARRAY[@]:offset} 取偏移量之后的所有元素

[root@Rocky8-mini ~]# nums=({1..10})
[root@Rocky8-mini ~]# echo ${nums[*]:2:3}
3 4 5
[root@Rocky8-mini ~]# echo ${nums[*]:2}
3 4 5 6 7 8 9 10

向数组中追加元素

1
2
3
4
5
6
7
8
ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

[root@Rocky8-mini ~]# echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10
[root@Rocky8-mini ~]# nums[${#nums[*]}]=11
[root@Rocky8-mini ~]# echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10 11

6.8 关联数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
declare -A ARRAY_NAME
ARRAY_NAME=([idex1]='val1' [idex2]='val2'...)
# 关联数组必须先声明再调用

[root@Rocky8-mini ~]# unset names
[root@Rocky8-mini ~]# declare -A names
[root@Rocky8-mini ~]# names[one]=Tom
[root@Rocky8-mini ~]# names[two]=Jerry
[root@Rocky8-mini ~]# echo ${names[one]}
Tom
[root@Rocky8-mini ~]# echo ${names[two]}
Jerry
[root@Rocky8-mini ~]# echo ${names[*]}
Jerry Tom

example:
root@ubuntu18-server:~# declare -A SUB
root@ubuntu18-server:~# SUB=([contry]='CN' [provice]='AnHui' [company]='Tsinglink' [dept]='IT')
root@ubuntu18-server:~# echo ${SUB[*]}
AnHui Tsinglink IT CN
root@ubuntu18-server:~# contry=${SUB[contry]};echo $contry
CN

6.9 示例

Example1:生成10个随机数保存于数组中,并找出最大值和最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos7 ~]# cat test.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
done

echo -e "\E[1;39mAll numbers: ${nums[*]}\E[0m"

for ((i=0;i<10;i++))
do
if [ ${nums[$i]} -gt $MAX ];then
MAX=${nums[$i]}
elif [ ${nums[$i]} -lt $MIN ];then
MIN=${nums[$i]}
fi
done

echo -e "The MAX value: \E[1;32m$MAX\E[0m\nThe MIN value: \E[1;32m$MIN\E[0m"

Example2:编写脚本,定义一个数组,数组中的元素对应的值是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和

1

七、字符串处理

7.1 字符串切片

7.1.1 基于偏移量取字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1、返回字符串变量var的字符的长度,一个汉字算一个字符
${#var}

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

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

4、取字符串的最右侧几个字符(冒号后有空白字符)
${var: -length}

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

6、先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容
${var: -length:-offset}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@Rocky8-mini ~]# str=abcdef汤姆

[root@Rocky8-mini ~]# echo ${#str}
8
[root@Rocky8-mini ~]# echo ${str:2}
cdef汤姆
[root@Rocky8-mini ~]# echo ${str:2:3}
cde
[root@Rocky8-mini ~]# echo ${str: -3}
f汤姆
[root@Rocky8-mini ~]# echo ${str:2:-3}
cde
[root@Rocky8-mini ~]# echo ${str: -2:-3}
-bash: -3: substring expression < 0
[root@Rocky8-mini ~]# echo ${str: -3:-2}
f
[root@Rocky8-mini ~]# echo ${str:-3:-2}
abcdef汤姆
[root@Rocky8-mini ~]# echo ${str: -5:-2}
def

7.1.2 基于模式取子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、${var#*word}
note:
word 可以是指定的任意字符,自左向右,查找var变量所存储的字符串中,第一次出现的word,删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右

2、${var##*word}
note:
同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左删右

3、${var%word*}
note:
word可以是指定的任意字符。功能:自右向左,查找var变量所存储的字符串中,第一次出现的word,删除字符串最后一个字符向左至第一次word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第一个word为界删右留左

4、${var%%word*}
note:
同上,但删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向左的最后一个word为界删右留左

示例

1
2
3
4
5
6
7
8
9
10
[root@Rocky8-mini ~]# file="var/log/messages"
[root@Rocky8-mini ~]# echo ${file%/*}
var/log
[root@Rocky8-mini ~]# echo ${file%%/*}
var
[root@Rocky8-mini ~]# url=http://www.baidu.com:8080
[root@Rocky8-mini ~]# echo ${url##*:}
8080
[root@Rocky8-mini ~]# echo ${url%%:*}
http

7.2 查找替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、${var/pattern/substr}
note:
查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之

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

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

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

7.3 查找删除

1
2
3
4
5
6
7
8
9
10
11
12
1、${var/pattern}
note:
删除var表示的字符串中第一次被pattern匹配到的字符串

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

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

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

7.4 字符大小写转换

1
2
3
4
5
1、${var^^}
把var中的所有小写字母转换成大写

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

八、高级变量

8.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=$str
var=${str:?expr} expr 输出至 stderr expr 输出至 stderr var=$str

示例

1
2
3
4
5
6
7
8
9
10
11
[root@Rocky8-mini ~]# title=ceo
[root@Rocky8-mini ~]# name=${title-wh}
[root@Rocky8-mini ~]# echo $name
ceo
[root@Rocky8-mini ~]# title=
[root@Rocky8-mini ~]# name=${title-wh}
[root@Rocky8-mini ~]# echo $name

[root@Rocky8-mini ~]# unset title
[root@Rocky8-mini ~]# name=${title-wh};echo $name
wh

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

1
2
3
4
5
6
7
8
9
10
11
12
13
declare [选项] 变量名

选项:
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 将变量中的大写字母都转换成小写字母
-u 声明变量为大写字母 将变量中的小写字母都转换成大写字母
-n make NAME a reference to the variable named by its value

8.3 变量间接引用

8.3.1 eval命令

​ eval命令对变量做两次扫描

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

8.3.2 间接变量引用

​ 如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得值value的行为

1
2
3
4
5
6
7
8
9
间接变量引用的两种方式
-- 方式一
eval tempvar=\$$variable 赋值
eval echo \$$variable
eval echo '$'$variable 显示值

-- 方式二
tempvar=${!variable} 赋值
echo ${!variable} 显示值

示例

1
2
3
4
[root@Rocky8-mini ~]# ceo=name
[root@Rocky8-mini ~]# name=wh
[root@Rocky8-mini ~]# echo ${!ceo}
wh

8.3.3 变量引用 reference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@Rocky8-mini ~]# cat test.sh 
#!/bin/bash
title=ceo
ceo=wh

declare -n ref=$title
# -R 该变量被定义并且是被引用的
[ -R ref ] && echo "reference"

echo ref=$ref
ceo=wang
echo ref=$ref
[root@Rocky8-mini ~]# bash test.sh
reference
ref=wh
ref=wang