shell编程进阶

一、数组相关

数组定义

注意:
  1 bash支持一维数组(不支持多维数组),并且没有限定数组的大小。数组元素的下标由0开始编号。
  2 获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0
  3 bash的数组支持稀疏格式(索引名称可以不连续)

数组定义
在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的语法格式:
  array_name=(value1 ... valuen)
  注意:
    基于元素的格式,主要有单行定义、多行定义、单元素定义、命令定义等多种样式
单行定义
array_name=(value0 value1 value2 value3)
多行定义
  array_name=(
  value0
  value1
  value2
  value3
  )

单元素定义
  array_name[0]=value0
  array_name[1]=value1
  array_name[2]=value2
批量多元素定义
  array_name=([0]="value1" [2]="value2")
注意:
  单元素定义的时候,可以不使用连续的下标,而且下标的范围没有限制。
命令定义就是value的值以命令方式来获取
  file_array=($(ls /tmp/))
查看数组所有元素
  echo ${array_name[@]}
命令定义
  file_array=$(ls *.sh)
注意:
  对于命令的数组创建来说,它只有一个元素
真正的命令方式 数组定义
  file_array=($(ls *.sh))

数组取值

基于索引找内容
  读取数组元素值可以根据元素的下标值来获取,语法格式如下:
     ${array_name[index]}
     ${array_name[@]:起始位置:获取数量}
注意:
  获取具体的元素内容,指定其下标值,从0开始
  获取所有的元素内容,下标位置写"@"或者"*"
获取数组索引
在找内容的时候,有时候不知道数组的索引都有哪些,我们可以基于如下方式来获取,数组的所有索引:
   ${!array_name[index]}
注意:
  获取所有的元素位置,下标位置写"@"或者"*"
获取数组长度的方法与获取字符串长度的方法相同,格式如下:
  ${#array_name[index]}
注意:
  获取具体的元素长度,指定其下标值,从0开始
  获取所有的元素个数,下标位置写"@"或者"*"
从系统中获取所有的数组
  declare -a

数组元素修改和删除

元素内容替换:
  array_name[index]=值
注意:
  在修改元素的时候,index的值一定要保持准确
元素部分内容替换,可以参考字符串替换格式:
  ${array_name[index]/原内容/新内容}
注意:
  默认是演示效果,原数组未被修改,如果真要更改需要结合单元素内容替换

将shell中的数组删除,可以使用unset来实现,主要有两种情况:删除单元素,删除整个数组。格式如下:
删除单元素
  unset array_name[index]
删除整个数组
  unset array_name
  注意: 支持同时删除多个数组名字

数组关联

定制索引数组 - 数组的索引是普通的数字
  declare -a array_name
  - 普通数组可以不事先声明,直接使用
定制关联数组 - 数组的索引是自定义的字母
  declare -A array_name
  - 关联数组必须先声明,再使用
关联数组的操作
[root@rocky9 ~]# score=([yingyu]="32" [yuwen]="67" [shuxue]="65")
[root@rocky9 ~]# declare -A | grep score
declare -A score='([yingyu]="32" [yuwen]="67" [shuxue]="65" )'
[root@rocky9 ~]# echo ${!score[@]}
yingyu yuwen shuxue
[root@rocky9 ~]# echo ${score[@]}
32 67 65

数组相关示例

信息统计

分别打印CPU 1min 5min 15min load负载值
cat cpu_load.sh
#!/bin/bash
# 功能:采集系统cpu负载信息
# 获取CPU负载情况,下面适合ubuntu系统,CentOS系统参数需要在进行调整
cpu_load=($(uptime | tr -s " " | cut -d " " -f 11-13 | tr "," " "))
# 信息输出
echo -e "\e[31m\t系统cpu负载信息\e[0m"
echo -e "\e[32m================================"
echo "CPU 1 min平均负载为: ${cpu_load[0]}"
echo "CPU 5 min平均负载为: ${cpu_load[1]}"
echo "CPU 15 min平均负载为: ${cpu_load[2]}"
echo -e "================================\e[0m"

服务管理

服务的管理动作有:
  "启动" "关闭" "重启" "重载" "状态"
服务的管理命令有:
  "start" "stop" "restart" "reload" "status"
选择不同的动作,输出不同的服务执行命令,格式如下:
  systemctl xxx service_name

vim service_manager.sh
#!/bin/bash
# 功能:定制服务管理的功能
# 定制普通数组
oper_array=(启动 关闭 重启 重载 状态)
# 定制关联数组
declare -A cmd_array
cmd_array=([启动]=start [关闭]=stop [重启]=restart [重载]=reload [状态]=status)
# 服务的操作提示
echo -e "\e[31m---------------服务的操作动作---------------
1: 启动 2: 关闭 3: 重启 4: 重载 5: 状态
--------------------------------------------"'\033[0m'
# 选择服务操作类型
read -p "> 请输入服务的操作动作: " oper_num
echo
echo -e "\e[31m您选择的服务操作动作是: ${oper_array[$oper_num-1]}\e[0m"
echo -e "\e[32m===============服务的执行动作===============
您即将对服务执行如下命令:
\tsystemctl ${cmd_array[${oper_array[$oper_num-1]}]} service_name
=========================================="'\033[0m'

二、分支逻辑

if分支语法

单路决策 - 单分支if语句
样式:
    if [ 条件 ]
    then
      指令
    fi
  特点:
    单一条件,只有一个输出

双路决策 - 双分支if语句
样式:
    if [ 条件 ]
    then
      指令1
    else
      指令2
    fi
  特点:
    单一条件,两个输出

多路决策 - 多分支if语句
样式:
if [ 条件 ]
    then
      指令1
    elif [ 条件2 ]
    then
      指令2
    else
      指令3
    fi
 特点:
   n个条件,n+1个输出

单行命令写法
  if [ 条件1 ]; then 指令1; elif [ 条件2 ]; then 指令2; ... ; else 指令n; fi
关键点解读:
  1 if 和 then 配套使用
  2 if 和末尾的 fi 顺序反写
shell的if语句中关于条件判断这块内嵌了如下几种测试表达式语句:
  [ 表达式 ] - 针对通用的判断场景
  [[ 表达式 ]] - 针对扩展的判断场景
  (( 命令 )) - (())代替let命令来测试数值表达式

多if示例

cat multi_branch_if.sh
#!/bin/bash
# 多分支if语句的使用场景
# 定制普通变量
gender="$1"
# 条件判断逻辑
if [ "${gender}" == "nan" ]
then
  echo "您的性别是 男"
elif [ "${gender}" == "nv" ]
then
  echo "您的性别是 女"
else
  echo "您的性别,我不知道"
fi

case分支语法

case 变量名 in
 值1)
   指令1
    ;;
 ...
 值n)
 指令n
    ;;
