# Shell 脚本编程及 Sed、AWK 进阶实践
# 1. Shell 脚本概述
# 1.1. 什么是 shell
Shell 是一个命令解释器,它在操作系统的最外层,负责直接与用户进行对话,将用户输入的命令翻译给操作系统,并将处理的结果输出至屏幕。

当然 shell 命令是存在 交互式、非交互式两种方式:
- 交互:日常使用,登陆、执行命令、退出;
- 非交互:直接读取某个文件,文件从头执行到尾即结束;
# 1.2 Shell 脚本 #号的使用
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2021-09-22 | |
#FileName : args.sh | |
#Description: The test script | |
#******************************************************************** |
# 1.3 自动添加 Shell 的首部
[root@web01 ~]# cat ~/.vimrc | |
autocmd BufNewFile *.sh exec ":call SetTitle()" | |
func SetTitle() | |
if expand("%:e") == 'sh' | |
call setline(1,"#!/bin/bash") | |
call setline(2,"#********************************************************************") | |
call setline(3,"#Author : XuYong") | |
call setline(4,"#Date : ".strftime("%Y-%m-%d")) | |
call setline(5,"#FileName : ".expand("%")) | |
call setline(6,"#Description: The test script") | |
call setline(7,"#********************************************************************") | |
call setline(8,"") | |
endif | |
endfunc | |
autocmd BufNewFile * normal G |
# 2. Shell 变量
# 2.1 什么是变量
变量是 shell 中传递数据的一种方法。简单理解,就是用一个固定的字符串去表示不固定的值,便于后续引用。
# 2.2 变量命名规范
- 变量定义命名:大写小写字母、下划线组成,尽量字母开头。
- 变量定义语法:变量名 = 变量值,等号是赋值,需要注意:等号两边不能有空格,其次定义的变量不要与系统命令出现冲突。参考如下定义变量方式:
ip=10.0.0.100 | |
Hostname_Ip=10.0.0.100 | |
hostname_IP=10.0.0.100 | |
system_cpu_load_avg1=w | awk '{print $1}' | |
system_cpu_load_avg5=w | awk '{print $2}' | |
system_cpu_load_avg15=w |awk '{print $3} |
# 2.3 变量定义方式
- 1. 用户自定义变量:人为定义变量名与变量的值。
- 2. 系统环境变量:保存的是和系统操作环境相关的数据,所有用户都可以使用。
- 3. 位置参数变量:向脚本中进行参数传递,变量名不能自定义,变量作用是固定的。
- 4. 特殊参数变量:是 Bash 中已经定义好的变量,变量名不能自定义,变量作用也是固定的。
# 2.3.1 用户自定义变量
1. 定义变量,变量名 = 变量值。不能出现 "- 横岗" 命名
[root@web01 ~]# var="hello shell" #定义变量有空格必须使用双引号 |
2. 引用变量,$ 变量名 或 $
[root@web01 ~]# echo $var | |
hello shell | |
[root@web01 ~]# echo $var_log | |
[root@web01 ~]# echo ${var}_log | |
hello shell_log |
3. 注意事项,引用变量时注意事项,"" 双引号属于弱引用,'' 单引号属于强引用
[root@web01 ~]# var2=Iphone | |
[root@web01 ~]# echo "$var2 is good" | |
Iphone is good | |
[root@web01 ~]# echo '$var2 is good' | |
$var2 is good | |
# 如果有变量的情况下,建议增加双引号; | |
# 如果存在特殊的字符,不希望被解析,这个时需要使用 '' 或者转移字符; |
# 2.3.2 系统环境变量
1. 使用系统已定义好的环境变量
[root@web01 ~]# cat env.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : env.sh | |
#Description: The test script | |
#******************************************************************** | |
echo "用户的家目录: $HOME" | |
echo "当前主机名是: $HOSTNAME" | |
echo "当前所在目录: $PWD" | |
[root@web01 ~]# sh env.sh | |
用户的家目录: /root | |
当前主机名是: web01 | |
当前所在目录: /root | |
# 场景:脚本只能 root 用户运行,非 root 拒绝运行; |
2. 人为定义环境变量:export 变量,将自定义变量转换成环境变量;
[root@web01 ~]# var2=hello | |
[root@web01 ~]# echo $var2 | |
hello | |
[root@web01 ~]# cat env.sh | |
#!/bin/bash | |
echo "$var2" | |
#执行 a.sh 时,会使用另一个 bash 去执行,就访问不到 $VAR1 的值 | |
[root@web01 ~]# sh env.sh | |
#将变量转为环境变量 | |
[root@web01 ~]# export var2=hello | |
[root@web01 ~]# sh env.sh | |
hello |
# 2.3.3 位置参数变量
位置参数顾名思义,就是传递给脚本参数的位置,例如给一个脚本传递一个参数,我们可以在 Shell 脚本内部获取传入的位置参数,获取参数的格式为:$n。n 代表一个数字。
例如传递给脚本的第一个参数就为 $1,第 2 个参数就为 $2, 以此类推……,其中 $0 为该脚本的名称。
1. 编写脚本
[root@web01 ~]# cat args.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : args.sh | |
#Description: The test script | |
#******************************************************************** | |
echo "#当前shell脚本的文件名: $0" | |
echo "#第1个shell脚本位置参数:$1" | |
echo "#第2个shell脚本位置参数:$2" | |
echo "#第3个shell脚本位置参数:$3" |
2. 执行脚本(发现第一个参数 11、第二个参数 22、第三个参数 33、脚本名称 args.sh)
[root@web01 ~]# sh args.sh 11 22 33 | |
#当前 shell 脚本的文件名: args.sh | |
#第 1 个 shell 脚本位置参数:11 | |
#第 2 个 shell 脚本位置参数:22 | |
#第 3 个 shell 脚本位置参数:33 |
# 2.3.4 特殊参数变量
特殊参数:
- $#:传递给脚本或函数的参数个数总和;
- $*:传递给脚本或函数的所有参数,当被双引号 "" 包含时,所有的位置参数被看做一个字符串
- $@:传递给脚本或函数的所有参数,当被双引号 "" 包含时,每个位置参数被看做独立的字符串
- $?:上个命令的退出状态,或函数的返回值,0 为执行成功,非 0 则为执行失败
[root@web01 ~]# cat args2.sh
#!/bin/bash
#********************************************************************
#Author : XuYong
#Date : 2025-09-17
#FileName : args2.sh
#Description: The test script
#********************************************************************
echo "第一个参数为: $1"
echo" 第二个参数为: $2"
echo" 脚本名称为: $0"
echo" 脚本接受参数总数为: $#"
curl -I baidu.com &>/dev/null
echo "运行命令的状态为:$?"
echo "脚本的 ID 为:$$"
echo"$* 的结果为:@"
echo "=========================="
echo "$* 循环接收的结果"
for i in "$*";
do
echo $i
done
echo "$@ 循环接收的结果"
for j in "$@";
do
echo $j
done
2.执行脚本
```bash
[root@web01 ~]# sh args2.sh jenkins docker kubernetes
第一个参数为: jenkins
第二个参数为: docker
脚本名称为: args2.sh
脚本接受参数总数为: 3
运行命令的状态为:0
脚本的ID为:12989
$* 的结果为:jenkins docker kubernetes
$@ 的结果为:jenkins docker kubernetes
==========================
$* 循环接收的结果
jenkins docker kubernetes
$@ 循环接收的结果
jenkins
docker
kubernetes
# 2.3.5 参数场景示例
需求 1:通过位置变量创建 Linux 系统账户及密码,执行 var1.sh username password
[root@web01 var]# cat var1.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : var1.sh | |
#Description: The test script | |
#******************************************************************** | |
#$1 是执行脚本的第一个参数,$2 是执行脚本的第二个参数 | |
useradd $1 | |
echo "$2" | passwd --stdin $1 |
需求 2:通过位置变量创建 Linux 系统账户及密码,执行 var1.sh username password,控制最多传递两个参数。
[root@web01 var]# cat var1.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : var1.sh | |
#Description: The test script | |
#******************************************************************** | |
#通过 $# 控制用户传递参数的个数 | |
if [ $# -ne 2 ];then | |
echo "USAGE USER|PASSWD" | |
exit | |
fi | |
#$1 是执行脚本的第一个参数,$2 是执行脚本的第二个参数 | |
useradd $1 | |
echo "$2" | passwd --stdin $1 |
需求 3:通过位置变量创建 Linux 系统账户及密码,执行 var1.sh username password,控制最多传递两个参数,且必须是 root 身份;
[root@web01 var]# cat var1.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : var1.sh | |
#Description: The test script | |
#******************************************************************** | |
# 1. 判断执行者的身份 | |
if [ $UID -ne 0 ];then | |
echo "$0 Permission denied, Please Switch Root." | |
exit | |
fi | |
#1. 判断传递的参数总个数 -ne 不等于 | |
if [ $# -ne 2 ];then | |
echo "USAGE $0 [ UserName && Password]" | |
exit | |
fi | |
# 执行业务逻辑 | |
useradd $1 | |
echo "$2" | passwd --stdin $1 |
# 2.4 read 交互传递变量
除了自定义变量,以及系统内置变量,还可以使用 read 命令通过交互式方式传递变量。
| read 选项 | 选项含义 |
|---|---|
| -p | 打印信息 |
| -t | 限定时间 |
| -s | 不回显 |
| -n | 指定字符个数 |
1.read 示例语法
[root@web01 ~]# cat read-1.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : read-1.sh | |
#Description: The test script | |
#******************************************************************** | |
read -p "Login: " acc | |
read -s -p "Passwd: " pw | |
echo "account: $acc password: $pw" |
2.read -p 示例
[root@web01 read]# cat read-2.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : read-2.sh | |
#Description: The test script | |
#******************************************************************** | |
read -p "Login: " acc | |
read -p "Passwd: " passwd | |
echo "account: $acc password: $passwd" |
3.read -p -t -n -s 示例,限制用户输入密码超时 5s,密码密文,位数不能超过 6。
[root@web01 read]# cat read-2.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : read-2.sh | |
#Description: The test script | |
#******************************************************************** | |
read -p "Login: " acc | |
read -t 60 -n 6 -s -p "Passwd: " passwd | |
echo "account: $acc password: $passwd" |
# 2.4.1 场景 1 模拟登陆页面脚本
使用 read 模拟 Linux 登陆页面:
- 1. 如果输入用户为 root,密码为 123,则输出欢迎登陆;
- 2. 否则输出用户或密码错误;
[root@web01 read]# cat read-2.sh | |
#!/bin/bash | |
#******************************************************************** | |
#Author : XuYong | |
#Date : 2025-09-17 | |
#FileName : read-2.sh | |
#Description: The test script | |
#******************************************************************** | |
#!/bin/bash | |
#******************************************************************** | |
#Author : Oldxu | |
#Date : 2021-09-22 | |
#FileName : read-2.sh | |
#Description: The test script | |
#******************************************************************** | |
System=$(hostnamectl | awk '/Operating/' | awk -F ': ' '{print $NF}') | |
Kernel=$(hostnamectl | awk '/Kernel/' | awk -F '[: ]+' '{print $2}') | |
Kernel_version=$(hostnamectl | awk '/Kernel/' | awk -F '[: ]+' '{print $4}') | |
# 打印系统信息 | |
echo $System | |
echo "${Kernel} ${Kernel_version} on an $(uname -m)" | |
# 交互输入 | |
read -p "$(hostname) login: " user | |
read -s -p "Password: " pass | |
echo "" | |
# 判断用户输入的用户名称 + 密码是否正确 | |
if [ $user == "root" -a $pass == "123" ];then | |
echo "欢迎 $user 用户登录节点.." | |
else | |
echo "用户密码错误...." | |
exit | |
fi |
# 2.4.2 场景 2 - 系统备份脚本
使用 read 编写一个备份脚本,需要用户传递 2 个参数,源和目标。
- 1. 提示用户,你需要备份的文件在哪个路径下;
- 2. 提示用户,你要备份到哪个目录;
- 3. 你确定要备份吗?[yes | no]
- 4. 如果输入 yes 就进行备份的操作,如果输入 no,则取消备份;
#!/bin/bash | |
cat <<EOF | |
################################################################################### | |
############################Backup Scripts ######################################## | |
################################################################################### | |
EOF | |
read -p "你要备份的源文件在哪里: " src_file | |
read -p "你要备份到哪个目录下: " dest_dir | |
read -p " 你要备份的源文件 $src_file 要备份到 $dest_dir 你确定吗? [ yes | no ]: " Action | |
if [ ${Action:=yes} == "yes" ];then | |
cp -rpv $src_file $dest_dir | |
else | |
exit | |
fi |
# 2.4.3 场景 3 - 探测主机存活脚本
使用 read 编写一个探测主机存活脚本,需要用户传递测试的 IP 地址。
#!/usr/bin/bash | |
read -p "请输入你要探测的IP地址: " Ip | |
ping -W 1 -c 1 $Ip &>/dev/null | |
if [ $? -eq 0 ];then | |
echo -e "\033[32m $Ip 通... \033[0m" | |
else | |
echo -e "\033[31m $Ip 不通... \033[0m" | |
fi |
# 2.4.4 场景 4 - 修改主机名称脚本
使用 read 编写一个修改系统主机名称脚本。
- 1. 打印当前主机名称;
- 2. 询问用户需要修改的新主机名称是什么;
- 3. 你是否要将 旧的名称 --- 新的名称 是:
- 4. 调用 shell 命令执行修改;
#!/usr/bin/bash | |
old_host=$(hostname) | |
echo "当前主机名称是 ${old_host} " | |
read -p "你要修改的新主机名称是: " new_host | |
read -p "你要将 $old_host --> $new_host: [ yes | no ]" Action | |
if [ $Action == "yes" ];then | |
hostnamectl set-hostname $new_host | |
fi |
# 2.5 Shell 变量删除
# 2.5.1 什么是变量删除
简单来说,就是在不改变原有变量的情况下,对变量值进行删除。
# 2.5.2 为什么要用变量删除
比如:我们需要对某个变量的值进行整数比对,但变量的值是一个小数。怎么办?
我们可以使用变量删除的方式,将小数位进行删除,然后在进行整数比对。
# 2.5.3 变量删除的几种方式
| 变量 | 说明 |
|---|---|
| $ | 从头开始匹配,最短删除 |
| $ | 从头开始匹配,最长删除 |
| $ | 从尾开始匹配,最短删除 |
| $ | 从尾开始匹配,最长删除 |
# 2.5.4 变量删除语法示例
示例 1:从前往后删除变量内容
#定义变量 | |
[root@client ~]# url=www.sina.com.cn | |
#输出变量的值 | |
[root@client ~]# echo ${url} | |
www.sina.com.cn | |
#从前往后,最短匹配 | |
[root@client ~]# echo ${url#*.} | |
sina.com.cn | |
#从前往后,最长匹配 | |
[root@client ~]# echo ${url##*.} | |
cn |
示例 2:从后往前删除变量内容
#定义变量 | |
[root@client ~]# url=www.sina.com.cn | |
#输出变量结果 | |
[root@client ~]# echo ${url} | |
www.sina.com.cn | |
#从后往前,最短匹配 | |
[root@client ~]# echo ${url%.*} | |
www.sina.com | |
#从后往前,最长匹配 贪婪匹配 | |
[root@client ~]# echo ${url%%.*} | |
www |
# 2.5.5 场景 1 - 提取内存百分比脚本
查看内存 / 当前使用状态,如果使用率超过 80% 则报警发邮件
- 1. 如何获取内存指标; free -m
- 2. 拿到使用率的百分比;free -m | awk '/^Mem/{print
2*100}'
- 3. 与定义的阈值做比对 80%;
- 4. 超过 80,则发送邮件,否则没有任何提示;
#!/usr/bin/bash | |
Free_use=$(free -m | awk '/^Mem/ {print $3/$2*100}') | |
if [ ${Free_use%.*} -ge 5 ];then | |
echo "你的内存已经满了.,当前已经使用了多少 ${Free_use}%" | |
else | |
echo "当前内存足够应用使用,目前已使用 ${Free_use}%" | |
fi |
# 2.5.6 场景 2 - 为不同版本系统安装源
写一个脚本,在 CentOS6 上运行则安装 6 的 epel,在 CentOS7 系统运行则安装 7 系统的 epel;
1. 判断系统的版本;cat /etc/redhat-release | awk '{print $(NF-1)}'
2. 根据不同的版本安装不同的源;
#!/usr/bin/bash | |
system_version=$(cat /etc/redhat-release | awk '{print $(NF-1)}') | |
# -ne 不等于 -eq 等于 | |
if [ ${system_version%%.*} -eq 7 ];then | |
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo &>/dev/null | |
echo "CentOS ${system_version} Epel OK" | |
fi | |
if [ ${system_version%%.*} -eq 6 ];then | |
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo | |
echo "CentOS ${system_version} Epel OK" | |
fi |
# 2.6 Shell 变量替换
# 2.6.1 什么是变量替换
简单来说,就是在不改变原有变量的情况下,对变量进行替换。
比如:原本输出 linux 是小写,可以将其转为 LINUX 大写,或者直接删除;
2.6.2 变量替换的方式
| 变量 | 说明 |
|---|---|
| $ | 替换变量内的旧字符串为新字符串,只替换第一个 |
| $ | 替换变量内的旧字符串为新字符串,全部替换 |
# 2.6.3 场景 1 - 替换 PATH 变量
如何替换 $PATH 中的 /bin/ 替换为 / BIN
[root@client ~]# echo $PATH | |
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin | |
#替换操作 | |
[root@client ~]# echo ${PATH//bin/BIN} |
# 2.6.4 场景 2 - 字符串替换脚本
需求:变量 string="Bigdata process is Hadoop, Hadoop is open source project" 执行脚本后,打印输出 string 变量,并给出用户以下选项:
- 1)、打印 string 长度
- 2)、删除字符串中所有的 Hadoop
- 3)、替换第一个 Hadoop 为 Linux
- 4)、替换全部 Hadoop 为 Linux
- 5)、用户输入数字 1|2|3|4,可以执行对应项的功能,输入 q|Q 则退出交互模式
#!/usr/bin/bash | |
string="Bigdata process is Hadoop, Hadoop is open source project" | |
echo ${string} | |
echo "" | |
cat <<EOF | |
1)、打印 string 长度 | |
2)、删除字符串中所有的 Hadoop | |
3)、替换第一个Hadoop为Linux | |
4)、替换全部Hadoop为Linux | |
EOF | |
while true | |
do | |
read -p "请输入你要操作的编号 [ 1 | 2 | 3 | 4 ]: " Action | |
if [ $Action -eq 1 ];then | |
echo ${#string} | |
fi | |
if [ $Action -eq 2 ];then | |
echo ${string//Hadoop/} | |
fi | |
if [ $Action -eq 3 ];then | |
echo ${string/Hadoop/Linux} | |
fi | |
if [ $Action -eq 4 ];then | |
echo ${string//Hadoop/Linux} | |
fi | |
done |
# 2.7 Shell 变量运算
# 2.7.1 什么是变量运算
其实就是我们以前学习过的 加 减 乘 除。
# 2.7.2 为什么需要变量运算
当我们需要开发一个计算器程序时,是不是就需要运算了?
当我们要对结果进行单位换算时,是不是就需要变量运算了?
# 2.7.3 变量运算实现的方式
通常整数运算有 expr、 [] 等方式,小数运算有 bc、awk 方式。
定义变量,使用 expr、 []、进行加减乘除。expr 必须空格隔开。
[root@client ~]# num1=10 | |
[root@client ~]# num2=20 | |
[root@client ~]# expr $num1 + $num2 | |
[root@client ~]# echo $(( $num1 + $num2 )) | |
[root@client ~]# echo $[ $num1 + $num2 ] |
# 2.7.4 场景 1 - 根据当前时间计算明年时间
根据系统时间,打印今年和明年时间。
[root@client ~]# echo "This is $(date +%Y) year" | |
This is 2019 year | |
[root@client ~]# echo "This is $(($(date +%Y)+1)) year" | |
This is 2020 year |
# 2.7.5 场景 2 - 计算今年还剩下多少周
需求 2:根据系统时间获取今年还剩下多少星期,已经过了多少星期。思路如下:
[root@client ~]# echo "今年已经过了 $(date +% j) days" | |
[root@client ~]# echo "今年已经过了 $(($(date +% j)/7 )) weeks" | |
[root@client ~]# echo "今年还剩下 $(((365 - $(date +% j) )/7 )) weeks" |
# 2.8 Shell 默认变量
- 场景一 $
- 若变量 var 为空时,则用 string 作为变量 var 的值;
- 若变量 var 不为空时,则直接使用变量 var 的值;
#1. 脚本示例 | |
[root@client ~]# cat var-default.sh | |
#!/usr/bin/bash | |
read -p "请输入你要安装的路径 [default: /usr/local/teleport]:" var | |
echo "${var:-/usr/local/teleport}" | |
echo "${var}" | |
#2. 变量 var 不为空示例 | |
[root@client ~]# sh var-default.sh | |
请输入你要安装的路径 [default: /usr/local/teleport]:/test | |
/test | |
/test | |
#3. 变量 var 为空示例 | |
[root@client ~]# sh var-default.sh | |
请输入你要安装的路径 [default: /usr/local/teleport]: | |
/usr/local/teleport |
- 场景二 $
- 若变量 var 为空时,则用 string 作为变量 var 的值,同时将 string 赋值给变量 var;
- 若变量 var 不为空时,则直接使用变量 var 的值;
#1. 脚本示例 | |
[root@client ~]# cat var-default.sh | |
#!/usr/bin/bash | |
read -p "请输入你要安装的路径 [default: /usr/local/teleport]:" var | |
echo "${var:=/usr/local/teleport}" | |
echo "${var}" | |
#2. 变量 var 不为空示例 | |
[root@client ~]# sh var-default.sh | |
请输入你要安装的路径 [default: /usr/local/teleport]:/test | |
/test | |
/test | |
#3. 变量 var 为空示例 | |
[root@client ~]# sh var-default.sh | |
请输入你要安装的路径 [default: /usr/local/teleport]: | |
/usr/local/teleport | |
/usr/local/teleport |
${var:=string} 很常用的一种用法是,判断某个变量是否赋值,没有的话则给它赋上一个默认值。
- 场景三 $
- 若变量 var 为空时,则 不替换或者说是替换成变量 var 的值,即空值(因为变量 var 此时为空,所以这两种说法是等价的);
- 若变量 var 不为空时,则将 string 作为 var 变量的值,同时将 string 复制给 var 变量;
#1. 脚本示例 | |
[root@client ~]# cat var-default.sh | |
#!/usr/bin/bash | |
read -p "请输入你要安装的路径 [default: /usr/local/teleport]:" var | |
echo "${var:+/usr/local/teleport}" | |
echo "${var}" | |
#2. 变量 var 不为空示例 | |
[root@client ~]# sh var-default.sh | |
请输入你要安装的路径 [default: /usr/local/teleport]:/test | |
/usr/local/teleport | |
/test | |
#3. 变量 var 为空示例 | |
[root@client ~]# sh var-default.sh | |
请输入你要安装的路径 [default: /usr/local/teleport]: |
