一篇文章让你学会shell编程

01. Shell的作用

  1. 自动化安装操作系统:先部署reid。自动化安装操作系统,使用Kickstart或Cobbler,Cobbler依赖于Shell脚本。
  2. 系统优化:关闭SELinux,优化防火墙,配置YUM仓库,修改默认SSH端口,禁止root登录,时间同步,加大文件描述符,常用命令,内核优化。100台服务器优化怎么办?使用Ansible。四五台写成脚本。
  3. 安装服务:经常安装多个服务或不同版本号,将流程写成脚本。
  4. 优化服务:修改配置(代码变更),使用脚本。
  5. 监控系统:监控系统、网络、硬件、服务、日志、接口等,使用脚本取值。
  6. 日志切割:日志分析统计,辅助自研程序正常运行,使用定时任务+脚本。

02. 什么是Shell

Shell命令解释器,用户输入命令,解释器负贵翻译给内核,内核驱动硬件,返回she11
Linux默认的解释器是:bash sh

shell脚本
将多个可执行的命令写入到文件中,称为shel脚本,脚本中还包含了变量,表达式,循环判断语句等。

image-20250202201919874

03. 编程语言

  • 解释型语言、脚本语言
  • Shell:解释型语言,脚本语言
  • Python:解释型语言,脚本语言
  • 编译型语言
  • C、C++
  • 其他
  • Java:JVM虚拟机
  • Go
  • HTML

04. 学习Shell用到的基础知识

  1. Linux常用的基础命令
  2. Vim编辑器
  3. awk、sed、grep
  4. Xshell工具

05. 如何学好Shell编程

  1. 可以读懂,明白Shell里面每行的含义
  2. 看懂后自己熟练地写出来,模仿
  3. 修改原有的Shell脚本
  4. 反复练习,多敲
  5. 切忌拿来即用,一定要转换成自己的

06. Shell脚本书写规范

  1. Shell脚本必须以.sh结尾
  2. 文件的开头加指定解释器 #!/bin/bash#!/bin/sh
  3. 脚本尽量带注释
  4. 脚本中成对的符号尽量一次性书写完毕
  5. 语法,一次性写完
  6. 注意不能出现中文符号
  7. 脚本尽量放到一个目录

07. 第一个Shell脚本,执行脚本

[root@shell ~]# cat test.sh
#!/bin/bash

# 版本 1.0
# 作者 haoshuaicong QQ 110
# 日志切割

echo "Hello world!!"

执行脚本的几种方式:

执行方式命令格式适用场景优点缺点说明
使用解释器执行sh test.shbash test.sh– 不需要脚本具有执行权限(chmod +x)的情况
– 当不清楚脚本的解释器类型时
不需要赋予脚本执行权限需要显式指定解释器类型执行时,生成一个子shell,子shell能继承父shell的变量,执行完毕子shell结束进程
使用路径方式执行/root/test.sh./test.sh– 脚本需要直接通过路径调用的场景
– 脚本会被频繁调用时
简单直接,适合频繁调用的脚本需要先赋予脚本执行权限
使用 source. 执行source test.sh. test.sh– 脚本需要修改当前 Shell 环境(如设置环境变量、定义函数等)
– 需要在当前 Shell 会话中立即生效
在当前 Shell 环境中执行,适合修改环境变量或定义函数不适合需要隔离环境的脚本都是在父shell中执行,不会产生子shell

08. Shell变量

  1. 什么是变量
    用一个固定的值来表示不固定的值称为变量。
   name=haoshuaicong
   name=oldgirl
   x=1
  1. 变量分类
  • 环境变量:全局变量(对所有的bash生效)
  • 普通变量:局部变量(只对当前的bash生效)
   # /etc/profile    # 定义全局变量,类似国法
   # test.sh --> name=haoshuaicong  # 定义局部变量,只在test.sh中生效,类似家规
   export作用:

   #不加export 只对父shell生效,子shell不生效
   [root@200 ~]# echo $name
   hsc
   [root@200 ~]# bash          #进入子shell
   [root@200 ~]# echo $name

   [root@200 ~]# 

   #加上export 全局生效
   [root@200 ~]# export name=hsc
   [root@200 ~]# echo $name
   hsc
   [root@200 ~]# bash          #进入子shell
   [root@200 ~]# echo $name
   hsc
   [root@200 ~]# 
  1. 变量相关文件(按照执行顺序): 谁后执行谁生效,也就是倒过来,重复赋值覆盖了
  • /etc/profile
  • .bash_profile 只针对用户,家目录下的文件
  • .bashrc 只针对用户,家目录下的文件
  • /etc/bashrc
  1. 环境变量定义
  • 变量名称的定义规范:
    1. 使用字母、数字、下划线的组合
    2. 不能以数字开头
    3. 可以用字母或下划线开头
    4. 等号两端不允许有空格
    5. 见名知其意
  1. 变量值的定义
  • 定义数字[root@shell ~]# age=110 [root@shell ~]# echo $age 110
  • 定义字符串[root@shell ~]# name=haoshuaicong [root@shell ~]# name="old boy" [root@shell ~]# echo $name old boy
  • 定义命令[root@shell ~]# IP=$(hostname -I | awk '{print $1}') [root@shell ~]# echo $IP 10.0.0.71

6.关于时间变量