esac

注意:
  首行关键字是case,末行关键字esac
  选择项后面都有 )
  每个选择的执行语句结尾都有两个分号;

case分支示例

cat service_manager_case.sh
#!/bin/bash
# 功能:定制服务管理的功能
# 定制普通变量
service_ops="$1"
# 脚本内容的判断
case "${service_ops}" in
  "start")
    echo -e "\e[31m服务启动中...\e[0m";;
"stop")
    echo -e "\e[31m服务关闭中...\e[0m";;
"restart")
    echo -e "\e[31m服务重启中...\e[0m";;
"start")
    echo -e "\e[31m服务启动中...\e[0m";;
*)
    echo -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m";;
esac

二、循环逻辑

循环逻辑语法解析:
  关键字 [ 条件 ]
  do
    执行语句
  done
注意:
  这里的关键字主要有四种:
    for - 循环遍历一个元素列表
    while - 满足条件情况下一直循环下去
    until - 不满足条件情况下一直循环下去

for循环语法

场景:遍历列表
  for 值 in 列表
  do
   执行语句
  done
注意:
  ”for” 循环总是接收 “in” 语句之后的某种类型的字列表
  执行次数和list列表中常数或字符串的个数相同,当循环的数量足够了,就自动退出

for循环示例

定制循环
cat for_define_list.sh
#!/bin/bash
# 功能:定制列表 for循环
for i in {1..3}
do
 echo "列表元素: ${i}"
done

命令生成for循环、
cat for_cmd_list.sh
#!/bin/bash
# 功能:命令生成列表 for循环
for i in $(seq 1 3)
do
 echo "列表元素: ${i}"
done
脚本参数
cat for_arg_list.sh
#!/bin/bash
# 功能:脚本参数列表 for循环
for i in $@
do
 echo "列表元素: ${i}"
done
脚本执行后效果
[root@rocky9 ~]# /bin/bash for_arg_list.sh 1 2 3
列表元素: 1
列表元素: 2
列表元素: 3

