欢迎光临
我们一直在努力

【Linux运维极简教程】08-Shell脚本编程入门

08 – Shell 脚本编程入门

一、Shell 脚本基础

1.1 第一个脚本

#!/bin/bash
# 这是注释
echo "Hello, World!"
echo "当前用户:$(whoami)"
echo "当前时间:$(date '+%Y-%m-%d %H:%M:%S')"

  • #!/bin/bash 称为 shebang,指定解释器
  • # 开头为注释
  • echo 输出内容
  • $() 执行命令并获取输出

1.2 执行脚本

# 方法一:添加执行权限后执行
chmod +x script.sh
./script.sh

# 方法二:用解释器执行(不需要执行权限)
bash script.sh

# 方法三:source 执行(在当前 Shell 中执行,变量不释放)
source script.sh
. script.sh # 等同于 source

1.3 脚本规范

#!/bin/bash
#==================================================================
# 脚本名称:backup.sh
# 功能描述:备份指定目录
# 作者:运维团队
# 创建日期:2024-01-15
# 使用方法:./backup.sh <源目录> <目标目录>
#==================================================================

set -e # 遇到错误即退出
set -u # 使用未定义变量报错
set -o pipefail # 管道中任一命令失败即失败
# 或合并写:set -euo pipefail

二、变量

2.1 变量定义与使用

# 变量定义(等号两边不能有空格!)
name="alice"
age=25
PI=3.14159

# 使用变量
echo $name
echo "我的名字是 ${name}" # 推荐加花括号

# 命令替换
current_date=$(date +%Y%m%d) # 推荐写法
current_date=`date +%Y%m%d` # 反引号写法(不推荐)

# 只读变量
readonly HOSTNAME="web-01"

# 删除变量
unset name

2.2 特殊变量

变量含义
$0 脚本名称
$1~$9 第 1~9 个参数
${10} 第 10 个及以后的参数
$# 参数个数
$* 所有参数(作为单个字符串)
$@ 所有参数(每个参数独立)
$? 上一条命令的退出状态码
$$ 当前脚本的 PID
$! 最近一个后台进程的 PID

#!/bin/bash
echo "脚本名称:$0"
echo "参数个数:$#"
echo "第一个参数:$1"
echo "所有参数:$@"
echo "上一条命令状态:$?"

2.3 字符串操作

str="Hello World"