1.时间要求不能变化数据备份客户端(引号和反引号实现)
[root@200 ~]# Time=`date +%F-%H-%M-%S`
[root@200 ~]# echo $Time
[rootashe11:-]#mkdir web01_$Time
find /backup/*  -mtime +3 | xargs rm -rf
mkdir -p /backup_conf/`hostname`_`hostname -I | awk '{print $1}'`_`$Time`/
tar zcf /backup_conf/`hostname`_`hostname -I | awk '{print $1}'`_`date +%F`/`hostname`_`hostname -I | awk '{print $1}'`_`$Time`.tar.gz  /etc/*
rsync -av /backup_conf/* rsync_backup@172.16.1.41::backup_conf --password-file=/etc/rsync.password


2.时间要求实时变化(引号和反引号实现)
[root@200 ~]# Time="date +%F-%H-%M-%S"
[root@200 ~]# $Time    #相当于执行这个命令
2025-02-02-22-59-24
[root@200 ~]# 
知识点内容示例
什么是变量用一个固定的值来表示不固定的值称为变量。name=haoshuaicong
name=oldgirl
x=1
变量分类环境变量:全局变量(对所有的bash生效)
普通变量:局部变量(只对当前的bash生效)
/etc/profile(全局变量)
test.sh(局部变量)
变量相关文件按照执行顺序:
/etc/profile
.bash_profile
.bashrc
/etc/bashrc
N/A
环境变量定义规范1. 使用字母、数字、下划线的组合
2. 不能以数字开头
3. 可以用字母或下划线开头
4. 等号两端不允许有空格
5. 见名知其意
USER=linux
PATH=/usr/bin:/bin
变量值的定义 – 数字定义数字类型的变量。[root@shell ~]# age=110
[root@shell ~]# echo $age
110
变量值的定义 – 字符串定义字符串类型的变量,可以用单引号或双引号包裹。[root@shell ~]# name=haoshuaicong
[root@shell ~]# name="old boy"
[root@shell ~]# echo $name
old boy
变量值的定义 – 命令定义变量的值为命令的输出结果,使用 $()`` 语法。 |[root@shell ~]# IP=$(hostname -Iawk ‘{print 1}’)<br>[root@shell ~]# echo IP<br>10.0.0.71`

09. 特殊位置变量

  • $0:脚本名称
root@200 ~]# cat script.sh 
echo $0
[root@200 ~]# ./script.sh 
./script.sh
  • $n:第n个参数
脚本传参,参数空格分隔
[root@200 ~]# cat script.sh 
echo $1
echo $2
[root@200 ~]# ./script.sh   hsc  old   boy
hsc
old
[root@200 ~]#
  • $#:参数个数
[root@200 ~]# cat script.sh 
echo $#
[root@200 ~]# ./script.sh hsc old boy
3
[root@200 ~]# 
  • $?:上一条命令的退出状态,0或非0
  • $*:所有参数
[root@200 ~]# cat script.sh 
echo $*
[root@200 ~]# ./script.sh hsc old boy
hsc old boy
  • $@:所有参数(跟上面一样)
  • $_:上一个命令的最后一个参数
[root@200 ~]# cat script.sh 
ls | wc -l
echo $_
[root@200 ~]# ./script.sh
1
./script.sh
[root@200 ~]# 

10.vim快捷键

光标移动
G:到文件末尾。
gg 或 1G:到文件开头。
0:到行首。
$:到行尾。

删除
D:删到行尾。
C:删到行尾并进入插入模式。
dw:删掉当前单词。
dd:删掉当前行。
dG:删到文件到末尾行。
d0 或 d^:删到行首。

编辑
O:上一行插入新行并进入编辑模式。
o:下一行插入新行并进入编辑模式。

vim批量缩进

1.ctrl+v

2.选中

3.按大写i

4.添加操作

5.esc按两下

11. 其他执行脚本的方式

[root@shell ~]# cat test.sh | bash
Hello world!!

[root@shell ~]# curl http://10.0.0.7/ | bash

[root@shell ~]# bash < test.sh
Hello world!!

执行Shell脚本的区别:

  • bash执行和路径执行是相同的,都是在子Shell中执行,执行完成后子Shell退出。
  • source.都是在父Shell中执行,相当于将文件中的命令直接拿到父Shell中执行。

12. 变量案例

  1. 定义IP地址
   [root@shell ~]# IP=$(hostname -I | awk '{print $1}')
   [root@shell ~]# echo $IP
   10.0.0.71
  1. 定义时间
   [root@shell ~]# Time=$(date +%F-%H-%M-%S)
   [root@shell ~]# echo $Time
   2025-01-21-11-56-08
  1. 变量中定义变量
   [root@shell ~]# name=haoshuaicong
   [root@shell ~]# test=${name}_oldgirl
   [root@shell ~]# echo $test
   haoshuaicong_oldgirl

13. 重点

  1. 面试题:给你一台新的服务器,你的操作流程
面试题:给你一台新的机器,说一下你的工作流程?
1.安装操作系统(自动化安装操作系统kickstart cobbler 使用she11) 
2.优化操作系统防火墙优化优化SSH服务禁止root登录修改默认端口时间同步加大文件描述符.she11脚本
3.安装部署服务(yum编译安装安装不同的版本)she11脚本安装不同版本的服务
4.代码变更,业务升级,shell脚本
5.巡检系统内存磁盘cpu服务状态she11 
6.数据分析、数据统计定时任务+脚本
7.重要的数据备份脚本
8.zabbix监控+脚本取值
9.日志切割、防止日志过大。 
10.启动脚本,tomcat公司python脚本she11 
  1. Shell的执行方式
  2. sh、路径执行和source的区别
  3. 变量名称的定义
  4. 变量分类:全局、局部
  5. export的作用
  6. 变量值的定义:数字、字符串、命令定义

15. 总结

  • Shell脚本是自动化运维的重要工具。
  • 掌握Shell脚本的编写和执行方式是学习Linux系统管理的基础。
  • 变量是Shell脚本中的重要组成部分,理解变量的定义和使用是编写高效脚本的关键。

echo -e使用说明

简单说明

  1. echo -e:
  • 用于解析转义字符。
  • 默认情况下,echo 不会解析转义字符(如 \n\t 等),但如果添加 -e 参数,echo 会将这些转义字符作为特殊字符处理。
  • 语法:echo -e "内容"
  1. \n:
  • 换行符,用于在文本中插入换行。
  • echo -e 中使用时,echo 会识别 \n 并将其作为换行符处理。

简单举例

示例 1:普通 echo 的输出

echo "Hello\nWorld"

输出结果:

Hello\nWorld       # 换行符 \n 未被识别

示例 2:使用 echo -e 解析 \n

echo -e "Hello\nWorld"

输出结果:

Hello
World       # 换行符 \n 被解析,文本分两行显示

示例 3:结合制表符 \t

echo -e "Hello\tWorld"

输出结果:

Hello   World       # 制表符 \t 被解析,文本有水平间距

示例 4:多行文本使用 \n

echo -e "Line 1\nLine 2\nLine 3"

输出结果:

Line 1
Line 2
Line 3       # 每个 \n 都被解析,文本分三行显示

总结

  • 使用 echo -e 可以解析转义字符(如 \n\t 等)。
  • \n 是换行符,用于在文本中插入换行。
  • 这种用法常用于格式化输出或生成多行文本。

其他类似\n符号

1. 换行符 \n

  • 作用:插入一个换行。
  • 示例
  echo -e "Line 1\nLine 2"
  • 输出
  Line 1
  Line 2

2. 制表符 \t

  • 作用:插入一个水平制表符(用于对齐文本)。
  • 示例
  echo -e "Name:\tAlice\nAge:\t25"
  • 输出
  Name:    Alice
  Age:    25

3. 回车符 \r

  • 作用:将光标回退到当前行的开头。
  • 示例
  echo -e "Countdown: 5\rCountdown: 4\rCountdown: 3\rCountdown: 2\rCountdown: 1"
  • 输出
  Countdown: 1

4. 退格符 \b

  • 作用:删除前一个字符。
  • 示例
  echo -e "Hello\bWorld"
  • 输出
  HellWorld

5. 单引号 \' 和双引号 \"

  • 作用:插入单引号或双引号。
  • 示例
  echo -e "She said, \"Hello!\""
  • 输出
  She said, "Hello!"

6. 反斜杠 \\

  • 作用:插入一个反斜杠 \
  • 示例
  echo -e "Path: C:\\Users\\Alice"
  • 输出
  Path: C:\Users\Alice

7. 响铃 \a

  • 作用:发出一声响铃(通常是终端的提示音)。
  • 示例
  echo -e "\a"
  • 输出:终端会发出一声提示音。

8. 垂直制表符 \v

  • 作用:插入一个垂直制表符(用于垂直对齐文本)。
  • 示例
  echo -e "Name\vAlice\nAge\v25"
  • 输出
  NameAlice
  Age25

Shell 特殊位置变量


一、核心变量

  1. $?:上一条命令的退出状态
  • 作用:判断命令是否执行成功。
    • 0:成功。
    • 非0:失败(具体值表示错误类型)。
  • 示例ping -c1 www.example.com &>/dev/null if [ $? -eq 0 ]; then echo "网络可达" else echo "网络不可达" fi
  1. $#:脚本参数个数
  • 作用:验证用户传入的参数数量。
  • 示例# 必须传入2个参数 [ $# -ne 2 ] && echo "需传入2个参数" && exit 1
  1. $0:脚本名称
  • 作用:获取脚本的路径或名称。脚本传参报错提示时,可以使用变量打印脚本名称和参数
  • 示例echo "脚本名称:$0" # 输出示例:./script.sh 或 /path/to/script.sh echo "脚本: $0 [start|stop|reload]" echo "Hello World!!"
  1. $n:第n个参数
  • 规则$1为第一个参数,$2为第二个,以此类推,脚本传参。
  • 注意:超过$9需用大括号,如 ${10}
  • 示例echo "第一个参数:$1" echo "第十个参数:${10}"
  1. $$:当前脚本的PID
  • 用途:生成唯一临时文件、记录进程ID。
  • 示例echo "当前脚本PID:$$" # 输出:1234
  1. $!:上一个后台进程的PID
  • 用途:管理后台进程(如停止服务)。
  • 示例sleep 1000 & echo "后台进程PID:$!"

二、进阶变量(了解)

  1. $*$@:所有参数
  • 区别
    • 不加引号:两者相同,所有参数视为独立个体。
    • 加引号
    • "$*":合并为单个字符串(如 "arg1 arg2 arg3")。
    • "$@":保持参数独立性(如 "arg1" "arg2" "arg3")。
  • 示例# 脚本内容:test.sh for arg in "$*"; do echo "[$arg]"; done # 输出一行 for arg in "$@"; do echo "[$arg]"; done # 每个参数一行 sh test.sh "hello world" 123 # 输出: # [hello world 123] # [hello world] # [123]
  1. $_:上一个命令的最后一个参数
  • 示例ls /tmp/file.txt echo $_ # 输出:/tmp/file.txt echo 1 2 3 5 echo $_ #输出5

三、关键总结

变量作用示例场景
$?检查命令成功与否错误处理、条件判断
$#验证参数数量脚本参数合法性检查
$0获取脚本名称日志记录、帮助信息
$n获取第n个参数脚本传参处理
$$获取脚本PID生成临时文件、进程管理
$!获取后台进程PID停止后台任务
$*所有参数(合并为一个字符串)批量处理参数(不涉及空格)
$@所有参数(保持独立性)处理带空格的参数

四、常见问题

  1. 子Shell变量作用域问题
  • 问题:子Shell中定义的变量,父Shell无法访问。
  • 示例# script.sh Test=`whoami` sh script.sh echo $Test # 输出为空
  • 解决:使用 export 导出变量,或通过文件传递数据。
  1. 参数超过9个时的处理
  • 错误写法$10(实际解析为$1后跟字符0)。
  • 正确写法${10}${11}
  1. $*$@ 在循环中的差异
  • 关键点:使用双引号时的行为不同。
  • 示例set -- "I am" lizhenya for i in "$*"; do echo $i; done # 输出:I am lizhenya for i in "$@"; do echo $i; done # 输出两行:I am 和 lizhenya

五、实战应用

  1. 参数合法性检查
   #!/bin/bash
   [ $# -ne 2 ] && echo "用法:$0 <用户名> <年龄>" && exit 1
   echo "用户名:$1,年龄:$2"
  1. 后台进程管理
   # 启动后台任务并记录PID
   nohup ./service.sh > log.txt 2>&1 &
   echo "服务PID:$!"
  1. 生成唯一临时文件
   TEMP_FILE="/tmp/data.$$"
   echo "临时文件:$TEMP_FILE"

脚本传参

一、3种核心传参方式

1. 直接传参(位置参数)

特点

  • 通过命令行按顺序传递参数
  • 脚本内用$1 $2${10}获取
  • $0表示脚本自身名称

示例脚本

#!/bin/bash
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第十个参数: ${10}"

执行与输出

$ ./demo.sh A B C D E F G H I J K
脚本名称: ./demo.sh
第一个参数: A
第二个参数: B
第十个参数: J

关键点

  • 参数超过9个必须用${}包裹(如${10}
  • 空参数会保留位置但值为空

2. 赋值传参(环境变量)

特点

  • 通过预定义变量传递参数
  • 变量名在脚本中直接使用

示例脚本

#!/bin/bash
echo "用户名: $USER"
echo "调试模式: $DEBUG_MODE"

执行方式

$ USER=admin DEBUG_MODE=true ./demo.sh

输出

用户名: admin
调试模式: true

应用场景

  • 配置参数传递(如数据库连接信息)
  • 临时环境变量设置

3. 交互式传参(read命令)

特点

  • 脚本运行时动态输入参数
  • 支持隐藏输入(-s参数)

基础示例

#!/bin/bash
read -p "输入姓名: " name
read -sp "输入密码: " pass
echo -e "\n欢迎 $name,密码已安全接收"

执行过程

输入姓名: 李雷
输入密码: [输入不可见]
欢迎 李雷,密码已安全接收

安全增强技巧

# 密码加密存储示例
read -sp "输入密码: " pass
encrypted_pass=$(echo "$pass" | openssl enc -base64)

三、实战案例解析

案例1:修改主机名

#!/bin/bash
# 需要root权限执行
[ $# -ne 1 ] && echo "用法: $0 <新主机名>" && exit 1
hostnamectl set-hostname "$1"
echo "127.0.0.1 $1" >> /etc/hosts
systemctl restart systemd-hostnamed

使用方式

$ sudo ./set_hostname.sh web-server-01

案例2:动态配置IP地址

#!/bin/bash
# 参数顺序:IP 子网掩码 网关
[ $# -ne 3 ] && echo "用法: $0 <IP> <掩码> <网关>" && exit 1

CONFIG_FILE="/etc/sysconfig/network-scripts/ifcfg-ens33"
cp "$CONFIG_FILE" "${CONFIG_FILE}.bak"  # 备份配置

sed -i "/^IPADDR/c\IPADDR=$1" "$CONFIG_FILE"
sed -i "/^PREFIX/c\PREFIX=$2" "$CONFIG_FILE"
sed -i "/^GATEWAY/c\GATEWAY=$3" "$CONFIG_FILE"

systemctl restart network
echo "IP配置已更新,请验证连接"

使用方式

$ sudo ./set_ip.sh 192.168.1.100 24 192.168.1.1

案例3:更改ip

[root@200 ~]# cat network.sh 
#!/bin/bash
ens33=/etc/sysconfig/network-scripts/ifcfg-ens33
ens36=/etc/sysconfig/network-scripts/ifcfg-ens36
read -p "请输入网卡名称[ens33|ens36]:" netw
read -p "请输入要更改的ip:" ipa
if [ ens33 = $netw ];then
    sed -ri "s#^IPADDR.*#IPADDR=$ipa#" $ens33
    systemctl restart network
    echo "IP配置已更新,请验证连接"
elif [ ens36 = $netw ];then 
    sed -ri "s#^IPADDR.*#IPADDR=$ipa#" $ens36
    systemctl restart network
    echo "IP配置已更新,请验证连接"
else
    echo "格式错误,请重新执行脚本"
fi

四、高级技巧与注意事项

1. 参数校验最佳实践

# 必需参数检查
if [ -z "$1" ]; then
    echo "错误:必须指定配置文件路径"
    exit 100
fi

# 参数类型校验
if ! [[ $2 =~ ^[0-9]+$ ]]; then
    echo "错误:第二个参数必须是数字"
    exit 101
fi

2. 参数移位处理

while [ $# -gt 0 ]; do
    case "$1" in
        -v|--verbose) VERBOSE=true ;;
        -f|--file) shift; TARGET_FILE=$1 ;;
        *) echo "未知参数: $1"; exit 1 ;;
    esac
    shift
done

3. 安全注意事项

  • 敏感参数处理:
  • 避免在日志中记录密码等参数
  • 使用unset pass及时清除变量
  • 考虑使用/dev/stdin交互输入密码

五、传参方式选择指南

场景特征推荐方式理由
自动化脚本直接传参适合CI/CD等无需交互的场景
需要保护敏感信息read -s避免密码显示在屏幕或日志中
多环境配置赋值传参通过环境变量区分不同环境配置
参数组合复杂混合使用结合位置参数与选项参数

变量的子串

1. 统计字符串长度

方法 1:wc -L

  • 作用:统计字符串的最长长度(适用于单行文本)。
  • 示例
  echo haoshuaicong | wc -L
  • 输出
  6

方法 2:awk '{print length}'

  • 作用:使用 awk 打印字符串的长度。
  • 示例
  echo haoshuaicong | awk '{print length}'
  • 输出
  6

方法 3:expr length

  • 作用:使用 expr 命令计算字符串长度。
  • 示例
  expr length haoshuaicong
  • 输出
  6

方法 4:${#name}

  • 作用:使用 Bash 变量的长度属性 ${#name} 统计字符串长度。
  • 示例
  name=haoshuaicong
  echo ${#name}
  • 输出
  6

2. 循环遍历字符串

示例:for 循环

  • 作用:逐个输出字符串中的每个单词。
  • 示例
  for i in I am haoshuaicong I am 18
  do
    echo $i
  done
  • 输出
  I
  am
  haoshuaicong
  I
  am
  18

3. 筛选字符串长度小于 3 的字符串

方法 1:for 循环 + 条件判断

  • 作用:筛选字符串长度小于 3 的单词。
  • 示例
  for i in I am haoshuaicong I am 18
  do
    [ ${#i} -lt 3 ] && echo $i
  done
  • 输出
  I
  am
  I
  am

方法 2:awk 筛选

  • 作用:使用 awk 筛选字符串长度小于 3 的单词。
  • 示例
  cat 1.txt | awk '{if(length($1)<3) print $1}'
  • 说明:假设 1.txt 文件内容为:
  I am haoshuaicong
  I am 18
  • 输出
  I
  am
  I
  am

方法 3:xargs + awk 筛选

  • 作用:使用 xargsawk 筛选字符串长度小于 3 的单词。
  • 示例
  echo I am haoshuaicong I am 18 | xargs -n1 | awk '{if(length($1)<3) print $1}'
  • 输出: 复制
  I
  am
  I
  am

方法 4:awk 循环筛选

  • 作用:使用 awk 的循环功能筛选字符串长度小于 3 的单词。
  • 示例
  echo I am haoshuaicong I am 18 | awk 'for(i=1;i<=NF;i++) if(length($i)<3) print $i'
  • 输出: 复制
  I
  am
  I
  am

4. 总结

  1. 统计字符串长度
  • wc -L:统计最长行的长度。
  • awk '{print length}':打印字符串长度。
  • expr length:计算字符串长度。
  • ${#name}:Bash 变量长度属性。
  1. 循环遍历字符串
  • 使用 for 循环逐个输出字符串中的单词。
  1. 筛选字符串长度小于 3 的字符串
  • 使用 for 循环结合条件判断。
  • 使用 awk 筛选。
  • 使用 xargsawk 筛选。
  • 使用 awk 循环筛选。

变量子串的删除与替换

1. 基本变量操作

  • 变量赋值与显示
  url=www.baidu.com
  echo $url
  • 作用:定义变量并显示其值。
  • 输出www.baidu.com

2. 子串删除

2.1 从前往后删除(#)

  • 基本语法
  ${变量#模式}
  ${变量##模式}
  • #:删除匹配的最短子串。
  • ##:删除匹配的最长子串。
  • 示例 1:删除开头的 www.
  echo ${url#www.}
  • 输出baidu.com
  • 示例 2:删除开头的 www.baidu.
  echo ${url#www.baidu.}
  • 输出com
  • 示例 3:删除开头的任意字符直到第一个 .
  echo ${url#*.}
  • 输出com

2.2 从后往前删除(%)

  • 基本语法
  ${变量%模式}
  ${变量%%模式}
  • %:删除匹配的最短子串。
  • %%:删除匹配的最长子串。
  • 示例 1:删除结尾的任意字符从第一个 . 开始。
  echo ${url%%.*}
  • 输出www
  • 示例 2:删除结尾的任意字符从任意位置开始。
  echo ${url%.}
  • 输出www.baidu.com

2.3 应用场景:删除小数点部分

  • 变量
  a=10.5
  • 操作:删除小数部分。
  [ ${a%.*} -eq 10 ]
  • 输出0 # 表示条件成立

2.4 提取磁盘使用率

  • 命令
  df -h | awk 'NR==6{print $(NF-1)}'
  • 作用:提取第六行的倒数第二个字段(磁盘使用率)。
  • 输出8%
  • 提取整数部分
  disk_use=8%
  [ ${disk_use%\%} -eq 10 ]
  • 输出1 # 表示条件不成立

3. 子串替换

  • 基本语法
  ${变量/旧子串/新子串}
  ${变量//旧子串/新子串}
  • /:替换第一个匹配的子串。
  • //:替换所有匹配的子串。
  • 示例 1:替换 baiduhsc
  echo ${url/baidu/hsc}
  • 输出www.hsc.com
  • 示例 2:替换所有 wA
  echo ${url//w/A}
  • 输出AAA.baidu.com

4. 总结

  1. 子串删除
  • 从前往后删除:#${变量#模式}#${变量##模式}
  • 从后往前删除:%${变量%模式}%${变量%%模式}
  • 应用场景:删除小数点部分、提取磁盘使用率等。
  1. 子串替换
  • 替换第一个匹配:${变量/旧子串/新子串}
  • 替换所有匹配:${变量//旧子串/新子串}
  1. 复杂操作
  • 结合 awksed 实现更复杂的文本处理。
  • 使用 ${变量%%模式}${变量%模式} 提取和处理字符串。

5. 面试题:统计字符串长度

  • 方法 1:使用 ${#变量}
  url=www.baidu.com
  echo ${#url}
  • 输出14
  • 方法 2:使用 awk
  echo $url | awk '{print length}'
  • 输出14
  • 方法 3:使用 expr length
  expr length $url
  • 输出14

6. 面试题:统计字符串小于3的字符

方法 1:for 循环 + 条件判断

  • 作用:筛选字符串长度小于 3 的单词。
  • 示例
  for i in I am haoshuaicong I am 18
  do
    [ ${#i} -lt 3 ] && echo $i
  done
  • 输出
  I
  am
  I
  am

方法 2:awk 筛选

  • 作用:使用 awk 筛选字符串长度小于 3 的单词。
  • 示例
  cat 1.txt | awk '{if(length($1)<3) print $1}'
  • 说明:假设 1.txt 文件内容为:
  I am haoshuaicong
  I am 18
  • 输出
  I
  am
  I
  am

方法 3:xargs + awk 筛选

  • 作用:使用 xargsawk 筛选字符串长度小于 3 的单词。
  • 示例
  echo I am haoshuaicong I am 18 | xargs -n1 | awk '{if(length($1)<3) print $1}'
  • 输出: 复制
  I
  am
  I
  am

方法 4:awk 循环筛选

  • 作用:使用 awk 的循环功能筛选字符串长度小于 3 的单词。
  • 示例
  echo I am haoshuaicong I am 18 | awk 'for(i=1;i<=NF;i++) if(length($i)<3) print $i'
  • 输出: 复制
  I
  am
  I
  am

数值运算

1. expr

  • 特点
  • 支持基本的整数运算(加、减、乘、除)。
  • 操作符之间必须有空格,否则不会运算。
  • 不支持浮点运算和复杂的表达式。
  • 示例
  echo $(expr 1 + 1)  # 输出 2
  echo $(expr 10 \* 10)  # 输出 100
  echo $(expr 10 / 2)  # 输出 5
  echo $(expr 10 - 2)  # 输出 8

2. $[]

  • 特点
  • expr 类似,支持基本的整数运算。
  • 语法更简洁,但功能有限。
  • 示例
  echo $[10 + 10]  # 输出 20
  echo $[10 * 10]  # 输出 100
  echo $[10 / 2]  # 输出 5
  echo $[10 - 2]  # 输出 8

3. $(( ))

  • 特点
  • 支持基本的整数运算。
  • 语法简洁,效率高。
  • 可以直接在表达式中使用变量。
  • 示例
  echo $((1 + 1))  # 输出 2
  echo $((10 * 10))  # 输出 100
  echo $((10 / 2))  # 输出 5
  echo $((10 - 2))  # 输出 8

4. let

  • 特点
  • 支持基本的整数运算。
  • 可以直接对变量进行赋值和运算。
  • 常用于循环中的变量自增或自减。
  • 示例
  let a=1+1
  echo $a  # 输出 2

  let i=0
  for i in I am hsc
  do
    let a++
  done
  echo $a  # 输出 3

  let i++
  echo $i  # 输出 1

  unset i
  let ++i
  echo $i  # 输出 1

5. bc

  • 特点
  • 支持整数和浮点运算。
  • 可以处理复杂的数学表达式。
  • 常用于需要高精度计算的场景。
  • 示例
  echo "1 + 2" | bc  # 输出 3
  echo "100 - 1" | bc  # 输出 99
  echo "10 * 10" | bc  # 输出 100
  echo "10 / 2" | bc  # 输出 5
  echo "10 / 3" | bc  # 输出 3
  echo "scale=2; 10 / 3" | bc  # 输出 3.33

6. awk

  • 特点
  • 支持整数和浮点运算。
  • 可以处理复杂的数学表达式。
  • 常用于文本处理和数值计算。
  • 示例
  awk 'BEGIN{print 10 + 10 * 2}'  # 输出 30
  awk 'BEGIN{print 10 / 3}'  # 输出 3.3333333333
  awk 'BEGIN{print sin(3.141592653589793)}'  # 输出 0.0000000000

7. python

  • 特点
  • 支持整数和浮点运算。
  • 可以处理复杂的数学表达式。
  • 功能强大,适合复杂的数值计算和数据分析。
  • 示例
  python3 -c "print(10 * 10)"  # 输出 100
  python3 -c "print(10 + 10)"  # 输出 20
  python3 -c "print(1.2 * 4)"  # 输出 4.8

8. 实际应用场景

8.1 内存使用百分比

  • 示例
  free | awk 'NR==2{print $3/$2*100}'
  • 输出39.3626

8.2 统计第5列的和

  • 示例
  ps axuf | awk '{print $5}' | grep -v VSZ | xargs -n100000 | sed 's# #+#g' | bc
  • 说明
    • ps axuf:列出所有进程的详细信息。
    • awk '{print $5}':提取第5列(通常是内存使用量)。
    • grep -v VSZ:过滤掉标题行。
    • xargs -n100000:将输出传递给 bc 进行计算。
    • sed 's# #+#g':替换空格为加号。
    • bc:计算总和。

9. 总结

  1. 简单整数运算
  • expr:适合简单的整数运算,但需要在操作符之间加空格。
  • $[]$(( )):语法简洁,适合简单的整数运算。
  1. 变量运算
  • let:适合对变量进行赋值和运算,常用于循环中的变量自增或自减。
  1. 复杂运算
  • bc:支持整数和浮点运算,适合需要高精度计算的场景。
  • awk:支持整数和浮点运算,适合文本处理和数值计算。
  • python:功能强大,适合复杂的数值计算和数据分析。

表达式-文件判断

test -f /etc/hosts  #判断hosts文件是否存在 了解
[ -f /etc/hosts ]  #判断hosts文件是否存在  必会

[root@200 ~]# test -f /etc/hosts && echo "文件存在" || echo "文件不存在"
[root@200 ~]# test -f /etc/hostasaaaa && echo "文件存在" || echo "文件不存在"

理解&&和||

[root@200 ~]# [ -f /etc/hosts ] && echo "yes"
yes
[root@200 ~]# 
[root@200 ~]# [ -f /etc/hosts ] && echo "yes" || echo no
yes
[root@200 ~]# 
[root@200 ~]# [ -f /etc/hostsaaaa ] && echo "yes" || echo no
no
[root@200 ~]# 


-f #判断文件
-d #判断目录
-e #存在为真
-r #可读为真
-w #可写为真
-x #可执行为真


[root@200 ~]# cat .bashrc 
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi


案例1
[root@200 ~]# cat /root/1.sh
code_dir=/code/wordpress

[ -f /root/1.sh ] && soure /root/1.sh
echo $code_dir


案例2
在脚本互相调用的时候 -f判断
[root@200 ~]# cat /root/1.sh
[ -f /root/1.sh ] && soure /root/1.sh
echo $code_dir

[root@200 ~]# sh 1.sh
/code/wordpress



案例3 -d案例
[ -d /hsc ] || mkdir /hsc

太长可以先赋值
[ -d /backup/`hostname -I | awk '{print $2}'`_`hostname` ] ||  mkdir -p /backup/`hostname -I | awk '{print $2}'`_`hostname`

并且&&    
或者||    

-a并且
-o或者

[root@200 /backup]# [ -f /etc/hosts  -a  -f /root/xxx.txt ]  && echo yes || echo no
no
[root@200 /backup]# [ -f /etc/hosts  -o  -f /root/xxx.txt ]  && echo yes || echo no
yes
[root@200 /backup]# [ -f /etc/hostsxxx.txt  -o  -f /root/xxx.txt ]  && echo yes || echo no
no
[root@200 /backup]# 

1. 基本文件判断

1.1 test 命令

  • 语法
  test [表达式]
  • 示例
  test -f /etc/hosts && echo "文件存在" || echo "文件不存在"

1.2 [ ] 方括号

  • 语法
  [ 表达式 ]
  • 示例
  [ -f /etc/hosts ] && echo "yes" || echo "no"

2. 文件判断操作符

可以用man test查看

  • -f:判断是否为普通文件。
  • -d:判断是否为目录。
  • -e:判断文件是否存在(无论类型)。
  • -r:判断文件是否可读。
  • -w:判断文件是否可写。
  • -x:判断文件是否可执行。

3. 逻辑运算符

  • &&:逻辑与,前一个条件为真时执行后一个命令。
  • ||:逻辑或,前一个条件为假时执行后一个命令。
  • -a:逻辑与(旧语法)。
  • -o:逻辑或(旧语法)。

4. 实际案例

4.1 判断文件是否存在

  • 示例
  [ -f /etc/hosts ] && echo "文件存在" || echo "文件不存在"

4.2 判断目录是否存在

  • 示例
  [ -d /hsc ] || mkdir /hsc

4.3 复杂条件判断

  • 示例
  [ -f /etc/hosts -a -f /root/xxx.txt ] && echo "yes" || echo "no"
  [ -f /etc/hosts -o -f /root/xxx.txt ] && echo "yes" || echo "no"

5. 脚本中的应用

5.1 案例 1:加载配置文件

  • 脚本内容
  if [ -f /etc/bashrc ]; then
      . /etc/bashrc
  fi

5.2 案例 2:脚本互相调用

  • 脚本内容
  [ -f /root/1.sh ] && source /root/1.sh
  echo $code_dir

5.3 案例 3:动态创建目录

  • 脚本内容
  [ -d /backup/`hostname -I | awk '{print $2}'`_`hostname` ] || mkdir -p /backup/`hostname -I | awk '{print $2}'`_`hostname`

6. 总结

  1. 文件判断操作符,可以用man查看
  • -f:判断是否为普通文件。
  • -d:判断是否为目录。
  • -e:判断文件是否存在。
  • -r:判断文件是否可读。
  • -w:判断文件是否可写。
  • -x:判断文件是否可执行。
  1. 逻辑运算符
  • &&:逻辑与。
  • ||:逻辑或。
  • -a:逻辑与(旧语法)。
  • -o:逻辑或(旧语法)。
  1. 实际应用
  • 判断文件或目录是否存在。
  • 动态创建目录。
  • 脚本互相调用。
  • 加载配置文件。

条件表达式-字符串比对

1. 条件表达式基础

1.1 test 命令

  • 语法
  test [表达式]
  • 功能: 用于判断条件是否成立,返回退出状态码(0 表示真,非 0 表示假)。

1.2 [ ] 方括号

  • 语法
  [ 表达式 ]
  • 功能: 与 test 命令等价,用于条件判断。

2. 字符串比较

2.1 等于 (=)

  • 语法
  [ 字符串1 = 字符串2 ]
  • 示例
  [ root = root ] && echo "相等" || echo "不相等"
  • 说明
  • 比较时区分大小写。
  • 如果字符串1和字符串2相等,返回 0(真)。

2.2 不等于 (!=)

  • 语法
  [ 字符串1 != 字符串2 ]
  • 示例
  [ root != Root ] && echo "不相等" || echo "相等"
  • 说明
  • 比较时区分大小写。
  • 如果字符串1和字符串2不相等,返回 0(真)。

3. 逻辑运算符

3.1 逻辑与 (-a)

  • 语法
  [ 条件1 -a 条件2 ]
  • 示例
  [ root = root -a a = b ] && echo "两个条件都成立" || echo "至少一个条件不成立"
  • 说明
  • 两个条件都为真时,返回 0(真)。

3.2 逻辑或 (-o)

  • 语法
  [ 条件1 -o 条件2 ]
  • 示例
  [ root = root -o a = b ] && echo "至少一个条件成立" || echo "两个条件都不成立"
  • 说明
  • 两个条件中有一个为真时,返回 0(真)。

4. 多字符串比较

  • 语法
  [ 条件1 -o 条件2 -o 条件3 ]
  • 示例
  user=haoshuaicong
  [ $user = root -o $user = haoshuaicong -o $user = haoshuaicong ] && echo "匹配成功" || echo "匹配失败"
  • 说明
  • 使用 -o 进行多个字符串的比较,判断是否满足任意一个条件。

5. 字符串长度判断

5.1 -n:字符串长度不为 0

  • 语法
  [ -n 字符串 ]
  • 示例
  length=aaa
  [ -n $length ] && echo "字符串长度不为 0" || echo "字符串长度为 0"

5.2 -z:字符串长度为 0

  • 语法
  [ -z 字符串 ]
  • 示例
  length=aaa
  [ -z $length ] && echo "字符串长度为 0" || echo "字符串长度不为 0"

  length=''
  [ -z $length ] && echo "字符串长度为 0" || echo "字符串长度不为 0"

6. 实际应用

6.1 密码输入验证

  • 功能: 检查用户输入的密码是否为空。
  • 代码
  #!/bin/bash
  echo "请输入您的密码:"
  read -s password
  [ -z $password ] && echo "必须输入密码" && exit
  echo "您输入的密码是:$password"

6.2 密码长度验证

  • 功能: 检查用户输入的密码长度是否符合要求(如 6 位)。
  • 代码
  #!/bin/bash
  echo "请输入您的密码:"
  read -s password
  length=${#password}
  [ $length -ne 6 ] && echo "密码长度必须为 6 位" && exit
  [ -z $password ] && echo "必须输入密码" && exit
  echo "您输入的密码是:$password"

6.3 年龄输入验证

  • 功能: 检查用户输入的年龄是否为整数且在合理范围内(如 1-100)。
  • 代码
  #!/bin/bash
  read -p "请输入您的年龄:" age
  expr 1 + $age &>/dev/null
  [ $? -ne 0 ] && echo "年龄必须是整数" && exit

  length=${#age}
  [ $length -ne 1 -a $length -ne 2 ] && echo "年龄必须在 1-100 范围内" && exit
  echo "您的年龄是:$age"

7. 综合示例:密码验证

7.1 单次验证

  • 代码
  #!/bin/bash
  read -s -p "请输入游戏密码:" pass
  echo -e "\n"
  [ $pass != "qq123" ] && echo "密码错误,请重新输入密码" && exit
  echo -e "\n"
  echo "密码正确,您的密码是:$pass"

7.2 循环验证

  • 功能: 如果密码错误,允许用户重新输入,直到输入正确为止。
  • 代码
  #!/bin/bash
  while true
  do
    read -s -p "请输入游戏密码:" pass
    echo -e "\n"
    [ $pass != "qq123" ] && echo "密码错误,请重新输入密码" && continue
    echo -e "\n"
    echo "密码正确,您的密码是:$pass"
    break
  done

7.3 带错误限制的验证

  • 功能: 如果用户连续输入错误密码超过一定次数(如 3 次),程序将暂停一段时间(如 60 秒)。
  • 代码
  #!/bin/bash
  count=0
  while true
  do
    read -s -p "请输入游戏密码:" pass
    echo -e "\n"
    [ $pass != "qq123" ] && echo "密码错误,请重新输入密码" && continue
    echo -e "\n"
    echo "密码正确,您的密码是:$pass"
    break
    count=$((count + 1))
    if [ $count -ge 3 ]; then
      echo "您已连续输入错误 3 次,程序将暂停 60 秒"
      sleep 60
      count=0
    fi
  done

8. 总结

  1. 基本条件判断
  • 使用 test 命令或 [ ] 方括号进行条件判断。
  • 常用字符串比较操作符:=(等于)、!=(不等于)。
  1. 逻辑运算符
  • -a:逻辑与(两个条件都为真)。
  • -o:逻辑或(两个条件中有一个为真)。
  1. 字符串长度判断
  • -n:字符串长度不为 0。
  • -z:字符串长度为 0。
  1. 实际应用
  • 密码验证:检查密码是否为空或长度是否符合要求。
  • 年龄验证:检查输入是否为整数且在合理范围内。
  • 循环验证:允许用户多次输入,直到满足条件为止。
  • 错误限制:限制用户输入错误次数,防止暴力破解。

整数比较

1.整数比较

1.1 整数比较操作符

在 Bash 中,可以使用以下操作符进行整数比较:

操作符含义示例结果
-eq等于[ 10 -eq 10 ]成立
-ne不等于[ 10 -ne 10 ]不成立
-gt大于[ 10 -gt 10 ]不成立
-ge大于等于[ 10 -ge 10 ]成立
-lt小于[ 10 -lt 10 ]不成立
-le小于等于[ 10 -le 10 ]成立

1.2 示例代码

[ 10 -eq 10 ] && echo "成立" || echo "不成立"  # 输出:成立
[ 10 -ne 10 ] && echo "成立" || echo "不成立"  # 输出:不成立
[ 10 -gt 10 ] && echo "成立" || echo "不成立"  # 输出:不成立
[ 10 -ge 10 ] && echo "成立" || echo "不成立"  # 输出:成立
[ 10 -lt 10 ] && echo "成立" || echo "不成立"  # 输出:不成立
[ 10 -le 10 ] && echo "成立" || echo "不成立"  # 输出:成立

1.3 多条件比较

可以使用逻辑运算符 -a(逻辑与)和 -o(逻辑或)进行多条件比较。

  • 逻辑与 (-a)
  [ 10 -le 10 -a 5 -eq 5 ] && echo "成立" || echo "不成立"  # 输出:成立
  • 解释:10 -le 105 -eq 5 都为真,因此整体条件为真。
  • 逻辑或 (-o)
  [ 10 -le 10 -o 5 -eq 5 ] && echo "成立" || echo "不成立"  # 输出:成立
  • 解释:10 -le 10 为真,因此整体条件为真。

2. 磁盘使用率告警

2.1 功能描述

通过脚本监控磁盘使用率,如果使用率超过设定阈值(如 10%),则发送告警邮件。

2.2 实现步骤

步骤 1:开启邮箱邮件服务
  1. 登录邮箱:以 163 邮箱为例,登录 163 邮箱官网
  2. 获取授权码
  • 手机验证码登录邮箱后,进入「设置」->「客户端授权密码」,生成授权码。
  • 授权码将用于邮件客户端或脚本中代替密码。
步骤 2:安装 mailx 服务
  • CentOS 系统
  yum install mailx
  • Ubuntu 系统
  apt-get install mailutils
步骤 3:配置邮件发送参数

编辑配置文件 /etc/mail.rc,添加以下内容:

set from=hao1643251490@163.com  # 发件人邮箱
set smtp=smtps://smtp.163.com:465  # SMTP 服务器地址和端口
set smtp-auth-user=hao1643251490@163.com  # 发件人邮箱
set smtp-auth-password=FMfFBMtRtUN87K5U  # 授权码
set smtp-auth=login  # 认证方式
set ssl-verify=ignore  # 忽略 SSL 验证
set nss-config-dir=/etc/pki/nssdb/  # NSS 配置目录
步骤 4:测试邮件发送

使用以下命令测试邮件发送功能:

echo "测试内容" | mail -s "测试邮件" 123789@qq.com
  • 参数说明
  • -s "主题":邮件主题。
  • 123789@qq.com:收件人邮箱。
  • < /etc/hosts:邮件正文内容。
步骤 5:编写磁盘使用率告警脚本
#!/bin/bash
# 取出当前磁盘的使用率
use_disk=$(df -h | awk 'NR==6{print $(NF-1)}')

# 对比判断
if [ ${use_disk%\%} -gt 5 ]; then  # 如果使用率大于 5%
    echo "磁盘使用率到达 $use_disk, 请快速解决" > disk.txt
    mail -s "磁盘告警" hao1643251490@163.com < disk.txt
else
    echo "磁盘使用率正常 $use_disk"
fi
  • 代码说明
  • df -h:显示磁盘使用情况。
  • awk 'NR==6{print $(NF-1)}':提取第 6 行的倒数第二个字段(通常为使用率)。
  • ${use_disk%\%}:去掉字符串末尾的 % 符号,提取数字部分。
  • mail -s "磁盘告警" hao1643251490@163.com < disk.txt:发送告警邮件。

2.3 脚本运行

  1. 赋予脚本执行权限
   chmod +x disk_alert.sh
  1. 运行脚本
   ./disk_alert.sh

3. Bash 中的 if 语句

3.1 格式

if 语句用于条件判断,基本格式如下:

if [ 条件 ]; then
    # 条件成立时执行的代码
elif [ 条件 ]; then
    # 条件成立时执行的代码
else
    # 条件不成立时执行的代码
fi

3.2 示例

#!/bin/bash
# 判断两个数是否相等
if [ 1 -eq 1 ]; then
    echo "条件成立"
else
    echo "条件不成立"
fi

3.3 注意事项

  1. 条件表达式必须用 [ ] 包裹
  • [ 条件 ] 中的条件可以是整数比较、字符串比较等。
  1. 整数比较和字符串比较的区别
  • 整数比较:使用 -eq-ne-gt-ge-lt-le 等操作符。
  • 字符串比较:使用 =!= 操作符。

4. 总结

  1. 整数比较
  • 使用 -eq(等于)、-ne(不等于)、-gt(大于)、-ge(大于等于)、-lt(小于)、-le(小于等于)。
  • 可以结合逻辑运算符 -a(逻辑与)和 -o(逻辑或)进行多条件判断。
  1. 磁盘使用率告警
  • 使用 df 命令获取磁盘使用率。
  • 使用 if 语句判断是否超过阈值。
  • 使用 mail 命令发送告警邮件。
  1. Bash 中的 if 语句
  • 用于条件判断,支持多种条件表达式。
  • 常用于整数比较、字符串比较、文件属性判断等。

while循环

一、while循环核心语法

1. 基础语法结构

while [ 条件表达式 ]  # 或使用命令替代表达式
do
    执行的命令
done

2. 运行机制

  • 条件检查在前:先判断条件是否成立,再决定是否执行循环体
  • 条件为真时循环:当条件测试返回0(true)时持续执行
  • 条件更新在循环体内:需在循环体内改变条件变量,否则可能形成死循环

二、循环控制核心要素

1. 条件表达式类型

类型示例说明
数值比较[ $i -lt 10 ]小于10时循环
字符串判断[ "$str" != "exit" ]输入非exit时循环
文件检测[ -f "/tmp/lock" ]文件存在时循环
命令返回值while ping -c1 host根据ping结果决定循环

2. 死循环实现方式

# 方式1:使用true命令
while true
do
    echo  hello
done

# 方式2:空条件表达式
while :
do
   echo  hello
done

# 方式3:自增永真条件
while [ 1 -eq 1 ]
do
    echo  hello
done

三、循环控制进阶技巧

1. 流程控制语句

语句作用域说明
break立即终止当前循环可指定跳出层数:break 2
continue跳过本次循环继续下一次循环迭代
exit退出整个脚本可带退出状态码:exit 1

2. 嵌套循环示例

#!/bin/bash
while true  # 外层循环
do
    echo "外层循环开始"

    while true  # 内层循环
    do
        read -p "输入命令(q退出当前层,e完全退出): " cmd

        case $cmd in
            q) break ;;
            e) break 2 ;;
            *) echo "执行命令: $cmd" ;;
        esac
    done

    echo "返回到外层循环"
done
echo "脚本完全退出"

3. 执行频率控制

# 每秒执行一次监控
while true
do
    check_system_status
    sleep 1  # 关键延迟控制
done

# 带进度显示的下载模拟
i=0
while [ $i -le 100 ]
do
    printf "\r下载进度: %3d%%" $i
    sleep 0.1
    ((i++))
done

四、实战案例解析

案例1:服务状态监控

#!/bin/bash
SERVICE="nginx"
MAX_RETRY=3
retry_count=0

while [ $retry_count -lt $MAX_RETRY ]
do
    systemctl is-active --quiet $SERVICE
    if [ $? -ne 0 ]; then
        echo "$(date) 服务异常,尝试重启..."
        systemctl restart $SERVICE
        ((retry_count++))
        sleep 5
    else
        echo "服务运行正常"
        break
    fi
done

[ $retry_count -eq $MAX_RETRY ] && echo "警报:服务重启失败!"

案例2:交互式菜单系统

#!/bin/bash
while true
do
    clear
    echo "1. 显示系统信息"
    echo "2. 查看磁盘空间"
    echo "3. 退出程序"

    read -p "请输入选项: " choice
    case $choice in
        1) neofetch | less ;;
        2) df -h ;;
        3) break ;;
        *) echo "无效选项,请重试"; sleep 1 ;;
    esac
done

五、调试与优化技巧

1. 循环调试方法

  • 打印调试信息
  while [ $i -lt 5 ]; do
      echo "DEBUG: i=$i"
      # ...其他代码...
  done
  • 使用set命令
  set -x  # 开启调试模式
  while [...]
  do
      echo  hello
  done
  set +x  # 关闭调试模式

2. 性能优化建议

  • 避免高频循环:适当使用sleep控制循环间隔
  • 减少循环内计算:将固定计算移到循环外部
  • 及时释放资源:在循环内及时unset不再使用的变量

六、常见问题解决方案

问题1:无法退出的死循环

现象:脚本无限执行无法终止
排查点

  1. 检查条件变量是否在循环体内被修改
  2. 确认break语句是否被执行到
  3. 使用ctrl+c测试响应,检查是否有信号处理

问题2:多层循环控制失效

现象break 2未按预期跳出两层循环
解决方案

  1. 确认循环嵌套层级
  2. 使用标签控制跳转:
   outer_loop:
   while true
   do
       while true
       do
           break outer_loop
       done
   done

附:循环结构对比表

特性while循环for循环until循环
执行条件真条件时执行遍历集合时执行假条件时执行
适用场景未知次数的循环已知次数的循环等待条件变为真
典型用例持续监控、交互式菜单文件批量处理服务启动等待
退出控制break/continue同左同左
性能特点可能产生长时间运行通常有限次数类似while但条件相反

正则对比

1. 状态码 $? 的含义

  • $? 表示上一条命令的退出状态码。
  • 0 表示命令执行成功。
  • 0 表示命令执行失败。
[root@localhost ~]# root = ^r
echo $?  # 输出 1,表示命令执行失败

[[ root =~ ^r ]]
echo $?  # 输出 0,表示命令执行成功

2. 正则表达式的简单用法

2.1 匹配字符串的开头或结尾

  • ^:表示匹配字符串的开头。
  • $:表示匹配字符串的结尾。
[[ root =~ ^r ]]  # 匹配以 "r" 开头的字符串
echo $?  # 输出 0

[[ root =~ t$ ]]  # 匹配以 "t" 结尾的字符串
echo $?  # 输出 0

2.2 匹配任意单个字符

  • [a-Z]:表示匹配任意单个字母(a-Z 是错误写法,正确的应该是 [a-zA-Z])。
[[ root1 =~ [a-Z] ]]  # 匹配任意单个字母
echo $?  # 输出 0

2.3 匹配任意连续字符

  • [a-Z]+:表示匹配任意连续的字母(a-Z 是错误写法,正确的应该是 [a-zA-Z]+)。
[[ root1 =~ [a-Z]+ ]]  # 匹配任意连续的字母
echo $?  # 输出 0

2.4 匹配整个字符串

  • ^[a-Z]+$:表示从开头到结尾都是字母。
[[ root1 =~ ^[a-Z]+$ ]]  # 匹配整个字符串都是字母
echo $?  # 输出 0

3. 使用正则表达式进行验证

3.1 验证字符串

  • 确保输入的参数是纯字母字符串。
read -p "请输入你的姓名: " name
if [[ $name =~ ^[a-zA-Z]+$ ]]; then
  echo "你的姓名是: $name"
else
  echo "你的姓名必须是字符串"
  exit
fi

3.2 验证数字

  • 确保输入的参数是纯数字。
read -p "请输入你的年龄: " age
if [[ $age =~ ^[0-9]+$ ]]; then
  echo "你的年龄是: $age"
else
  echo "你的年龄必须是连续的数字"
  exit
fi

3.3 验证数字范围

  • 确保输入的数字在指定范围内(例如 1 到 100)。
read -p "请输入你的年龄: " age
if [[ $age =~ ^[0-9]+$ ]]; then
  if [ $age -gt 100 ]; then
    echo "年龄必须是 1-100 的数字"
    exit
  else
    echo "你的年龄是: $age"
  fi
else
  echo "你的年龄必须是连续的数字"
  exit
fi

4. 多个条件判断

  • 在 Bash 中,不能直接使用 -a-o 进行多个条件判断,需要使用 &&||
[[ root =~ ^r || hsc =~ c$]]
[[ root =~ ^r && hsc =~ c$ ]

总结

  1. $? 用于获取上一条命令的退出状态码。
  2. 正则表达式可以用来验证字符串的格式。
  3. 使用 if 语句结合正则表达式进行条件判断。
  4. 在 Bash 中,-a-o 已被弃用,应使用 &&||

if判断

1. 条件判断的基本语法

单分支结构

  • 语法
  if [ 条件表达式 ]; then
      执行的命令
  fi
  • 等效快捷写法
  [ 条件表达式 ] && 执行的命令
  • 特点:只有一个条件,条件成立时执行相应的命令。

双分支结构

  • 语法
  if [ 条件表达式 ]; then
      成立执行的命令
  else
      不成立执行的命令
  fi
  • 等效快捷写法
  [ 条件表达式 ] && 成立执行的命令 || 不成立执行的命令
  • 特点:条件成立执行一部分命令,不成立执行另一部分命令。

多分支结构

  • 语法
  if [ 条件表达式1 ]; then
      成立执行的命令
  elif [ 条件表达式2 ]; then
      成立执行的命令
  elif [ 条件表达式3 ]; then
      成立执行的命令
  else
      都不成立执行的命令
  fi
  • 特点:多个条件,依次判断,匹配到第一个成立的条件后执行对应命令,其余分支不再判断。

2. 条件判断的应用案例

案例1:判断两个数字的大小

  • 功能:比较两个数字的大小并输出结果。
  • 代码
  #!/bin/bash
  if [ $1 -gt $2 ]; then
      echo "$1大于$2"
  elif [ $1 -lt $2 ]; then
      echo "$1小于$2"
  else
      echo "$1等于$2"
  fi
  • 特点:使用多分支结构,依次判断大小关系。

案例2:增加判断是否为整数

  • 功能:确保输入的参数是整数,再比较大小。
  • 代码
  #!/bin/bash
  expr $1 + $2 &>/dev/null
  if [ $? -eq 0 ]; then
      if [ $1 -gt $2 ]; then
          echo "$1 > $2"
      elif [ $1 -lt $2 ]; then
          echo "$1 < $2"
      else
          echo "$1 = $2"
      fi
  else
      echo "必须输入整数"
      exit
  fi
  • 特点:嵌套判断,先验证输入是否为整数,再比较大小。

案例3:限制输入参数数量

  • 功能:确保输入两个参数,再判断是否为整数并比较大小。
  • 代码
  #!/bin/bash
  [ $# -ne 2 ] && echo "必须输入两个参数,以空格分隔" && exit

  expr $1 + $2 &>/dev/null
  if [ $? -eq 0 ]; then
      if [ $1 -gt $2 ]; then
          echo "$1 > $2"
      elif [ $1 -lt $2 ]; then
          echo "$1 < $2"
      else
          echo "$1 = $2"
      fi
  else
      echo "必须输入两个整数"
      exit
  fi
  • 特点:先检查参数数量,再验证是否为整数,最后比较大小。

案例4:猜数字游戏

  • 功能:生成随机数,让用户在5次机会内猜出正确数字。
  • 代码
  #!/bin/bash
  random_num=$((RANDOM % 100 + 1))  # 生成1-100的随机数
  chances=5

  echo "猜数字游戏:输入1-100的数字,你有 $chances 次机会!"

  while [ $chances -gt 0 ]; do
      read -p "请输入你的猜数字:" guess

      if [ $guess -eq $random_num ]; then
          echo "恭喜你,猜对了!"
          exit
      elif [ $guess -gt $random_num ]; then
          echo "猜大了!"
      else
          echo "猜小了!"
      fi

      chances=$((chances - 1))
      echo "你还剩下 $chances 次机会!"
  done

  echo "很遗憾,你没有猜对!正确答案是:$random_num"
  • 特点
  • 使用随机数生成目标数字。
  • 使用 while 循环限制猜数次数。
  • 使用多分支结构判断用户的猜测结果。

总结

  1. 条件判断的结构
  • 单分支:一个条件,一个结果。
  • 双分支:一个条件,两个结果。
  • 多分支:多个条件,多个结果。
  1. 案例应用
  • 数字大小比较:从简单到复杂,逐步增加输入验证和参数限制。
  • 猜数字游戏:结合随机数和循环,实现一个完整的交互式程序。
  1. 扩展知识
  • $RANDOM:生成随机数。
  • exit:终止脚本。
  • $?:获取上一条命令的返回值。

生成随机数

echo $((RANDOM%100+1))

3. RANDOM

RANDOM 是 Bash shell 中的一个内置环境变量,每次引用它时,都会返回一个范围在 0 到 32767 之间的随机整数。例如:

echo $RANDOM

每次执行上述代码时,都会在终端输出一个不同的随机整数。

4. RANDOM%100

% 是取模运算符,用于计算两个数相除的余数。RANDOM%100 表示将 RANDOM 变量生成的随机整数除以 100,然后取其余数。由于余数的范围是从 0 到除数减 1,所以 RANDOM%100 的结果范围是 0 到 99。例如:

echo $((RANDOM%100))

上述代码会输出一个 0 到 99 之间的随机整数。

5. RANDOM%100 + 1

RANDOM%100 的基础上加上 1,就可以将结果的范围从 0 到 99 调整为 1 到 100。例如:

echo $((RANDOM%100 + 1))

if菜单

1. 基础功能实现

功能描述

  • 提供一个菜单,用户可以选择查看系统信息(内存、磁盘、负载、IP)。
  • 使用 read 获取用户输入,并通过 if-elif 判断执行对应命令。

代码示例

#!/bin/bash
echo "1.查看系统内存"
echo "2.查看系统磁盘"
echo "3.查看系统负载"
echo "4.查看系统IP"

read -p "请输入查看信息的编号: " num
if [ $num -eq 1 ]; then
    free -h
elif [ $num -eq 2 ]; then
    df -h
elif [ $num -eq 3 ]; then
    uptime
elif [ $num -eq 4 ]; then
    curl cip.cc
fi

2. 美化菜单

功能描述

  • 使用 echo -e 和 ANSI 转义序列美化菜单字体颜色。
  • 使用 \t 对齐菜单项。

代码示例

#!/bin/bash
echo -e "\t\t\t\t\t\033[34m1.查看系统内存\033[0m"
echo -e "\t\t\t\t\t\033[34m2.查看系统磁盘\033[0m"
echo -e "\t\t\t\t\t\033[34m3.查看系统负载\033[0m"
echo -e "\t\t\t\t\t\033[34m4.查看系统IP\033[0m"

3. 循环显示菜单

功能描述

  • 使用 while true 循环,让用户可以多次选择功能,而不需要重新运行脚本。
  • 增加退出选项(编号 5)。

代码示例

#!/bin/bash
while true; do
    echo -e "\t\t\t\t\t\033[34m1.查看系统内存\033[0m"
    echo -e "\t\t\t\t\t\033[34m2.查看系统磁盘\033[0m"
    echo -e "\t\t\t\t\t\033[34m3.查看系统负载\033[0m"
    echo -e "\t\t\t\t\t\033[34m4.查看系统IP\033[0m"
    echo -e "\t\t\t\t\t\033[34m5.退出脚本\033[0m"

    read -p "请输入查看信息的编号: " num
    if [ $num -eq 1 ]; then
        free -h
    elif [ $num -eq 2 ]; then
        df -h
    elif [ $num -eq 3 ]; then
        uptime
    elif [ $num -eq 4 ]; then
        curl cip.cc
    elif [ $num -eq 5 ]; then
        exit
    fi
done

4. 使用函数封装菜单

功能描述

  • 将菜单部分封装成函数 fun,便于重复调用。
  • 增加菜单选项 6,用于重新显示菜单。

代码示例

#!/bin/bash
fun() {
    echo -e "\t\t\t\t\t\033[34m1.查看系统内存\033[0m"
    echo -e "\t\t\t\t\t\033[34m2.查看系统磁盘\033[0m"
    echo -e "\t\t\t\t\t\033[34m3.查看系统负载\033[0m"
    echo -e "\t\t\t\t\t\033[34m4.查看系统IP\033[0m"
    echo -e "\t\t\t\t\t\033[34m5.退出脚本\033[0m"
    echo -e "\t\t\t\t\t\033[34m6.菜单信息\033[0m"
}

fun

while true; do
    read -p "请输入查看信息的编号[6帮助]: " num
    if [ $num -eq 1 ]; then
        free -h
    elif [ $num -eq 2 ]; then
        df -h
    elif [ $num -eq 3 ]; then
        uptime
    elif [ $num -eq 4 ]; then
        curl cip.cc
    elif [ $num -eq 5 ]; then
        exit
    elif [ $num -eq 6 ]; then
        clear
        fun
    fi
done

5. 多条件判断优化

功能描述

  • 使用 = 替代 -eq,避免数字和字母混用时报错。
  • 增强脚本的健壮性。

代码示例

#!/bin/bash
fun() {
    echo -e "\t\t\t\t\t\033[34m1.查看系统内存\033[0m"
    echo -e "\t\t\t\t\t\033[34m2.查看系统磁盘\033[0m"
    echo -e "\t\t\t\t\t\033[34m3.查看系统负载\033[0m"
    echo -e "\t\t\t\t\t\033[34m4.查看系统IP\033[0m"
    echo -e "\t\t\t\t\t\033[34m5.退出脚本\033[0m"
    echo -e "\t\t\t\t\t\033[34m6.菜单信息\033[0m"
}

fun

while true; do
    read -p "请输入查看信息的编号[6帮助]: " num
    if [ "$num" = "1" ]; then
        free -h
    elif [ "$num" = "2" ]; then
        df -h
    elif [ "$num" = "3" ]; then
        uptime
    elif [ "$num" = "4" ]; then
        curl cip.cc
    elif [ "$num" = "5" ]; then
        exit
    elif [ "$num" = "6" ]; then
        clear
        fun
    fi
done

总结

  1. 基础功能:实现菜单选择和对应功能。
  2. 美化菜单:使用 ANSI 转义序列美化字体颜色。
  3. 循环显示:通过 while true 实现循环,用户可以多次选择功能。
  4. 函数封装:将菜单封装成函数,便于重复调用。
  5. 多条件判断:优化条件判断,避免数字和字母混用时报错。

if二级菜单

1. 初始脚本(无回退功能)

#!/bin/bash
while true
do
#clear
echo -e "\t\t\t\t\t\033[34m1.nginx\033[0m"
echo -e "\t\t\t\t\t\033[34m2.mysql\033[0m"
echo -e "\t\t\t\t\t\033[34m3.php\033[0m\n"

read -p "请输要安装软件的编号[1-3]: " num

if [ $num -eq 1 ];then
    echo -e "\t\t\t\t\t\033[35m1.nginx-1.20\033[0m"
    echo -e "\t\t\t\t\t\033[35m2.nginx-1.21\033[0m\n"

    read -p "请输入要安装nginx版本的编号:" num1
    if [ $num1 -eq 1 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
        echo -e "\t\t\t\t\t\033[35m正在安装...\033[0m\n"
    elif [ $num1 -eq 2 ];then
        echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
        echo -e "\t\t\t\t\t\033[35m正在安装...\033[0m\n"
    fi

elif [ $num -eq 2 ];then
        echo -e "\t\t\t\t\t\033[35m1.mysql-5.0\033[0m"
        echo -e "\t\t\t\t\t\033[35m2.mysql-8.0\033[0m\n"

        read -p "请输入要安装mysql版本的编号:" num2
        if [ $num2 -eq 1 ];then
                echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
                echo -e "\t\t\t\t\t\033[35m正在安装...\033[0m\n"
        elif [ $num2 -eq 2 ];then
                echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
                echo -e "\t\t\t\t\t\033[35m正在安装...\033[0m\n"
        fi

elif [ $num -eq 3 ];then
        echo -e "\t\t\t\t\t\033[35m1.php-7.1\033[0m"
        echo -e "\t\t\t\t\t\033[35m2.php-7.7\033[0m\n"

        read -p "请输入要安装mysql版本的编号:" num3
        if [ $num3 -eq 1 ];then
                echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
                echo -e "\t\t\t\t\t\033[35m正在安装...\033[0m\n"
        elif [ $num3 -eq 2 ];then
                echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
                echo -e "\t\t\t\t\t\033[35m正在安装...\033[0m\n"
        fi

fi
done
  • 功能概述:这是一个简单的软件安装菜单脚本,通过一个无限循环while true来持续显示菜单,让用户选择要安装的软件(Nginx、MySQL、PHP)。用户选择后,会进入对应的二级菜单,选择软件版本。根据用户选择的版本,脚本会提示正在配置仓库和安装软件,但实际上没有执行真正的安装操作。
  • 代码结构
  • 一级菜单:通过echo语句显示三个选项(Nginx、MySQL、PHP),并使用read命令读取用户输入的编号。
  • 二级菜单:根据用户在一级菜单中的选择,进入相应的二级菜单,显示不同版本的软件供用户选择。然后根据用户在二级菜单中的选择,输出安装提示信息。

2. 改进后的脚本(增加回退功能)

#!/bin/bash
while true
do
#clear

fun1(){
    echo -e "\t\t\t\t\t\033[34m1. Nginx\033[0m"
    echo -e "\t\t\t\t\t\033[34m2. MySQL\033[0m"
    echo -e "\t\t\t\t\t\033[34m3. PHP\033[0m\n"
}

fun1

ng(){
    echo -e "\t\t\t\t\t\033[35m1. Nginx-1.20\033[0m"
    echo -e "\t\t\t\t\t\033[35m2. Nginx-1.21\033[0m"
    echo -e "\t\t\t\t\t\033[35m3. 显示菜单\033[0m"
    echo -e "\t\t\t\t\t\033[35m4. 返回上一层菜单\033[0m\n"
}

mysql_menu(){
    echo -e "\t\t\t\t\t\033[35m1. MySQL-5.0\033[0m"
    echo -e "\t\t\t\t\t\033[35m2. MySQL-8.0\033[0m"
    echo -e "\t\t\t\t\t\033[35m3. 显示菜单\033[0m"
    echo -e "\t\t\t\t\t\033[35m4. 返回上一层菜单\033[0m\n"
}

php_menu(){
    echo -e "\t\t\t\t\t\033[35m1. PHP-7.1\033[0m"
    echo -e "\t\t\t\t\t\033[35m2. PHP-7.7\033[0m"
    echo -e "\t\t\t\t\t\033[35m3. 显示菜单\033[0m"
    echo -e "\t\t\t\t\t\033[35m4. 返回上一层菜单\033[0m\n"
}

read -p "请输要安装软件的编号[1-3]: " num

if [ $num -eq 1 ];then
    ng
    while true
    do
        read -p "请输入要安装 Nginx 版本的编号[3帮助]: " num1
        if [ $num1 -eq 1 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
            echo -e "\t\t\t\t\t\033[35m正在安装 Nginx-1.20...\033[0m\n"
        elif [ $num1 -eq 2 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
            echo -e "\t\t\t\t\t\033[35m正在安装 Nginx-1.21...\033[0m\n"
        elif [ $num1 -eq 3 ];then
            ng
        elif [ $num1 -eq 4 ];then
            fun1
            break
        else
            echo -e "\t\t\t\t\t\033[31m无效选项,请重新输入!\033[0m\n"
        fi
    done

elif [ $num -eq 2 ];then
    mysql_menu
    while true
    do
        read -p "请输入要安装 MySQL 版本的编号[3帮助]: " num2
        if [ $num2 -eq 1 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
            echo -e "\t\t\t\t\t\033[35m正在安装 MySQL-5.0...\033[0m\n"
        elif [ $num2 -eq 2 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
            echo -e "\t\t\t\t\t\033[35m正在安装 MySQL-8.0...\033[0m\n"
        elif [ $num2 -eq 3 ];then
            mysql_menu
        elif [ $num2 -eq 4 ];then
            fun1
            break
        else
            echo -e "\t\t\t\t\t\033[31m无效选项,请重新输入!\033[0m\n"
        fi
    done

elif [ $num -eq 3 ];then
    php_menu
    while true
    do
        read -p "请输入要安装 PHP 版本的编号[3帮助]: " num3
        if [ $num3 -eq 1 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
            echo -e "\t\t\t\t\t\033[35m正在安装 PHP-7.1...\033[0m\n"
        elif [ $num3 -eq 2 ];then
            echo -e "\t\t\t\t\t\033[35m正在配置仓库...\033[0m"
            echo -e "\t\t\t\t\t\033[35m正在安装 PHP-7.7...\033[0m\n"
        elif [ $num3 -eq 3 ];then
            php_menu
        elif [ $num3 -eq 4 ];then
            fun1
            break
        else
            echo -e "\t\t\t\t\t\033[31m无效选项,请重新输入!\033[0m\n"
        fi
    done

else
    echo -e "\t\t\t\t\t\033[31m无效选项,请重新输入!\033[0m\n"
fi
done
  • 功能概述:在原脚本基础上增加了回退到一级菜单的功能,同时增加了显示当前二级菜单的功能(选项 3)。用户在二级菜单中可以选择返回上一层菜单,或者重新查看当前二级菜单。
  • 代码结构
  • 函数定义
    • fun1:显示一级菜单。
    • ng:显示 Nginx 的二级菜单。
    • mysql_menu:显示 MySQL 的二级菜单。
    • php_menu:显示 PHP 的二级菜单。
  • 一级菜单交互:显示一级菜单(通过调用fun1函数),读取用户输入的编号。
  • 二级菜单交互 :根据用户在一级菜单中的选择,调用相应的二级菜单函数(如ng、mysql_menu、php_menu)。在二级菜单中,通过一个内部循环while true持续读取用户输入,根据用户选择执行不同操作:
    • 选择 1 或 2:输出安装对应版本软件的提示信息。
    • 选择 3:重新显示当前二级菜单。
    • 选择 4:调用fun1函数显示一级菜单,并使用break语句跳出内部循环,回到一级菜单的选择界面。
    • 其他输入:提示用户输入无效,要求重新输入。

函数-调用脚本

使用场景

在编写 Shell 脚本时,我们可能希望多个脚本之间共享函数、变量或其他代码逻辑。例如,你创建了一个名为 123.sh 的脚本,在其中定义了几个常用的函数 fun1ng,如下:

# 123.sh
fun1() {
    echo -e "\t\t\t\t\t\033[34m1. Nginx\033[0m"
    echo -e "\t\t\t\t\t\033[34m2. MySQL\033[0m"
    echo -e "\t\t\t\t\t\033[34m3. PHP\033[0m\n"
}

fun1

ng() {
    echo -e "\t\t\t\t\t\033[35m1. Nginx-1.20\033[0m"
    echo -e "\t\t\t\t\t\033[35m2. Nginx-1.21\033[0m"
    echo -e "\t\t\t\t\t\033[35m3. 显示菜单\033[0m"
    echo -e "\t\t\t\t\t\033[35m4. 返回上一层菜单\033[0m\n"
}

但是,fun1 函数在脚本末尾被直接调用,这可能不是你想要的结果。你希望在其他脚本中调用 123.sh 中的函数,比如 fun1ng

如何调用脚本中的函数

要实现这个功能,可以使用 source 命令(或其别名 .)来加载脚本文件,从而在当前脚本环境中定义这些函数。具体步骤如下:

1. 检查目标脚本是否存在

在调用 source 之前,确保目标脚本文件存在。可以使用以下命令进行检测:

[ -f /root/123.sh ] && source /root/123.sh

这里的 [ -f /root/123.sh ] 用于检测 /root/123.sh 文件是否存在,如果存在,则执行 source 命令。

2. 开始使用函数

在验证目标脚本存在后,就可以直接调用其中定义的函数。例如:

fun1
ng

示例完整脚本

假设你在另一个脚本 other_script.sh 中调用 123.sh 的函数,完整脚本可能如下:

#!/bin/bash

# 检查并加载 123.sh 脚本
[ -f /root/123.sh ] && source /root/123.sh

# 调用目标函数
fun1
ng

运行 other_script.sh 后,123.sh 中定义的 fun1ng 函数会被执行。

总结

  • 使用 source 命令可以让一个脚本获取另一个脚本中定义的函数和变量。
  • 通过 [ -f 文件路径 ] 检查目标脚本是否存在,可以避免因文件缺失导致的错误。
  • 这种方式可以帮助你复用脚本中的代码,提高脚本的模块化和可维护性。

case语句

1 什么是Case语句

case语句是Shell脚本中的一种多分支选择结构,用于根据不同的条件执行不同的代码块。它的语法结构清晰,适合处理多个条件分支的情况。

2 语法结构

case 变量 in
    模式1)
        命令1
        ;;
    模式2)
        命令2
        ;;
    ...
    *)
        默认命令
        ;;
esac


case $1 in
        1)
                echo 第一行
                ;;
        2)
                echo 第二行
                ;;
        *)
                echo 未匹配到
                ;;
esac
  • 变量:需要进行匹配的变量。
  • 模式:用于匹配变量的值。
  • 命令:当匹配成功时执行的命令。
  • ;;:结束当前分支。
  • *:默认分支,当所有模式都不匹配时执行。

3 示例说明

3.1 示例1:简单的Case语句
#!/bin/bash
case $1 in
    start)
        echo "Starting service..."
        ;;
    stop)
        echo "Stopping service..."
        ;;
    restart)
        echo "Restarting service..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        ;;
esac
  • 解释
  • 如果传入的参数是start,则执行启动服务的命令。
  • 如果传入的参数是stop,则执行停止服务的命令。
  • 如果传入的参数是restart,则执行重启服务的命令。
  • 如果传入的参数不是上述任何一个,则输出用法提示。
3.2 示例2:匹配多个值
#!/bin/bash
case $1 in
    start|restart)
        echo "Starting or restarting service..."
        ;;
    stop|reload)
        echo "Stopping or reloading service..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|reload}"
        ;;
esac
  • 解释
  • startrestart都会匹配到第一个分支。
  • stopreload都会匹配到第二个分支。
  • 其他参数会匹配到默认分支。
3.3 示例3:启动Nginx服务
  • 注意:
  • 使用which nginx找到nginx命令在哪里
  • 路径启动方式和system启动方式,只能使用一种
  • fun1 $1 变量参数给函数使用
1.说明
[root@web01 ~]# /usr/sbin/nginx              #启动nginx
[root@web01 ~]# /usr/sbin/nginx -s stop      #停止nginx
[root@web01 ~]# /usr/sbin/nginx -s reload    #重载nginx
[root@web01 ~]# /usr/sbin/nginx -s stop && sleep 1 && /usr/sbin/nginx   ##重启nginx停止睡一秒再启动

2.nginx启动脚本
[root@web01 ~]# cat lianxi.sh 
#!/bin/bash
case $1 in 
    start)
        /usr/sbin/nginx 
        ;;
    stop) 
        /usr/sbin/nginx -s stop
        ;;
    reload)
        /usr/sbin/nginx -s reload
        ;;
    restart) 
        /usr/sbin/nginx -s stop && sleep 1 && /usr/sbin/nginx 
        ;;
    startus)
        ng_st=`netstat -tupln | grep nginx | wc -l `
        if [ $ng_st -eq 0 ];then
            echo "nginx离线"
        else
            echo "nginx正常在线"
        fi
        ;;
    *)
        echo "请输入:sh $0 [start|stop|restart|reload|startus]"
        ;;
esac

3.完善nginx启动脚本,使用函数
[root@web01 ~]# cat lianxi.sh 
#!/bin/bash

fun1(){
if [ $? -eq 0 ];then
        echo "nginx $1 成功"
else
        echo "nginx $1 失败"
fi
}

case $1 in 
    start)
        /usr/sbin/nginx 
        fun1 $1
        ;;
    stop) 
        /usr/sbin/nginx -s stop
        fun1 $1
        ;;
    reload)
        /usr/sbin/nginx -s reload
        fun1 $1
        ;;
    restart) 
        /usr/sbin/nginx -s stop && sleep 1 && /usr/sbin/nginx 
        fun1 $1
        ;;
    startus)
        ng_st=`netstat -tupln | grep nginx | wc -l `
        if [ $ng_st -eq 0 ];then
            echo "nginx离线"
        else
            echo "nginx正常在线"
        fi
        ;;
    *)
        echo "请输入:sh $0 [start|stop|restart|reload|startus]"
        ;;
esac
3.4 示例4:用户登录跳板机
  • trap “” HUP TSTP INT #该命令表示不能退出脚本,Ctrl + c 也不能退出,用于只能在跳板机菜单中
  • 要求:
  • 参考jumpserver
  • 1.不同的角色 运维 开发 测试(可以注册)
  • 2.每个角色可以连接服务器不同
  • 3.用户名和密码
  • 4.密码忘记了?
#!/bin/bash
WEB01=10.0.0.7
WEB02=10.0.0.8
MySQL=10.0.0.51

while true
do
    echo -e "\t\t\t\t\033[2;34m1. $WEB01\033[0m"
    echo -e "\t\t\t\t\033[2;34m2. $WEB02\033[0m"
    echo -e "\t\t\t\t\033[2;34m3. $MySQL\033[0m"
    echo -e "\t\t\t\t\033[2;34m4. Exit\033[0m"
    read -p "Enter your choice: " num1
    case $num1 in
        1)
            ssh $WEB01
            ;;
        2)
            ssh $WEB02
            ;;
        3)
            ssh $MySQL
            ;;
        4)
            echo "Exiting..."
            exit
            ;;
        *)
            echo "Invalid choice. Please try again."
            ;;
    esac
done
  • 解释
  • 提供一个菜单,用户可以选择连接不同的服务器。
  • 1连接到WEB012连接到WEB023连接到MySQL4退出脚本。
  • 默认分支提示无效选择。
3.5 示例5:角色选择与权限控制
#!/bin/bash
WEB01=10.0.0.7
WEB02=10.0.0.8
MySQL=10.0.0.51

while true
do
    echo -e "\t\t\t\t\033[2;33m1. Admin\033[0m"
    echo -e "\t\t\t\t\033[2;33m2. Developer\033[0m"
    echo -e "\t\t\t\t\033[2;33m3. Exit\033[0m"
    read -p "Enter your role: " num
    case $num in
        1)
            echo "Admin role selected."
            # Add admin-specific commands here
            ;;
        2)
            echo "Developer role selected."
            # Add developer-specific commands here
            ;;
        3)
            echo "Exiting..."
            exit
            ;;
        *)
            echo "Invalid role. Please try again."
            ;;
    esac
done
  • 解释
  • 用户可以选择不同的角色,每个角色对应不同的权限和操作。
  • 1选择管理员角色,2选择开发者角色,3退出脚本。
  • 默认分支提示无效选择。
3.6查看系统信息

要求

  • 1.内存
  • 2.负载
  • 3.磁盘
  • 4.IP
[root@web01 ~]# cat xinxi.sh 
#!/bin/bash

fun1(){
echo -e "\t\t1或m  内存"
echo -e "\t\t2或u  负载"
echo -e "\t\t3或d  磁盘"
echo -e "\t\t4或i  ip"
}

fun1
read -p "请输入编号:" num1
case $num1 in
    1|m)
        free -h
        ;;
    2|u)
        uptime
        ;;
    3|d)
        df -h
        ;;
    4|i)
        echo 主机名`hostname`
        echo 外网ip地址:`hostname -I | awk '{print $1}'`
        echo 内网ip地址:`hostname -I | awk '{print $2}'`
        ;;
    *) 
        fun1
        echo 请输入正确的编号
        ;;
esac
3.7今天吃什么,随机
#!/bin/bash

# 今天中午吃什么
echo -e "\n\n\n今天的菜单有以下:"
echo "1.面条"
echo "2.包子"
echo "3.盖饭"
echo "4.麻辣烫"
echo -e "5.汉堡\n\n\n"

num1=$((RANDOM%5+1))

case $num1 in
        1)
                echo -e "今天吃:面条\n\n\n"
                ;;
        2)
                echo -e "今天吃:包子\n\n\n"
                ;;
        3)
                echo -e "今天吃:盖饭\n\n\n"
                ;;
        4)
                echo -e "今天吃:麻辣烫\n\n\n"
                ;;
        5)
                echo -e "今天吃:汉堡\n\n\n"
                ;;
        *)
                echo -e "随机数错误,请去吃西北风\n\n\n"
                ;;
esac

for循环

for循环是Shell脚本中常用的循环结构之一,用于重复执行一系列命令。它可以遍历字符串、数字、文件内容等,适用于各种批量操作和自动化任务。以下是for循环的详细解析和实际案例。

语法结构

for 变量 in 取值列表
do
    命令集合
done
  • 变量:循环变量,用于存储当前循环的值。
  • 取值列表:可以是字符串、数字、文件内容等,用于指定循环的取值范围。
  • 命令集合:循环体,每次循环时执行的命令。

取值列表的常见形式

1. 直接指定值

for i in a b c
do
    echo $i
done

for i in 1 2 3
do
    echo $i
done

for i in af rgeg greg
do
    echo $i
done

输出

复制

a
b
c

2. 支持数字序列

for i in {1..5}
do
    echo $i
done

输出

复制

1
2
3
4
5

3. 支持命令结果

for i in `seq 5`
do
    echo $i
done

输出

复制

1
2
3
4
5

实际案例

1. 批量创建用户

  • 支持拼接
  • 可以批量删除和创建用户

需求: 判断用户是否存在,不存在则创建,存在了提示存在。判断传参不能为空 。判断用户个数只能是整数

for i in {1..3}
do
    useradd user$i
    echo "User user$i has been created."
done

说明:该脚本会创建user1user2user3三个用户。

2. 批量处理文件

假设有一个文件list.txt,内容如下:

file1.txt
file2.txt
file3.txt

使用for循环处理文件:

for file in `cat list.txt`
do
    cp $file /backup/
done

说明:该脚本会将list.txt中的文件逐一复制到/backup/目录。

3. 计算1到100的和

  • 简单方法[root@lianxi ~]# seq -s + 100 | bc
sum=0
for i in {1..100}
do
    sum=$((sum + i))
done
echo $sum

输出

5050

4. 从1加到100

  • 简单方法[root@lianxi ~]# seq -s + 100 | bc
sum=0
for i in {1..100}
do
    sum=$((sum + i))
done
echo $sum

输出

5. 从1加到100(详细执行流程)

sum=0
for i in {1..100}
do
    sum=$((sum + i))
    echo "当前循环次数: $i, 当前和: $sum"
done
echo "最终结果: $sum"

输出

复制

当前循环次数: 1, 当前和: 1
当前循环次数: 2, 当前和: 3
当前循环次数: 3, 当前和: 6
...
最终结果: 5050

6. 命令行直接使用for

for i in a b c; do echo $i; done

输出

a
b
c

7. 判断一个网段哪些IP地址在线

for i in {1..254}
do
    ip=10.0.0.$i
    ping -c1 -W1 $ip &>/dev/null
    if [ $? -eq 0 ]; then
        echo "$ip 在线...."
    fi
done

说明:该脚本会遍历10.0.0.110.0.0.254的IP地址,判断哪些IP地址在线。

8. 后台并发执行

for i in {1..254}
do
    {
    ip=10.0.0.$i
    ping -c1 -W1 $ip &>/dev/null
    if [ $? -eq 0 ]; then
        echo "$ip 在线...."
    fi
    } &
done
wait # 在有逻辑关系下使用
echo done...

说明

  • 后台并发{} &实现
  • 有可能脚本结果在 echo done…之后输出。所以要加上wait,在有逻辑关系的时候使用wait等待结束

9. 批量创建和删除用户

read -p "请输入用户名称的前缀: " pre
read -p "请输入用户的个数: " num
for i in `seq $num`
do
    user=$pre$i
    id $user &>/dev/null
    if [ $? -eq 0 ]; then
        echo "$user 用户已存在"
    else
        useradd $user
        [ $? -eq 0 ] && echo "$user 创建成功"
        pass=`echo $((RANDOM))|md5sum|cut -c1-8`
        echo "A$pass"|passwd --stdin $user
        echo -e "$user\t$pass" >> user.txt
    fi
done

说明:该脚本会根据用户输入的前缀和数量,批量创建用户,并生成随机密码。

10.反向破解md5数字

1.cut命令
ls -l 输出结果中每行的前 8 个字符。
cat /etc/passwd | cut -c1-8

2.random随机数
生成一个随机数,默认是0-32767区间
echo $((RANDOM))
生成一个100以内的随机数,需要+1所以是101
echo $((RANDOM%101))

根据加密数据破解出数字

已知:

  • RANDOM随机数字0-32767 [root@shell ~]# echo $((RANDOM))|md5sum|cut -c1-8 1846f30e [root@shell ~]# echo $((RANDOM))|md5sum|cut -c1-8 66d1962b [root@shell ~]# echo $((RANDOM))|md5sum|cut -c1-8 5ebd5df2 [root@shell ~]# echo $((RANDOM))|md5sum|cut -c1-8 1d1c8eb1 [root@shell ~]# echo $((RANDOM))|md5sum|cut -c1-6 93be06
for i in `seq 32767`
do
        {
        num1=`echo $i | md5sum | cut -c1-8`
        num2=`echo $i | md5sum | cut -c1-6`
        if [ $num1 = 1846f30e ];then
                echo "1846f30e算出为$i"

        elif [ $num1 = 66d1962b ];then
                echo "66d1962b算出为$i"

        elif [ $num1 = 5ebd5df2 ];then
                echo "5ebd5df2算出为$i"

        elif [ $num1 = 1d1c8eb1 ];then
                echo "1d1c8eb1算出为$i"

        elif [ $num2 = 93be06 ];then
                echo "93be06算出为$i"
        fi
        echo $i
        echo $num1
        } &
done
wait
echo done.......

while 循环

一、死循环

  • 使用true,死循环
while true; do   #表达式
    # 循环体,包含要重复执行的命令
    命令 1
    命令 2
    ...
done

二、while 循环基础

2.1 基本语法

while 循环的基本结构如下:

while [ 条件判断 ]; do   #表达式
    # 循环体,包含要重复执行的命令
    命令 1
    命令 2
    ...
done

在每次循环开始前,Shell 会先检查 [ 条件判断 ] 是否为真。若为真,则执行循环体中的命令;执行完毕后,再次检查条件,若条件仍为真,则继续循环,直到条件为假时停止循环。

2.2 简单示例

以下是一个简单的 while 循环示例,用于打印数字 1 到 5:

#!/bin/bash
count=1
while [ $count -le 5 ]; do
    echo $count
    ((count++))
done

代码解释:

  • count=1:初始化计数器变量 count 为 1。
  • while [ $count -le 5 ]; do:当 count 小于等于 5 时,执行循环体。
  • echo $count:打印当前 count 的值。
  • ((count++)):将 count 的值加 1。
  • done:循环结束标志。

三、exit 关键字

3.1 功能概述

exit 关键字用于退出整个脚本,并且可以通过提供一个数字作为参数来返回特定的状态码。在 Shell 中,状态码通常用于表示脚本的执行结果,0 表示成功,非 0 表示失败。

  • 如果代码太多,可以加上exit 1 、exit2、exit3……来判断脚本是那里退出的,那里有问题

3.2 示例代码

#!/bin/bash
count=1
while [ $count -le 10 ]; do
    if [ $count -eq 6 ]; then
        echo "达到数字 6,退出脚本"
        exit 1
    fi
    echo $count
    ((count++))
done

代码解释:

  • count 的值等于 6 时,执行 exit 1 语句,脚本将立即终止,并返回状态码 1。
  • 可以使用 echo $? 命令查看上一个命令的返回状态码。例如,运行上述脚本后,紧接着输入 echo $?,将输出 1。

四、break 关键字

4.1 功能概述

break 关键字用于跳出当前所在的循环。若在嵌套循环中,默认跳出最内层的循环;也可以通过指定数字来跳出多级循环。

4.2 单级出示例

#!/bin/bash
count=1
while [ $count -le 10 ]; do
    if [ $count -eq 6 ]; then
        echo "达到数字 6,跳出循环"
        break
    fi
    echo $count
    ((count++))
done
echo "循环结束"

代码解释:

  • count 等于 6 时,执行 break 语句,跳出 while 循环。
  • 循环结束后,继续执行 echo "循环结束" 语句。

4.3 多级跳出示例

#!/bin/bash
outer_count=1
while [ $outer_count -le 3 ]; do
    inner_count=1
    while [ $inner_count -le 3 ]; do
        if [ $outer_count -eq 2 ] && [ $inner_count -eq 2 ]; then
            echo "在内层循环中满足条件,跳出两级循环"
            break 2
        fi
        echo "外层: $outer_count, 内层: $inner_count"
        ((inner_count++))
    done
    ((outer_count++))
done
echo "脚本继续执行"

代码解释:

  • 当外层循环的 outer_count 等于 2 且内层循环的 inner_count 等于 2 时,执行 break 2 语句,跳出两级循环。
  • 循环结束后,继续执行 echo "脚本继续执行" 语句。

五、continue 关键字

5.1 功能概述

continue 关键字用于忽略循环体中剩余的代码,直接回到循环开始处,重新进行条件判断并执行下一次循环。

5.2 代码

#!/bin/bash
count=1
while [ $count -le 10 ]; do
    if [ $((count % 2)) -eq 0 ]; then
        echo "遇到偶数 $count,跳过本次循环"
        continue
    fi
    echo $count
    ((count++))
done

代码解释:

  • count 为偶数时,执行 continue 语句,忽略 echo $count((count++)) 语句,直接回到循环开始处,对 count 进行下一次判断。
  • count 为奇数时,正常执行循环体中的代码

六、处理文件

  • while是按照一行一行处理,for是处理一个一个字符串
  • 使用read处理,done</etc/host处理
  • 可以用来处理用户密码在一行的文件
[root@lianxi ~]# cat sdf.sh 
#!/bin/bash
while read i
do
    echo $i
done</etc/hosts

[root@lianxi ~]# sh sdf.sh 
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@lianxi ~]# 

七、总结

通过上述示例,我们详细介绍了 while 循环以及 exitbreakcontinue 三个关键字的用法。exit 用于终止整个脚本并返回状态码;break 用于跳出当前或多级循环;continue 用于跳过本次循环的剩余代码,重新开始下一次循环。合理运用这些关键字可以使 Shell 脚本的逻辑更加灵活和高效。

函数

#!/bin/bash
fun(){
    if [ -f $1 l;then
        echo "$1文件存在"
    else
        echo"$1 文件不存在
    fi
fun $1

脚本传参给$1,fun调用fun(){}函数并附带$1参数



2.
#!/bin/bash
fun(){
    if [ -f $1 l;then
        echo "$1文件存在"
    else
        echo"$1 文件不存在
    fi
fun $2 $1


sh wer.sh /etc/psswad /etc/hosts
判断的是hosts,因为fun第一个参数是$2为hosts

3.
#!/bin/bash
file=$1
fun(){
    if [ -f $file l;then
        echo "$file文件存在"
    else
        echo"$file 文件不存在
    fi
fun

通过赋值fun调用fun(){}函数是就不需要带参数了,运行脚本的时候带参数就行,赋值给file




4.nginx启动脚本带函数传参


-----------------------



函数变量
1.


# 在函数中和不在函数中定义变量是相同的。
[root@shell ~]# cat fun.sh
#!/bin/bash
fun(){
    name=haoshuaicong
}
fun
echo $name
[root@shell ~]# sh fun.sh
haoshuaicong

Shell 编程中的函数

函数概述

函数是完成特定功能的代码块。Shell 中的函数类似于其他编程语言中的函数,可以提高代码的复用性,并使脚本更易于维护和理解。函数需要先定义后调用。

1. 函数定义

Shell 中有三种定义函数的方式:

  • 第一种定义方式
  fun1() {
      echo "第一种定义方式"
  }
  • 第二种定义方式
  function fun2 {
      echo "第二种定义方式"
  }
  • 第三种定义方式
  function fun3() {
      echo "第三种定义方式"
  }

2. 函数调用

函数定义后,通过函数名调用函数。例如:

fun1
fun2
fun3

3. 函数传参

Shell 函数支持传参,参数传输与脚本传参类似,使用 $1$2 等来访问。

注意:接收变量需要在函数后面接收变量fun /etc/passwd

以下是引用fun的参数:

#!/bin/bash

fun() {
    # 判断文件是否存在
    if [ -f "$1" ]; then
        echo "$1 文件存在"
    else
        echo "$1 文件不存在"
    fi
}

fun "/etc/passwd"  # 调用函数并传参

以下是引用脚本的参数,传给fun函数fun $1,函数里的$1是fun的

#!/bin/bash

fun() {
    # 判断文件是否存在
    if [ -f "$1" ]; then
        echo "$1 文件存在"
    else
        echo "$1 文件不存在"
    fi
}

fun $1  # 调用函数并传参

4. 函数变量

  • 全局变量:在函数中定义的变量默认是全局的,可以在函数外部访问。
  • 局部变量:使用 local 关键字定义的变量只在函数内部有效。
  • 注意:函数调用才执行,不调用即使默认全局变量也没有值。不加local全局

以下是一个使用局部变量的示例:

#!/bin/bash

fun() {
    local name="haoshuaicong"  # 定义局部变量
    echo $name
}

fun
echo $name  # 在函数外部访问不到局部变量

5. 函数返回值

函数可以通过 return 语句返回一个退出状态码(0-255),通常用于表示函数执行是否成功。$? 可以获取上一条命令的返回值。

以下是一个返回值的示例:

#!/bin/bash

check_file() {
    if [ -f "$1" ]; then
        return 0  # 文件存在,返回成功状态码
    else
        return 1  # 文件不存在,返回失败状态码
    fi
}

check_file "/etc/passwd"
echo "返回值: $?"  # 返回值为 0 表示成功

以下为&?和return的进阶玩法

  • 因为第二次判断&?上一条的返回结果,不是判断文件
  • 所以首先执行完的结果交给变量rc再又rc去判断
  • 用if的elif也不行else可以
#!/bin/bash
check_file() {
    if [ -f "$1" ]; then
        return 50  # 文件存在,返回成功状态码
    else
        return 100  # 文件不存在,返回失败状态码
    fi
}
check_file /etc/hosts

rc=$?
[ $rc -eq 50 ] && echo 文件存在
[ $rc -eq 100 ] && echo 文件不存在

6. 进阶示例

示例 1:实现文件存在性检查
#!/bin/bash

check_file() {
    if [ -f "$1" ]; then
        echo "$1 文件存在"
        return 0
    else
        echo "$1 文件不存在"
        return 1
    fi
}

# 调用函数并传参
check_file "/etc/passwd"
check_file "/etc/nonexistent_file"
示例2:实现加法计算器
#!/bin/bash

add() {
    echo "$[ $1 + $2 ]"  # 返回两数之和
}

result=$(add 10 20)
echo "结果是: $result"
示例3:实现计算器菜单
#!/bin/bash

# 定义计算函数
add() {
    echo "$[ $1 + $2 ]"
}

sub() {
    echo "$[ $1 - $2 ]"
}

# 显示菜单
menu() {
    echo "1. 加法"
    echo "2. 减法"
    echo "3. 退出"
}

# 主程序
while true; do
    menu
    read -p "请选择操作: " choice

    case $choice in
        1)
            read -p "请输入第一个数字: " num1
            read -p "请输入第二个数字: " num2
            echo "结果是: $(add $num1 $num2)"
            ;;
        2)
            read -p "请输入第一个数字: " num1
            read -p "请输入第二个数字: " num2
            echo "结果是: $(sub $num1 $num2)"
            ;;
        3)
            echo "退出程序"
            exit
            ;;
        *)
            echo "无效的选择,请重新输入"
            ;;
    esac
done

7. 注意事项

  1. 函数定义必须在调用之前。
  2. 函数名和变量名需要遵循命名规范,避免使用特殊字符和数字开头。
  3. 在函数中使用局部变量可以避免对全局变量的意外修改。
  4. 函数返回值仅支持退出状态码(0-255),如果需要传递复杂数据,可以考虑使用全局变量或通过标准输出传递。
  5. 最好写代码的时候解耦写,一小块一小块,用到的时候调用

Shell 编程中的数组

数组概述

在 Shell 脚本中,数组是一种存储多个值的数据结构。Shell 支持 普通数组(索引数组)和 关联数组(键值对数组)。数组可以用来简化脚本中的数据处理逻辑,尤其是在需要批量操作或存储一组相关数据时。

1. 普通数组(索引数组)

定义普通数组

普通数组使用索引来存储和访问元素。索引从 0 开始,可以手动指定索引,也可以让 Shell 自动分配。

普通数组只能用数字作为索引、关联数组可以数字,字符串作为索引、array[索引]=值、array[下标]=值

定义方式:

array[0]=value1
array[1]=value2
array[2]=value3

或者直接定义:

array=(value1 value2 value3 ...)

示例 1:手动定义数组

#!/bin/bash

array[0]="a"
array[1]="b"
array[2]="c"

echo "数组元素: ${array[0]}, ${array[1]}, ${array[2]}"

示例 2:直接定义数组

#!/bin/bash

array=("a" "b" "c")

echo "数组元素: ${array[0]}, ${array[1]}, ${array[2]}"
访问数组元素

通过 ${array[index]} 访问数组中的元素。

示例:

#!/bin/bash

array=("a" "b" "c")
echo "第一个元素: ${array[0]}"
echo "第二个元素: ${array[1]}"
获取数组长度

通过 ${#array[@]}${#array[*]} 获取数组的长度(元素个数)。*和@一样

注意:带#号是长度,不带#号输出的是字符串${#array[@]}和${array[@]}

示例:

#!/bin/bash

array=("a" "b" "c")
echo "数组长度: ${#array[@]}"

输出:数组长度: 3
遍历数组

使用 for 循环遍历数组中的所有元素。

示例1:

#!/bin/bash

array=("a" "b" "c")
for i in "${array[@]}"; do
    echo "数组元素: $i"
done


输出:
数组元素: a
数组元素: b
数组元素: c
删除数组元素

使用 unset 删除数组中的某个元素或整个数组。

示例:

#!/bin/bash

array=("a" "b" "c")
unset array[1]  # 删除索引为 1 的元素、unset array删除全部
echo "删除后数组: ${array[@]}"
遍历索引、下标

使用 for 循环遍历数组中的所有下标。

只循环了1次

示例1:

#!/bin/bash

array=("a" "b" "c")
for i in "${!array[@]}"; do
    echo "下标号: $i"
done


输出:a b c 

示例2:

使用下标号方式ehco

[root@lianxi /scripts/shuzu]# cat bianli.sh 
#!/bin/bash
array=(a b c )
for i in ${!array[*]} 
do
    echo ${array[$i]}
done
[root@lianxi /scripts/shuzu]# sh bianli.sh 
a
b
c
[root@lianxi /scripts/shuzu]# 

示例3:

使用下标号方式统计解释器数量

[root@lianxi /scripts/shuzu]# cat jieshiqi.sh 
#!/bin/bash

#统计解释器出现的次数

declare -A bash

while read num
do
    let bash[`echo $num | awk -F: '{print $NF}'`]++

done</etc/passwd
for i in ${!bash[*]}
do
    echo "$i 有${bash[$i]} 个"

done
[root@lianxi /scripts/shuzu]# sh jieshiqi.sh 
/bin/bash 有5 个
/bin/sync 有1 个
/sbin/halt 有1 个
/sbin/nologin 有31 个
/sbin/shutdown 有1 个

示例4:

使用下标号方式统计nginx访问ip的数量

[root@lianxi /scripts/shuzu]# cat nginx_ip.sh 
#!/bin/bash


#使用数组统计ip访问的次数

declare -A ip

while read ng_ip
do
    let ip[`echo $ng_ip | awk '{print $1}'`]++
done</var/log/nginx/access.log
for i in ${!ip[*]}
do
    echo "$i 访问了 ${ip[$i]} 次"
done
[root@lianxi /scripts/shuzu]#
[root@lianxi /scripts/shuzu]# sh nginx_ip.sh 
10.0.0.1 访问了 51 次
10.0.0.8 访问了 9114 次
127.0.0.1 访问了 1 次
[root@lianxi /scripts/shuzu]# 
查看下标、索引

使用!查看下标 ${!array[*]}

示例:

[root@lianxi /scripts/while]# array=("a" "b" "c")
[root@lianxi /scripts/while]# echo ${!array[*]}
0 1 2
[root@lianxi /scripts/while]# 
支持命令

使用文本文档内容定义数组元素

示例:

[root@lianxi /scripts/while]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@lianxi /scripts/while]#
[root@lianxi /scripts/while]# array=(`cat /etc/hosts`)
[root@lianxi /scripts/while]# echo ${array[*]}
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@lianxi /scripts/while]# 
支持混合定义,指定下标

指定下标 5和10 、其他下标默认往后排

示例:

[root@lianxi ~]# array=(a [5]=b c [10]=e f)
[root@lianxi ~]# echo ${array[*]}
a b c e f
[root@lianxi ~]# echo ${!array[*]}
0 5 6 10 11
[root@lianxi ~]# 

2. 关联数组(键值对数组)

查看素组

关联数组使用键值对存储数据,键可以是字符串或数字。

declare -a 查看当普通数组,declare -A 查看所有关联数组

declare -A array设置为关联数组,不设置不能使用字符串赋值

定义方式:

declare -A array
array[key1]=value1
array[key2]=value2

示例:

#!/bin/bash

declare -A array
array["key1"]="value1"
array["key2"]="value2"
array["key3"]="value3"

echo "关联数组: ${array[key1]}, ${array[key2]}, ${array[key3]}"
访问关联数组元素

通过 ${array[key]} 访问关联数组中的元素。

示例:

#!/bin/bash

declare -A array
array["key1"]="value1"
array["key2"]="value2"

echo "key1 的值: ${array[key1]}"
echo "key2 的值: ${array[key2]}"
获取关联数组的键和值

通过 ${!array[@]} 获取所有键,通过 ${array[@]} 获取所有值。

示例:

#!/bin/bash

declare -A array
array["key1"]="value1"
array["key2"]="value2"

echo "所有键: ${!array[@]}"
echo "所有值: ${array[@]}"
遍历关联数组

使用 for 循环遍历关联数组的键和值。

示例:

#!/bin/bash

declare -A array
array["key1"]="value1"
array["key2"]="value2"
array["key3"]="value3"

for key in "${!array[@]}"; do
    echo "键: $key, 值: ${array[$key]}"
done

3. 实际案例

案例 1:批量 Ping IP 地址

需求:检查一组 IP 地址是否在线。

脚本:

#!/bin/bash

# 定义数组
ips=(
"192.168.1.1" 
"192.168.1.2" 
"192.168.1.3" 
"192.168.1.4"
)

# 遍历数组并检查每个 IP
for ip in "${ips[@]}"; do
    ping -c 1 -W 1 $ip &>/dev/null
    if [ $? -eq 0 ]; then
        echo "$ip 在线"
    else
        echo "$ip 不在线"
    fi
done
案例 2:统计文件中每个字母出现的次数

需求:统计文件 1.txt 中每个字母出现的次数。

文件内容(1.txt):

复制

cat 1.txt
f
f
x
m
f
m

脚本:

#!/bin/bash
declare -A sex
for i in `cat 1.txt`
do
    let sex[$i]++
done
for i in ${!sex[*]}
do
    echo "$i出现了 ${sex[$i]} 次"
done

[root@hsc ~]# sh array.sh
x出现了 1 次
m出现了 2 次
f出现了 3 次

总结:

  • 在循环中定义sex数组,下标是fxm三个。通过let sex[$i]++方式覆盖赋值,出现了几次+1几次。最后得到3个值,因为只有3种字母,覆盖赋值了
  • $i为下标也就是fxm
  • ${sex[$i]}为值。也就是++后覆盖赋的值
  • 查看sex数组:declare -A sex=([x]=”1″ [m]=”2″ [f]=”3″ )
案例 3:统计客户端 IP 出现的次数

需求:统计 Nginx 访问日志中每个客户端 IP 出现的次数。

脚本:

#!/bin/bash

# 定义关联数组
declare -A ip_count

# 读取 Nginx 访问日志并统计每个 IP 出现的次数
while read -r line; do
    ip=$(echo $line | awk '{print $1}')
    ip_count["$ip"]=$((ip_count["$ip"] + 1))
done < "/var/log/nginx/access.log"

# 输出统计结果
for ip in "${!ip_count[@]}"; do
    echo "$ip 出现了 ${ip_count[$ip]} 次"
done
案例 4:统计解释器出现次数

4. 注意事项

  1. 数组索引从 0 开始:在访问数组元素时,索引从 0 开始。
  2. 关联数组需要声明:使用 declare -A 声明关联数组。
  3. 数组元素可以是字符串或数字:数组元素的类型没有限制。
  4. 数组长度动态变化:数组的长度可以根据需要动态变化。

shell面试题

你写过哪些shell脚本
1.你写过什么shell脚本
系统巡检脚本
系统优化脚本  yum仓库 时间同步 加大文件描述符。。。
软件安装脚本  编译安装
数据统计脚本  (awk grep sed 取业务的top10)
日志切割脚本   
辅助开发程序脚本  (开发写的程序懒,比如日志太多,小文件太多,跑着跑着程序挂了)
zabbix取值的脚本
启动脚本、停止脚本  比如参数太多,写脚本里,一个start或stop启停。nohupt python3.7 test.py  参数 1 3 4 5 & 

2.现场写脚本
统计字符串长度,小于3个字符的输出
巡检脚本
探测ip脚本
1-100相加脚本
死循环脚本
说一下 $0 $$ $#.....意思
系统巡检脚本
1. 系统基本信息
  • 操作系统信息
  • 查看操作系统发行版本(如 cat /etc/redhat-release)。
  • 获取内核版本(uname -r)。
  • 系统运行时间
  • 查看系统自上次启动以来的运行时长(uptime)。
  • 系统负载
  • 获取系统 1 分钟、5 分钟和 15 分钟的平均负载(uptime 中的负载信息)。
2. 硬件资源信息
  • CPU 信息
  • 查看 CPU 型号、核心数等(lscpu)。
  • 获取 CPU 使用率(top -bn1 | grep "Cpu(s)")。
  • 内存信息
  • 查看内存总量、已使用量、空闲量等(free -h)。
  • 检查内存使用率(通过 free 命令计算)。
  • 磁盘信息
  • 查看磁盘分区情况(fdisk -l)。
  • 获取各文件系统的使用情况(df -h)。
  • 查看磁盘 I/O 使用率(iostat)。
3. 网络配置信息
  • 网络接口信息
  • 查看所有网络接口的配置(ip addr)。
  • 获取网络接口的状态(是否启用、IP 地址、子网掩码等)。
  • 网络连接状态
  • 检查当前的网络连接情况(netstat -tulnp)。
  • 查看防火墙状态(systemctl status firewalld)。
4. 用户和权限信息
  • 用户信息
  • 列出系统中的所有用户(cat /etc/passwd)。
  • 查看当前登录的用户(who)。
  • 权限信息
  • 检查 /etc/sudoers 文件的权限和内容。
  • 查看关键目录(如 /root/etc 等)的权限设置。
5. 服务状态信息
  • 系统服务
  • 查看系统中所有服务的运行状态(systemctl list-units --type=service)。
  • 检查关键服务(如 sshdhttpd 等)是否正常运行。
  • 定时任务
  • 查看系统的定时任务(crontab -l -u root)。
6. 日志检查信息
  • 系统日志
  • 查看系统日志文件(如 /var/log/messages),检查是否有异常信息。
  • 服务日志
  • 查看关键服务的日志文件(如 /var/log/httpd/access_log 等),检查是否有错误记录。
7. 软件包信息
  • 已安装软件包
  • 列出系统中已安装的所有软件包(yum list installed)。
  • 软件包更新情况
  • 检查是否有可用的软件包更新(yum check-update)。
系统优化脚本
1. 系统内核参数优化
  • 内存管理
  • 调整 vm.swappiness 参数,降低系统使用交换空间的倾向,减少内存交换带来的性能损耗。例如,将其值设置为 10。
  • 优化 vm.vfs_cache_pressure 参数,调整虚拟文件系统缓存的回收策略,提高文件系统访问性能。
  • 网络参数
  • 优化 TCP/IP 相关参数,如 net.ipv4.tcp_syncookiesnet.ipv4.tcp_tw_reusenet.ipv4.tcp_tw_recycle 等,提升网络连接的处理能力和效率。
  • 调整 net.core.somaxconn 参数,增大系统允许的最大监听队列长度,提高高并发场景下的网络性能。
  • 文件系统参数
  • 调整 fs.file-max 参数,增大系统允许打开的最大文件描述符数量,避免因文件描述符耗尽导致的程序异常。
2. 系统服务优化
  • 禁用不必要的服务
  • 识别并禁用系统中不必要的服务,如 avahi-daemoncupsrpcbind 等,减少系统资源占用。
  • 使用 systemctl disable 命令禁用服务,并使用 systemctl stop 命令停止正在运行的服务。
  • 优化服务启动顺序
  • 根据系统实际需求,调整关键服务的启动顺序,确保系统能够快速、稳定地启动。
3. 用户和权限优化
  • 删除不必要的用户和组
  • 清理系统中不必要的用户和组,减少潜在的安全风险。
  • 强化用户密码策略
  • 配置 pam_cracklib 模块,设置密码复杂度要求,如密码长度、包含字符类型等。
  • 定期更新用户密码,设置密码有效期。
4. 防火墙优化
  • 精简防火墙规则
  • 检查并删除不必要的防火墙规则,只开放必要的端口和服务。
  • 使用 firewall-cmd 命令管理防火墙规则,确保规则的正确性和有效性。
  • 启用防火墙日志
  • 开启防火墙日志功能,记录防火墙的访问信息,便于后续的安全审计和问题排查。
5. 磁盘和文件系统优化
  • 文件系统挂载优化
  • 根据磁盘类型和使用场景,调整文件系统的挂载选项,如 noatimenodiratime 等,减少磁盘 I/O 操作,提高文件系统性能。
  • 磁盘 I/O 调度算法调整
  • 根据磁盘类型(如 HDD、SSD)选择合适的 I/O 调度算法,如 deadlinenoop,优化磁盘 I/O 性能。
6. 系统日志优化
  • 日志轮转配置
  • 优化 logrotate 配置,定期轮转系统日志文件,避免日志文件过大占用过多磁盘空间。
  • 日志存储优化
  • 将重要的系统日志存储到独立的磁盘分区或远程日志服务器,提高日志数据的安全性和可靠性。
7. 软件包和系统更新
  • 更新系统软件包
  • 使用 yum update 命令更新系统中所有已安装的软件包,确保系统安全补丁和功能更新及时应用。
  • 清理无用软件包
  • 使用 yum autoremove 命令清理系统中不再需要的依赖软件包,释放磁盘空间。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