对特定网段主机扫描检测脚本
cat for_host_check.sh
#!/bin/bash
# 功能:for批量检测网段主机存活情况
# 定制普通变量
netsub='10.0.0'
net_file='/tmp/host.txt'
# 保证文件可用
[ -f ${net_file} ] && > ${net_file}
# 定制批量检测网段主机状态逻辑
for ip in {1..254}
do
  # 测试主机连通性
  host_status=$(ping -c1 -W0.01 $netsub.$ip >/dev/null 2>&1 && echo "UP" || echo "DOWN")
  echo "$netsub.$ip 主机状态: $host_status" >> ${net_file}
done
# 信息输出
live_num=$(grep UP ${net_file} | wc -l)
unlive_num=$(grep DOWN ${net_file} | wc -l)
echo -e "\e[31m${netsub}.0 网段主机存活情况\e[0m"
echo "------------------------------------"
echo -e "\e[32m${netsub}.0 网段存活主机数量: ${live_num}\e[0m"
echo -e "\e[32m${netsub}.0 网段异常主机数量: ${unlive_num}\e[0m"2026-06-08 11:33:37 星期一

while循环语法

场景:只要条件满足,就一直循环下去
  while [ 条件判断 ]
  do
   执行语句
  done
注意:
  条件支持的样式 命令、[[ 字符串表达式 ]]、(( 数字表达式 ))
  true是一个特殊的条件,代表条件永远成立

while循环示例

数字相加
cat while_num_list.sh
#!/bin/bash
# 功能:while的输出5范围以内的数字
# 定制初始变量值
a=1
# 定制内容输出逻辑
while [ "${a}" -le 5 ]
do
  echo -n "${a} "
  # 每输出一次数据,数据值+1
  a=$((a+1))
done
echo
站点存活性检测
cat while_site_healthcheck.sh
#!/bin/bash
# 功能:定制站点的检测功能
# 定制普通变量
read -p "> 请输入待测试站点域名: " site_addr
# 持久检测站点状态
while true
do
  wget --spider -T5 -q -t2 ${site_addr} && echo "${site_addr} 站点正常" || echo "${site_addr} 站点异常"
  sleep 1
done


注意:参数解析
  --spider:检查一个 URL 是否可用,而不是下载文件。
  -T5:这个选项设置网络连接的超时时间为 5 秒。
  -q:这个选项使 wget 在“安静”或“静默”模式下运行,即不显示任何进度或错误信息。
  -t2:这个选项设置重试次数为 2。

until循环语法

场景:只要条件不满足,就一直循环下去
  until [ 条件判断 ]
  do
   执行语句
  done
注意:
  条件支持的样式 命令、[[ 字符串表达式 ]]、(( 数字表达式 ))
  false是一个特殊的条件,代表条件永远不成立

三、循环控制

所谓的流程控制,主要针对的是,当我们处于流程步骤执行的过程中,因为某些特殊的原因,不得不停止既定的操作进行步骤的调整,常见的临时调整场景如下:
continue控制
  - 满足条件的情况下,临时停止当前的循环,直接进入到下一循环
break控制
  - 满足条件的情况下,提前退出当前的循环
exit控制
  - 直接退出当前循环的程序
shift控制
  - 依次从循环列表中读取读取内容,并将读取的内容从列表中剔除

exit

exit在shell中是一个特殊的程序退出信号,不仅仅可以直接退出当前程序,还可以设定退出后的状态返回值,使用方式如下:
  exit num
注意:
  1 在脚本中遇到exit命令,脚本立即终止;终止退出状态取决于exit命令后面的数字
  2 如果exit后面无数字,终止退出状态取决于exit命令前面命令执行结果
示例
网段内主机地址的存活性探测
ping -c1 -W1 10.0.0.12 &> /dev/null && echo '10.0.0.12 is up' || (echo '10.0.0.12 is unreachable'; exit 1)
curl -s -o /dev/null baidu.com &> /dev/null && echo 'baidu.com is up' || (echo 'baidu.com is unreachable'; exit 7)
嵌套循环中exit退出程序示例
cat exit_multi_for.sh
#!/bin/bash
# 功能:exit退出脚本程序
# 外层循环遍历1-5
for var1 in {1..5}
do
  # 内层循环遍历a-d
  for var2 in {a..d}
  do
    # 判断退出条件,var1是2或者var2是c就退出内层循环
        if [ $var1 -eq 2 -o "$var2" == "c" ]
        then
            exit 111
        else
            echo "$var1 $var2"
        fi
   done