# 字符串长度
echo ${#str} # 11

# 子字符串(从位置 0 开始,取 5 个字符)
echo ${str:0:5} # Hello

# 字符串替换
file="backup.tar.gz"
echo ${file%.gz} # 去掉后缀:backup.tar
echo ${file%%.*} # 去掉最长匹配:backup
echo ${file#backup.} # 去掉前缀:tar.gz
echo ${file##*.} # 取后缀:gz
echo ${file/backup/restore} # 替换第一个匹配:restore.tar.gz
echo ${file//a/A} # 替换所有匹配:bAckup.tAr.gz

2.4 默认值

# 如果 var 为空,使用默认值(不赋值)
echo ${var:-"default"}

# 如果 var 为空,赋值并使用默认值
echo ${var:="default"}

# 如果 var 为空,报错退出
echo ${var😕"var is not set"}

三、条件判断

3.1 if 语句

# 基本格式
if [ $age -ge 18 ]; then
echo "成年人"
elif [ $age -ge 13 ]; then
echo "青少年"
else
echo "儿童"
fi

3.2 条件测试

# 数值比较
[ $a -eq $b ] # 等于
[ $a -ne $b ] # 不等于
[ $a -gt $b ] # 大于
[ $a -lt $b ] # 小于
[ $a -ge $b ] # 大于等于
[ $a -le $b ] # 小于等于

# 字符串比较
[ "$str1" = "$str2" ] # 相等
[ "$str1" != "$str2" ] # 不相等
[ -z "$str" ] # 为空
[ -n "$str" ] # 非空

# 文件测试
[ -f file ] # 存在且是普通文件
[ -d dir ] # 存在且是目录
[ -e path ] # 存在
[ -r file ] # 可读
[ -w file ] # 可写
[ -x file ] # 可执行
[ -s file ] # 存在且非空
[ file1 -nt file2 ] # file1 比 file2 新
[ file1 -ot file2 ] # file1 比 file2 旧

# 逻辑运算
[ $a -gt 0 ] && [ $a -lt 100 ] # 与
[ $a -lt 0 ] || [ $a -gt 100 ] # 或
[ ! -f file ] # 非

# 推荐使用 [[ ]](bash 扩展,支持通配符和正则)
[[ $str == h* ]] # 通配符匹配
[[ $str =~ ^[0-9]+$ ]] # 正则匹配

3.3 case 语句

#!/bin/bash
read -p "请输入服务名: " service

case $service in
nginx|httpd)
echo "Web 服务器"
systemctl status $service
;;
mysql|mariadb)
echo "数据库服务"
systemctl status mysqld
;;
*)
echo "未知服务: $service"
echo "可选: nginx, httpd, mysql, mariadb"
;;
esac

四、循环

4.1 for 循环

# 列表遍历
for i in 1 2 3 4 5; do
echo "数字: $i"
done

# 范围遍历
for i in {1..10}; do
echo "数字: $i"
done

for i in {1..10..2}; do # 步长为 2
echo "数字: $i"
done

# 遍历命令输出
for file in /etc/*.conf; do
echo "配置文件: $file"
done

# C 语言风格
for ((i=0; i<10; i++)); do
echo "索引: $i"
done

# 遍历数组
servers=("web01" "web02" "db01")
for server in "${servers[@]}"; do
echo "服务器: $server"
done

4.2 while 循环

# 基本循环
count=0
while [ $count -lt 5 ]; do
echo "计数: $count"
((count++))
done

# 读取文件
while read line; do
echo "行内容: $line"
done < /etc/passwd

# 无限循环
while true; do
echo "运行中…"
sleep 5
done

4.3 循环控制

# break – 跳出循环
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
done

# continue – 跳过本次
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue
fi
echo "奇数: $i"
done

五、函数

# 定义函数
function greet() {
local name=$1 # local 变量仅在函数内有效
echo "你好, $name!"
return 0 # 返回状态码(0-255)
}

# 调用函数
greet "alice"

# 函数返回值通过命令替换获取
get_date() {
date +%Y%m%d
}
today=$(get_date)
echo "今天是 $today"

实用函数示例

# 日志函数
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1"
}

log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1" >&2
}

log_info "开始部署"
log_error "配置文件不存在"

# 检查命令是否存在
check_command() {
if ! command -v $1 &> /dev/null; then
echo "错误: $1 未安装"
exit 1
fi
}

check_command docker
check_command curl

六、数组

# 定义数组
fruits=("apple" "banana" "cherry")
nums=(1 2 3 4 5)

# 从命令输出创建数组
files=($(ls /etc/*.conf))

# 访问元素
echo ${fruits[0]} # apple
echo ${fruits[@]} # 所有元素
echo ${#fruits[@]} # 数组长度

# 遍历
for fruit in "${fruits[@]}"; do
echo $fruit
done

# 添加元素
fruits+=("orange")

# 删除元素
unset fruits[1] # 删除索引 1 的元素

七、输入输出

7.1 用户输入

# 读取输入
read -p "请输入用户名: " username
echo "你好, $username"

# 读取密码(不显示输入)
read -s -p "请输入密码: " password
echo ""
echo "密码长度: ${#password}"

# 读取多个值
read -p "输入姓名和年龄: " name age

# 限时读取
read -t 10 -p "10秒内输入: " input

7.2 重定向

# 输出重定向
command > file # 覆盖写入
command >> file # 追加写入

# 错误重定向
command 2> error.log # 只重定向错误
command > all.log 2>&1 # 标准输出和错误都重定向
command &> all.log # 同上(bash 简写)

# 输入重定向
command < input.txt

# /dev/null 黑洞
command > /dev/null 2>&1 # 丢弃所有输出

# Here Document
cat << EOF
这是多行文本
变量会被展开: $HOME
EOF

# Here String
grep "pattern" <<< "搜索这段文本"

八、实战脚本

脚本一:自动备份

#!/bin/bash
#==================================================================
# 脚本名称:auto_backup.sh
# 功能:备份指定目录,保留最近 7 天的备份
#==================================================================
set -euo pipefail

SOURCE_DIR="/data/app"
BACKUP_DIR="/backup"
KEEP_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/app_backup_${DATE}.tar.gz"

log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 检查源目录
if [ ! -d "$SOURCE_DIR" ]; then
log "错误:源目录 $SOURCE_DIR 不存在"
exit 1
fi

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 执行备份
log "开始备份 $SOURCE_DIR …"
tar -czf "$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)"

if [ $? -eq 0 ]; then
log "备份成功:$BACKUP_FILE ($(du -sh $BACKUP_FILE | awk '{print $1}'))"
else
log "备份失败!"
exit 1
fi

# 清理旧备份
log "清理 ${KEEP_DAYS} 天前的旧备份…"
find "$BACKUP_DIR" -name "app_backup_*.tar.gz" -mtime +$KEEP_DAYS -delete

log "备份完成"

脚本二:批量检查服务器状态

#!/bin/bash
#==================================================================
# 脚本名称:check_servers.sh
# 功能:批量检查多台服务器的连通性和负载
#==================================================================

SERVERS=("192.168.1.10" "192.168.1.11" "192.168.1.12")
PORT=22
TIMEOUT=3

echo "=========================================="
echo " 服务器状态检查 – $(date '+%Y-%m-%d %H:%M')"
echo "=========================================="
printf "%-16s %-8s %-12s %-10s\\n" "IP地址" "状态" "负载" "响应时间"
echo "——————————————"

for server in "${SERVERS[@]}"; do
# 检查端口
if nc -z -w $TIMEOUT $server $PORT 2>/dev/null; then
status="在线"

# 获取负载(通过 SSH)
load=$(ssh -o ConnectTimeout=$TIMEOUT $server "cat /proc/loadavg | awk '{print \\$1}'" 2>/dev/null)
load=${load:-"N/A"}

# ping 响应时间
rtime=$(ping -c 1 -W $TIMEOUT $server 2>/dev/null | grep 'time=' | awk -F'time=' '{print $2}' | awk '{print $1}')
rtime=${rtime:-"N/A"}"ms"
else
status="离线"
load="-"
rtime="-"
fi

printf "%-16s %-8s %-12s %-10s\\n" "$server" "$status" "$load" "$rtime"
done

echo "=========================================="

脚本三:服务部署脚本

#!/bin/bash
#==================================================================
# 脚本名称:deploy.sh
# 功能:部署 Web 应用
#==================================================================
set -euo pipefail

APP_NAME="webapp"
APP_DIR="/opt/$APP_NAME"
APP_USER="webapp"
PORT=3000
VERSION="${1:-latest}"

log() { echo "[$(date '+%H:%M:%S')] $1"; }

# 前置检查
for cmd in node npm; do
command -v $cmd >/dev/null 2>&1 || { echo "错误: $cmd 未安装"; exit 1; }
done

# 停止旧服务
if systemctl is-active –quiet $APP_NAME; then
log "停止旧服务…"
systemctl stop $APP_NAME
fi

# 备份当前版本
if [ -d "$APP_DIR" ]; then
log "备份当前版本…"
mv "$APP_DIR" "${APP_DIR}_backup_$(date +%Y%m%d_%H%M%S)"
fi

# 创建目录
mkdir -p "$APP_DIR"
cd "$APP_DIR"

# 拉取代码(此处用 git 示例)
log "拉取代码 (版本: $VERSION)…"
# git clone –branch $VERSION https://github.com/example/webapp.git .

# 安装依赖
log "安装依赖…"
npm install –production

# 构建项目
log "构建项目…"
npm run build

# 配置 systemd 服务
log "配置系统服务…"
cat > /etc/systemd/system/${APP_NAME}.service << EOF
[Unit]
Description=$APP_NAME
After=network.target

[Service]
Type=simple
User=$APP_USER
WorkingDirectory=$APP_DIR
ExecStart=/usr/bin/node $APP_DIR/server.js
Restart=on-failure
Environment=PORT=$PORT
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable $APP_NAME
systemctl start $APP_NAME

# 验证
sleep 2
if systemctl is-active –quiet $APP_NAME; then
log "部署成功!服务运行在端口 $PORT"
systemctl status $APP_NAME –no-pager
else
log "部署失败!"
journalctl -u $APP_NAME -n 20 –no-pager
exit 1
fi

九、调试技巧

# 方法一:bash -x 显示执行的每条命令
bash -x script.sh

# 方法二:脚本内开启调试
set -x # 开启调试
set +x # 关闭调试

# 方法三:只调试部分代码
set -x
# … 需要调试的代码 …
set +x

# 方法四:语法检查(不执行)
bash -n script.sh

十、小结

本篇介绍了 Shell 脚本编程的核心知识:变量、条件判断、循环、函数、数组、输入输出重定向以及实战脚本示例。Shell 脚本是运维自动化的基础工具,掌握后可以大幅提升工作效率。编写脚本时注意遵循 set -euo pipefail 规范,让脚本更健壮。


上一篇:07 – 磁盘管理与 LVM 下一篇:09 – 定时任务与自动化运维

赞(0)
未经允许不得转载:171主机测评 » 【Linux运维极简教程】08-Shell脚本编程入门
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址