01. Shell的作用
- 自动化安装操作系统:先部署reid。自动化安装操作系统,使用Kickstart或Cobbler,Cobbler依赖于Shell脚本。
- 系统优化:关闭SELinux,优化防火墙,配置YUM仓库,修改默认SSH端口,禁止root登录,时间同步,加大文件描述符,常用命令,内核优化。100台服务器优化怎么办?使用Ansible。四五台写成脚本。
- 安装服务:经常安装多个服务或不同版本号,将流程写成脚本。
- 优化服务:修改配置(代码变更),使用脚本。
- 监控系统:监控系统、网络、硬件、服务、日志、接口等,使用脚本取值。
- 日志切割:日志分析统计,辅助自研程序正常运行,使用定时任务+脚本。
02. 什么是Shell
Shell命令解释器,用户输入命令,解释器负贵翻译给内核,内核驱动硬件,返回she11
Linux默认的解释器是:bash sh
shell脚本
将多个可执行的命令写入到文件中,称为shel脚本,脚本中还包含了变量,表达式,循环判断语句等。
03. 编程语言
- 解释型语言、脚本语言:
- Shell:解释型语言,脚本语言
- Python:解释型语言,脚本语言
- 编译型语言:
- C、C++
- 其他:
- Java:JVM虚拟机
- Go
- HTML
04. 学习Shell用到的基础知识
- Linux常用的基础命令
- Vim编辑器
- awk、sed、grep
- Xshell工具
05. 如何学好Shell编程
- 可以读懂,明白Shell里面每行的含义
- 看懂后自己熟练地写出来,模仿
- 修改原有的Shell脚本
- 反复练习,多敲
- 切忌拿来即用,一定要转换成自己的
06. Shell脚本书写规范
- Shell脚本必须以
.sh
结尾 - 文件的开头加指定解释器
#!/bin/bash
或#!/bin/sh
- 脚本尽量带注释
- 脚本中成对的符号尽量一次性书写完毕
- 语法,一次性写完
- 注意不能出现中文符号
- 脚本尽量放到一个目录
07. 第一个Shell脚本,执行脚本
[root@shell ~]# cat test.sh
#!/bin/bash
# 版本 1.0
# 作者 haoshuaicong QQ 110
# 日志切割
echo "Hello world!!"
执行脚本的几种方式:
执行方式 | 命令格式 | 适用场景 | 优点 | 缺点 | 说明 |
---|---|---|---|---|---|
使用解释器执行 | sh test.sh 或 bash 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变量
- 什么是变量?
用一个固定的值来表示不固定的值称为变量。
name=haoshuaicong
name=oldgirl
x=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 ~]#
- 变量相关文件(按照执行顺序): 谁后执行谁生效,也就是倒过来,重复赋值覆盖了
/etc/profile
.bash_profile
只针对用户,家目录下的文件.bashrc
只针对用户,家目录下的文件/etc/bashrc
- 环境变量定义:
- 变量名称的定义规范:
- 使用字母、数字、下划线的组合
- 不能以数字开头
- 可以用字母或下划线开头
- 等号两端不允许有空格
- 见名知其意
- 变量值的定义:
- 定义数字:
[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 -I | awk ‘{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. 变量案例
- 定义IP地址:
[root@shell ~]# IP=$(hostname -I | awk '{print $1}')
[root@shell ~]# echo $IP
10.0.0.71
- 定义时间:
[root@shell ~]# Time=$(date +%F-%H-%M-%S)
[root@shell ~]# echo $Time
2025-01-21-11-56-08
- 变量中定义变量:
[root@shell ~]# name=haoshuaicong
[root@shell ~]# test=${name}_oldgirl
[root@shell ~]# echo $test
haoshuaicong_oldgirl
13. 重点
- 面试题:给你一台新的服务器,你的操作流程
面试题:给你一台新的机器,说一下你的工作流程?
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
- Shell的执行方式
sh
、路径执行和source
的区别- 变量名称的定义
- 变量分类:全局、局部
export
的作用- 变量值的定义:数字、字符串、命令定义
15. 总结
- Shell脚本是自动化运维的重要工具。
- 掌握Shell脚本的编写和执行方式是学习Linux系统管理的基础。
- 变量是Shell脚本中的重要组成部分,理解变量的定义和使用是编写高效脚本的关键。
echo -e使用说明
简单说明
echo -e
:
- 用于解析转义字符。
- 默认情况下,
echo
不会解析转义字符(如\n
、\t
等),但如果添加-e
参数,echo
会将这些转义字符作为特殊字符处理。 - 语法:
echo -e "内容"
\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 特殊位置变量
一、核心变量
$?
:上一条命令的退出状态
- 作用:判断命令是否执行成功。
- 值:
0
:成功。非0
:失败(具体值表示错误类型)。
- 示例:
ping -c1 www.example.com &>/dev/null if [ $? -eq 0 ]; then echo "网络可达" else echo "网络不可达" fi
$#
:脚本参数个数
- 作用:验证用户传入的参数数量。
- 示例:
# 必须传入2个参数 [ $# -ne 2 ] && echo "需传入2个参数" && exit 1
$0
:脚本名称
- 作用:获取脚本的路径或名称。脚本传参报错提示时,可以使用变量打印脚本名称和参数
- 示例:
echo "脚本名称:$0" # 输出示例:./script.sh 或 /path/to/script.sh echo "脚本: $0 [start|stop|reload]" echo "Hello World!!"
$n
:第n个参数
- 规则:
$1
为第一个参数,$2
为第二个,以此类推,脚本传参。 - 注意:超过
$9
需用大括号,如${10}
。 - 示例:
echo "第一个参数:$1" echo "第十个参数:${10}"
$$
:当前脚本的PID
- 用途:生成唯一临时文件、记录进程ID。
- 示例:
echo "当前脚本PID:$$" # 输出:1234
$!
:上一个后台进程的PID
- 用途:管理后台进程(如停止服务)。
- 示例:
sleep 1000 & echo "后台进程PID:$!"
二、进阶变量(了解)
$*
与$@
:所有参数
- 区别:
- 不加引号:两者相同,所有参数视为独立个体。
- 加引号:
"$*"
:合并为单个字符串(如"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]
$_
:上一个命令的最后一个参数
- 示例:
ls /tmp/file.txt echo $_ # 输出:/tmp/file.txt echo 1 2 3 5 echo $_ #输出5
三、关键总结
变量 | 作用 | 示例场景 |
---|---|---|
$? | 检查命令成功与否 | 错误处理、条件判断 |
$# | 验证参数数量 | 脚本参数合法性检查 |
$0 | 获取脚本名称 | 日志记录、帮助信息 |
$n | 获取第n个参数 | 脚本传参处理 |
$$ | 获取脚本PID | 生成临时文件、进程管理 |
$! | 获取后台进程PID | 停止后台任务 |
$* | 所有参数(合并为一个字符串) | 批量处理参数(不涉及空格) |
$@ | 所有参数(保持独立性) | 处理带空格的参数 |
四、常见问题
- 子Shell变量作用域问题
- 问题:子Shell中定义的变量,父Shell无法访问。
- 示例:
# script.sh Test=`whoami`
sh script.sh echo $Test # 输出为空
- 解决:使用
export
导出变量,或通过文件传递数据。
- 参数超过9个时的处理
- 错误写法:
$10
(实际解析为$1
后跟字符0
)。 - 正确写法:
${10}
、${11}
。
$*
和$@
在循环中的差异
- 关键点:使用双引号时的行为不同。
- 示例:
set -- "I am" lizhenya for i in "$*"; do echo $i; done # 输出:I am lizhenya for i in "$@"; do echo $i; done # 输出两行:I am 和 lizhenya
五、实战应用
- 参数合法性检查
#!/bin/bash
[ $# -ne 2 ] && echo "用法:$0 <用户名> <年龄>" && exit 1
echo "用户名:$1,年龄:$2"
- 后台进程管理
# 启动后台任务并记录PID
nohup ./service.sh > log.txt 2>&1 &
echo "服务PID:$!"
- 生成唯一临时文件
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
筛选
- 作用:使用
xargs
和awk
筛选字符串长度小于 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. 总结
- 统计字符串长度:
wc -L
:统计最长行的长度。awk '{print length}'
:打印字符串长度。expr length
:计算字符串长度。${#name}
:Bash 变量长度属性。
- 循环遍历字符串:
- 使用
for
循环逐个输出字符串中的单词。
- 筛选字符串长度小于 3 的字符串:
- 使用
for
循环结合条件判断。 - 使用
awk
筛选。 - 使用
xargs
和awk
筛选。 - 使用
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:替换
baidu
为hsc
。
echo ${url/baidu/hsc}
- 输出:
www.hsc.com
- 示例 2:替换所有
w
为A
。
echo ${url//w/A}
- 输出:
AAA.baidu.com
4. 总结
- 子串删除:
- 从前往后删除:
#${变量#模式}
、#${变量##模式}
。 - 从后往前删除:
%${变量%模式}
、%${变量%%模式}
。 - 应用场景:删除小数点部分、提取磁盘使用率等。
- 子串替换:
- 替换第一个匹配:
${变量/旧子串/新子串}
。 - 替换所有匹配:
${变量//旧子串/新子串}
。
- 复杂操作:
- 结合
awk
和sed
实现更复杂的文本处理。 - 使用
${变量%%模式}
和${变量%模式}
提取和处理字符串。
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
筛选
- 作用:使用
xargs
和awk
筛选字符串长度小于 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. 总结
- 简单整数运算:
expr
:适合简单的整数运算,但需要在操作符之间加空格。$[]
和$(( ))
:语法简洁,适合简单的整数运算。
- 变量运算:
let
:适合对变量进行赋值和运算,常用于循环中的变量自增或自减。
- 复杂运算:
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. 总结
- 文件判断操作符,可以用man查看:
-f
:判断是否为普通文件。-d
:判断是否为目录。-e
:判断文件是否存在。-r
:判断文件是否可读。-w
:判断文件是否可写。-x
:判断文件是否可执行。
- 逻辑运算符:
&&
:逻辑与。||
:逻辑或。-a
:逻辑与(旧语法)。-o
:逻辑或(旧语法)。
- 实际应用:
- 判断文件或目录是否存在。
- 动态创建目录。
- 脚本互相调用。
- 加载配置文件。
条件表达式-字符串比对
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. 总结
- 基本条件判断:
- 使用
test
命令或[ ]
方括号进行条件判断。 - 常用字符串比较操作符:
=
(等于)、!=
(不等于)。
- 逻辑运算符:
-a
:逻辑与(两个条件都为真)。-o
:逻辑或(两个条件中有一个为真)。
- 字符串长度判断:
-n
:字符串长度不为 0。-z
:字符串长度为 0。
- 实际应用:
- 密码验证:检查密码是否为空或长度是否符合要求。
- 年龄验证:检查输入是否为整数且在合理范围内。
- 循环验证:允许用户多次输入,直到满足条件为止。
- 错误限制:限制用户输入错误次数,防止暴力破解。
整数比较
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 10
和5 -eq 5
都为真,因此整体条件为真。 - 逻辑或 (
-o
):
[ 10 -le 10 -o 5 -eq 5 ] && echo "成立" || echo "不成立" # 输出:成立
- 解释:
10 -le 10
为真,因此整体条件为真。
2. 磁盘使用率告警
2.1 功能描述
通过脚本监控磁盘使用率,如果使用率超过设定阈值(如 10%),则发送告警邮件。
2.2 实现步骤
步骤 1:开启邮箱邮件服务
- 登录邮箱:以 163 邮箱为例,登录 163 邮箱官网。
- 获取授权码:
- 手机验证码登录邮箱后,进入「设置」->「客户端授权密码」,生成授权码。
- 授权码将用于邮件客户端或脚本中代替密码。
步骤 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 脚本运行
- 赋予脚本执行权限:
chmod +x disk_alert.sh
- 运行脚本:
./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 注意事项
- 条件表达式必须用
[ ]
包裹:
[ 条件 ]
中的条件可以是整数比较、字符串比较等。
- 整数比较和字符串比较的区别:
- 整数比较:使用
-eq
、-ne
、-gt
、-ge
、-lt
、-le
等操作符。 - 字符串比较:使用
=
或!=
操作符。
4. 总结
- 整数比较:
- 使用
-eq
(等于)、-ne
(不等于)、-gt
(大于)、-ge
(大于等于)、-lt
(小于)、-le
(小于等于)。 - 可以结合逻辑运算符
-a
(逻辑与)和-o
(逻辑或)进行多条件判断。
- 磁盘使用率告警:
- 使用
df
命令获取磁盘使用率。 - 使用
if
语句判断是否超过阈值。 - 使用
mail
命令发送告警邮件。
- 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:无法退出的死循环
现象:脚本无限执行无法终止
排查点:
- 检查条件变量是否在循环体内被修改
- 确认break语句是否被执行到
- 使用
ctrl+c
测试响应,检查是否有信号处理
问题2:多层循环控制失效
现象:break 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$ ]
总结
$?
用于获取上一条命令的退出状态码。- 正则表达式可以用来验证字符串的格式。
- 使用
if
语句结合正则表达式进行条件判断。 - 在 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
循环限制猜数次数。 - 使用多分支结构判断用户的猜测结果。
总结
- 条件判断的结构:
- 单分支:一个条件,一个结果。
- 双分支:一个条件,两个结果。
- 多分支:多个条件,多个结果。
- 案例应用:
- 数字大小比较:从简单到复杂,逐步增加输入验证和参数限制。
- 猜数字游戏:结合随机数和循环,实现一个完整的交互式程序。
- 扩展知识:
$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
总结
- 基础功能:实现菜单选择和对应功能。
- 美化菜单:使用 ANSI 转义序列美化字体颜色。
- 循环显示:通过
while true
实现循环,用户可以多次选择功能。 - 函数封装:将菜单封装成函数,便于重复调用。
- 多条件判断:优化条件判断,避免数字和字母混用时报错。
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
的脚本,在其中定义了几个常用的函数 fun1
和 ng
,如下:
# 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
中的函数,比如 fun1
和 ng
。
如何调用脚本中的函数
要实现这个功能,可以使用 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
中定义的 fun1
和 ng
函数会被执行。
总结
- 使用
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
- 解释:
start
和restart
都会匹配到第一个分支。stop
和reload
都会匹配到第二个分支。- 其他参数会匹配到默认分支。
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
连接到WEB01
,2
连接到WEB02
,3
连接到MySQL
,4
退出脚本。- 默认分支提示无效选择。
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
说明:该脚本会创建user1
、user2
和user3
三个用户。
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.1
到10.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
循环以及 exit
、break
和 continue
三个关键字的用法。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. 注意事项
- 函数定义必须在调用之前。
- 函数名和变量名需要遵循命名规范,避免使用特殊字符和数字开头。
- 在函数中使用局部变量可以避免对全局变量的意外修改。
- 函数返回值仅支持退出状态码(0-255),如果需要传递复杂数据,可以考虑使用全局变量或通过标准输出传递。
- 最好写代码的时候解耦写,一小块一小块,用到的时候调用
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. 注意事项
- 数组索引从 0 开始:在访问数组元素时,索引从 0 开始。
- 关联数组需要声明:使用
declare -A
声明关联数组。 - 数组元素可以是字符串或数字:数组元素的类型没有限制。
- 数组长度动态变化:数组的长度可以根据需要动态变化。
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
)。 - 检查关键服务(如
sshd
、httpd
等)是否正常运行。 - 定时任务
- 查看系统的定时任务(
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_syncookies
、net.ipv4.tcp_tw_reuse
、net.ipv4.tcp_tw_recycle
等,提升网络连接的处理能力和效率。 - 调整
net.core.somaxconn
参数,增大系统允许的最大监听队列长度,提高高并发场景下的网络性能。 - 文件系统参数
- 调整
fs.file-max
参数,增大系统允许打开的最大文件描述符数量,避免因文件描述符耗尽导致的程序异常。
2. 系统服务优化
- 禁用不必要的服务
- 识别并禁用系统中不必要的服务,如
avahi-daemon
、cups
、rpcbind
等,减少系统资源占用。 - 使用
systemctl disable
命令禁用服务,并使用systemctl stop
命令停止正在运行的服务。 - 优化服务启动顺序
- 根据系统实际需求,调整关键服务的启动顺序,确保系统能够快速、稳定地启动。
3. 用户和权限优化
- 删除不必要的用户和组
- 清理系统中不必要的用户和组,减少潜在的安全风险。
- 强化用户密码策略
- 配置
pam_cracklib
模块,设置密码复杂度要求,如密码长度、包含字符类型等。 - 定期更新用户密码,设置密码有效期。
4. 防火墙优化
- 精简防火墙规则
- 检查并删除不必要的防火墙规则,只开放必要的端口和服务。
- 使用
firewall-cmd
命令管理防火墙规则,确保规则的正确性和有效性。 - 启用防火墙日志
- 开启防火墙日志功能,记录防火墙的访问信息,便于后续的安全审计和问题排查。
5. 磁盘和文件系统优化
- 文件系统挂载优化
- 根据磁盘类型和使用场景,调整文件系统的挂载选项,如
noatime
、nodiratime
等,减少磁盘 I/O 操作,提高文件系统性能。 - 磁盘 I/O 调度算法调整
- 根据磁盘类型(如 HDD、SSD)选择合适的 I/O 调度算法,如
deadline
或noop
,优化磁盘 I/O 性能。
6. 系统日志优化
- 日志轮转配置
- 优化
logrotate
配置,定期轮转系统日志文件,避免日志文件过大占用过多磁盘空间。 - 日志存储优化
- 将重要的系统日志存储到独立的磁盘分区或远程日志服务器,提高日志数据的安全性和可靠性。
7. 软件包和系统更新
- 更新系统软件包
- 使用
yum update
命令更新系统中所有已安装的软件包,确保系统安全补丁和功能更新及时应用。 - 清理无用软件包
- 使用
yum autoremove
命令清理系统中不再需要的依赖软件包,释放磁盘空间。