done

break

break命令是在处理过程中终止循环的一种简单方法。可以使用break命令退出任何类型的循环,包括for、while、until等。
break主要有两种场景的表现样式:
  单循环场景下,break是终止循环
    - 仅有一层 while 、for、until等
  嵌套循环场景下,break是可以终止内层循环和外层循环。
    - 存在多层while、for、until嵌套等

break语法格式:
for 循环列表
do
  ...
  break num
done
注意:
    单循环下,break就代表退出循环
    多循环下,break的num大于嵌套的层数,就代表退出循环

break脚本示例

break终止单层循环
cat break_single_while.sh
#!/bin/bash
# 功能:break退出单层循环
while true
do
    read -p "输入你的数字,最好在 1 ~ 5: " aNum
    case $aNum in
        1|2|3|4|5)
            echo "你的数字是 $aNum!"
        ;;
        *)
            echo "你选择的数字没在 1 ~ 5, 退出!"
            break
        ;;
    esac
done

多层循环下break退出内层循环
cat break_multi_in_while.sh
#!/bin/bash
# 功能:break退出内层循环
# 外层循环遍历1-5
for var1 in {1..5}
do
    # 内层循环遍历a-d
    for var2 in {a..d}
    do
        # 判断退出条件,var1是2或者var2是c就退出内层循环
        if [ $var1 -eq 2 -o "$var2" == "c" ]
        then
            break # break后面如果跟上2 则直接退出到外层循环
        else
            echo "$var1 $var2"
        fi
    done
done

continue

continue命令是在处理过程中跳出循环的一种简单方法。可以使用continue命令跳出当前的循环直接进入到下一个循环,包括for、while、until等
continue主要有两种场景的表现样式:
  单循环场景下,continue是跳出当前循环
    - 仅有一层 while 、for、until等
  嵌套循环场景下,continue是可以跳出内层循环和外层循环。
    - 存在多层while、for、until嵌套等
continue语法格式:
for 循环列表
do
  ...
  continue num
done
注意:
  单循环下,continue就代表跳出当前循环
  多循环下,continue的num就代表要继续的循环级别

continue脚本示例

cat continue_single_while.sh
#!/bin/bash
# 功能:continue退出单层循环
while true
do
    read -p "输入你的数字,最好在 1 ~ 5: " aNum
    case $aNum in
        1|2|3|4|5)
            echo "你的数字是 $aNum!"
        ;;
        *)
            echo "你选择的数字没在 1 ~ 5, 退出!"
            continue
        ;;
    esac
done
结果是:即使输出的数据不是我们想要的,也不会退出循环逻辑

四、函数

函数定义

  所谓的函数,本质上就是一段能够满足特定功能的代码块。一旦定义好函数代码后,我们就可以在脚本的很多位置随意的使用。
  定义功能代码块的动作叫 函数定义,使用函数代码的动作叫 函数调用。
函数的优势:
  1. 代码模块化,调用方便,节省内存
  2. 代码模块化,代码量少,排错简单
  3. 代码模块化,可以改变代码的执行顺序
