# 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 则为执行失败
  • :当前程序运行的PID:当前程序运行的 PID

[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 "\$@ 的结果为:@"
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 image2*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、image [] 等方式,小数运算有 bc、awk 方式。

定义变量,使用 expr、image []、进行加减乘除。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 默认变量
  1. 场景一 $
  • 若变量 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
  1. 场景二 $
  • 若变量 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} 很常用的一种用法是,判断某个变量是否赋值,没有的话则给它赋上一个默认值。

  1. 场景三 $
  • 若变量 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]:
此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Xu Yong 微信支付

微信支付

Xu Yong 支付宝

支付宝