定义函数:
样式1:标准格式
  function 函数名{   或  function 函数名(){
    函数体
  }
样式2:简约格式
  函数名() {
    函数体
  }
注意:
  function 的作用和 () 的作用是一样的,都是定义一个函数。
  函数的名称是自定义的,而且在脚本范围内必须唯一。
  函数体内是普通的能够正常执行的命令,命令的执行流程符合顺序逻辑。
调用函数:
  函数名
注意:
  函数名出现在任何位置,就代表在该位置调用函数内代码块的执行。
  函数名一般在函数定义后调用,否则的话会发生报错。

函数示例

cat function_simple_test.sh
#!/bin/bash
# 功能:简单函数的定义和调用
# 定制一个函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    echo "您输入的脚本参数是1个"
else
    Usage
fi

函数退出

样式1-默认的退出状态
  默认情况下,函数的退出状态是函数体内的最后一条命令的退出状态,可以通过 $? 来获取
式2-return定制状态返回值
  在函数体内部,通过return定制状态返回值的内容
  注意:
    return的状态返回值必须尽快使用,否则会被其他return的值覆盖
    return的状态返回值必须在 0-255,否则失效
1、默认退出状态
cat function_exit_status1.sh
#!/bin/bash
# 功能:函数默认状态返回值
# 定制成功运行的函数
ok_func() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}
# 定制一个运行失败的函数
err_func() {
    666666
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    err_func
    echo "错误函数的执行状态返回值: " $?
else
    ok_func
    echo "成功函数的执行状态返回值: " $?
fi

脚本执行效果
[root@rocky9 ~]# /bin/bash function_exit_status1.sh
脚本的使用帮助信息: xxx
成功函数的执行状态返回值:  0
[root@rocky9 ~]# /bin/bash function_exit_status1.sh aa
function_exit_status1.sh:行10: 666666: 未找到命令
错误函数的执行状态返回值:  127
[root@rocky9 ~]# lll; echo $?
bash: lll: 未找到命令
127
结果显示:
  对于异常的函数来说,默认的状态返回值有安全隐患

传参函数

定义函数:
  函数名() {
    函数体 ${变量名}
  }
注意:
  函数体内通过 ${变量名} 来实现函数体的功能通用性。
调用函数:
  函数名 参数
注意:
  函数在调用的时候,接收一些参数并传输到函数体内部。

参数函数示例

cat function_arg_input.sh
#!/bin/bash
# 功能:传参函数定义和调用
# 定制数据运算的函数
add_func() {
    echo $(( $1 + $2 ))
}
sub_func() {
    echo $(( $1 - $2 ))
}
mul_func() {
    echo $(( $1 * $2 ))
}
div_func() {
    echo $(( $1 / $2 ))
}

echo -n "4+3="; add_func 4 3
echo -n "4-3="; sub_func 4 3
echo -n "4*3="; mul_func 4 3
echo -n "4/3="; div_func 4 3

脚本传参

定义函数:
  函数名() {
    函数体 ${函数参数}
  }
调用函数:
  函数名 ${脚本参数}
脚本执行:
  /bin/bash /path/to/scripts.sh arg
注意:
  由于脚本内部调用脚本参数和函数体内调用函数参数都遵循位置变量的使用
  所以,一般情况下,我们会借助于临时变量的方式接收各自的参数,从而避免引起误会

脚本传参函数示例

cat function_arg_scripts2.sh
#!/bin/bash
# 功能:传参函数定义和调用
# 接收脚本传参
arg1=$1
arg2=$2
# 定制数据运算的函数
add_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} + ${num2} ))
}
sub_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} - ${num2} ))
}
mul_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} * ${num2} ))
}
div_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} / ${num2} ))
}
[ $# -ne 2 ] && echo "必须传递两个数字参数" && exit
echo -n "${arg1}+${arg2}="; add_func ${arg1} ${arg2}
echo -n "${arg1}-${arg2}="; sub_func ${arg1} ${arg2}
echo -n "${arg1}*${arg2}="; mul_func ${arg1} ${arg2}
echo -n "${arg1}/${arg2}="; div_func ${arg1} ${arg2}

环境部署精简示例

cat function_kubernetes_manager.sh
#!/bin/bash
# 功能:定制kubernetes环境部署管理的功能
# 定制数组变量
env_array=(base ha k8s_base master slave)
# 监控平台的信息提示
menu(){
    echo -e "\e[31m   欢迎使用kubernetes部署平台"
    echo -e "\e[32m-----------请选择部署阶段-----------"
    echo -e " 1: 基础环境部署"
    echo -e " 2: 高可用环境部署"
    echo -e " 3: kubernetes基础环境部署"
    echo -e " 4: 主角色环境部署"
    echo -e " 5: 从角色环境部署"
    echo -e " q: 退出"
    echo -e "----------------------------------\033[0m"
}
# 定制基础环境
os_base_func(){
    echo -e "\e[31m开始基础环境部署..."
    echo "1 执行跨主机免密码操作"
    echo "2 执行时间同步操作"
    echo "3 执行内核配置操作"
    echo -e "4 执行容器私有仓库部署操作\e[0m"
}
# 定制高可用环境
ha_func(){
    echo -e "\e[31高可用环境部署..."
    echo "1 执行高可用环境部署操作"
    echo -e "2 执行负载均衡环境部署操作\e[0m"
}
# 定制k8s基础环境
k8s_base_func(){
    echo -e "\e[31mkubernetes基础环境部署..."
    echo "1 执行证书管理操作"
    echo "2 执行etcd环境部署操作"
    echo -e "3 执行集群证书配置操作\e[0m"
}

# 定制主角色环境
master_func(){
    echo -e "\e[31m主角色环境部署..."
    echo "1 执行apiserver环境部署操作"
    echo "2 执行scheduler环境部署操作"
    echo "3 执行controller环境部署操作"
    echo "4 执行认证配置操作"
    echo "5 执行容器环境部署操作"
    echo "6 执行kubelet环境部署操作"
    echo -e "7 执行kube-proxy环境部署\e[0m"
}

# 定制从角色环境
slave_func(){
    echo -e "\e[31m主角色环境部署..."
    echo "1 执行容器环境部署操作"
    echo "2 执行kubelet环境部署操作"
    echo -e "3 执行kube-proxy环境部署\e[0m"
}
# 定制错误提示信息
usage_func(){
    echo -e "\e[31m请输入有效的功能场景标识\e[0m"
}
# 脚本内容的判断
while true
do
    # 定制业务逻辑
    menu
    read -p "请输入功能标识: " env_id
    if [ ${env_id} == "q" ];then
        exit
    else
        # 执行配套业务逻辑
        case "${env_array[$env_id-1]}" in
            "base")
                os_base_func;;
            "ha")
                ha_func;;
            "k8s_base")
                k8s_base_func;;
            "master")
                master_func;;
            "slave")
                slave_func;;
            *)
                usage_func;;
        esac
    fi
done

全局变量

cat function_env_test.sh
#!/bin/bash
# 功能:全局变量实践
# 定制普通的全局变量
message="helloworld"
# 定制一个函数,提示脚本的使用方式
function Usage {
    echo "直接调用脚本的message: ${message}"
    message="function-message"
    echo "函数体重置后的message: ${message}"
}
# 定制脚本使用逻辑
while true
do
    read -p "查看变量的方式[ 1-脚本内 | 2-函数内 ]:" type
    if [ ${type} == "1" ];then
        # 直接在脚本环境使用全局变量
        echo ${message}
    elif [ ${type} == "2" ];then
        # 函数内部使用全局变量
        Usage
    fi
done
脚本执行效果
[root@rocky9 ~]# /bin/bash function_env_test.sh
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:2
直接调用脚本的message: helloworld
函数体重置后的message: function-message
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
function-message # 结果显示,函数体内的变量生效了
查看变量的方式[ 1-脚本内 | 2-函数内 ]:^C
[root@rocky9 ~]#
结果显示:
  对于脚本的普通变量,函数内外都可以正常使用,而且函数内可以直接修改脚本级别的普通变量

本地变量

cat function_env_test2.sh
#!/bin/bash
# 功能:local定制函数级别的局部变量实践
# 定制普通变量
message="helloworld"
local local_env="local"
# 定制一个函数,提示脚本的使用方式
function Usage {
    echo "直接调用脚本的变量: ${message}-${local_env}"
    local message="function-message"
    echo "函数体重置后的变量: ${message}-${local_env}"
}
# 定制脚本使用逻辑
while true
do
    read -p "查看变量的方式[ 1-脚本内 | 2-函数内 ]:" type
    if [ ${type} == "1" ];then
        # 直接在脚本环境使用普通变量
        echo ${message}
    elif [ ${type} == "2" ];then
        # 函数内部使用普通变量
        Usage
    fi
done
脚本执行效果
[root@rocky9 ~]# /bin/bash function_env_test2.sh
function_env_test2.sh: 第 6 行:local: 只能在函数中使用
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld

五、脚本自动化

信号

linux使用信号与系统上运行的进程进行通信,想要对shell的脚本控制,只需要传递相关信号给shell脚本即可
常用信号介绍,全部信号可通过命令kill -l 查看
SIGHUP (1):挂起信号,常用于让守护进程(如 nginx、httpd)重新加载配置而不退出。
SIGINT (2):终端中断信号(对应 Ctrl+C),请求前台进程优雅中断。
SIGQUIT (3):终端退出信号(对应 **Ctrl+**),中断并生成 core dump。
SIGKILL (9):强制立即终止,进程无法捕获、忽略或阻塞;仅在 SIGTERM 无效时使用。
SIGTERM (15):默认终止信号,请求进程优雅退出,可被捕获并执行清理逻辑。
SIGSTOP (19):强制暂停进程,不可捕获或忽略(用于调试或临时冻结)。
SIGCONT (18):恢复被 SIGSTOP(或 SIGTSTP)暂停的进程。
SIGUSR1 (10) / SIGUSR2 (12):用户自定义信号,常用于触发日志轮转、快照等应用级操作

默认情况下,bash shell会忽略收到的任何SIGQUIT(3)和SIGTERM(15)信号(正因为这样交互式shell才不会被意外终止)。但是bash shell会处理收到的SIGHUP(1)和SIGINT(2)信号。

如果bash shell收到SIGHUP信号,它会退出。但在退出之前,它会将信号传给shell启动的所有进程(比如shell脚本)。通过SIGINT信号,可以中断shell,Linux内核停止将CPU的处理时间分配给shell,当这种情况发生时,shell会将SIGINT信号传给shell启动的所有进程。

生成信号

终止进程:
  ctrl+c,
暂停进程:
  ctrl+z,停止的进程继续保留在内存中,并能从停止的位置继续运行
恢复进程:
  jobs查看运行任务,fg num 重新执行
杀死进程:
  kill -9 pid

expect

expect就是一套用来实现自动交互功能的软件。它主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信。在使用的过程中,主要是以脚本文件的样式来存在
安装软件
yum install expect -y
apt install expect -y
查看效果
expect -v

常用命令

set 设定环境变量
  格式:set 变量名 变量值
  样式:set host "192.168.8.12"
spawn 启动新的进程,模拟手工在命令行启动服务
  格式:spawn 手工执行命令
  样式:spawn ssh python@$host
expect 接收一个新进程的反馈信息,我们根据进程的反馈,再发送对应的交互命令 
  格式:expect "交互界面用户输入处的关键字"
  样式:expect "*password*"
send 接收一个字符串参数,并将该参数发送到新进程。
  格式:send "用户输入的信息"

expect与脚本结合使用,最常用

在于shell结合使用的时候,无非就是将expect的执行命令使用 <<-EOF  。。。 EOF 包装在一起即可。
样式:
/usr/bin/expect<<-EOF
spawn ...
...
expect eof
EOF
注意:
由于expect在shell中是作为一个子部分而存在的,所以,一般情况下,expect结束的时候,使用eof命令表示expect的内容到此结束

脚本示例

cat expect_auto_login.sh
#!/bin/bash
# 功能:shell自动登录测试
host="$1"
username="$2"
password="$3"
/usr/bin/expect <<-EOF
# 发出连接进程
spawn ssh ${username}@${host}
# - 正常登陆
expect {
    "yes/no*" { send "yes\n"; exp_continue }
    "password:" {send "${password}\n";}
}
puts "测试完毕!!!"
expect eof
EOF

高级赋值

字符串截取按分隔符截取: # 右 % 左
  ${file#/}  删除匹配结果,保留第一个/右边的字符串
  ${file##/} 删除匹配结果,保留最后一个/右边的字符串
  ${file%/} 删除匹配结果,保留第一个/左边的字符串
  ${file%%/} 删除匹配结果,保留最后一个/左边的字符串
  注意:
    匹配内容的正则表达式,尽量不要出现特殊边界字符
字符串替换
  ${file/dir/path} 把第一个dir替换成path:/path1/dir2/dir3/n
  ${file//dir/path} 把所有dir替换成path:/path1/path2/path3/n
  ${file/#dir/path} 将从左侧能匹配到的dir,则替换成 path 然后返回;否则直接返回${var}。
  ${file/%dir/path} 将从右侧能匹配到的dir,则替换成 path 然后返回;否则直接返回${var}。
  注意:
    如果匹配内容使用的是正则符号,应该注意正则符号的写法 
字符串转换
  ${file^^} 把file中的所有小写字母转换为大写
  ${file,,} 把file中的所有大写字母转换为小写

字符串截取示例

字符串截取示例
[root@rocky9 ~]# string=abc12342341
[root@rocky9 ~]# echo ${string#a*3}
42341
[root@rocky9 ~]# echo ${string#c*3}
abc12342341
[root@rocky9 ~]# echo ${string#*c1*3}
42341
[root@rocky9 ~]# echo ${string##a*3}
41
[root@rocky9 ~]# echo ${string%3*1}
abc12342
[root@rocky9 ~]# echo ${string%%3*1}
abc12
作者:于浩  创建时间:2026-06-07 20:21
最后编辑:于浩  更新时间:2026-07-12 22:32