Skip to content

Latest commit

 

History

History
561 lines (419 loc) · 8.84 KB

File metadata and controls

561 lines (419 loc) · 8.84 KB

2.4 Shell 脚本

📍 导航返回目录 | 上一节:Python实战


基础语法

变量

#!/bin/bash

# 定义变量(等号两边不能有空格)
name="Alice"
age=30

# 使用变量
echo "Name: $name, Age: $age"
echo "Name: ${name}, Age: ${age}"  # 推荐使用花括号

# 只读变量
readonly PI=3.14159

# 删除变量
unset name

# 环境变量
export PATH=/usr/local/bin:$PATH

# 特殊变量
# $0    脚本名称
# $1-$9 位置参数
# $#    参数个数
# $@    所有参数
# $?    上一个命令的退出状态
# $$    当前进程PID

条件判断

# if-else
if [ $age -gt 18 ]; then
    echo "Adult"
elif [ $age -eq 18 ]; then
    echo "Just 18"
else
    echo "Minor"
fi

# 文件判断
if [ -f "file.txt" ]; then
    echo "File exists"
fi

# 字符串判断
if [ "$str1" = "$str2" ]; then
    echo "Equal"
fi

# 逻辑运算
if [ $a -gt 0 ] && [ $a -lt 10 ]; then
    echo "0 < a < 10"
fi

# case 语句
case $1 in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    restart)
        echo "Restarting..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

常用判断符号

符号 含义
-eq 等于
-ne 不等于
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
-f 是否为文件
-d 是否为目录
-e 是否存在
-r 是否可读
-w 是否可写
-x 是否可执行

循环

# for 循环
for i in 1 2 3 4 5; do
    echo "Number: $i"
done

# for 循环(C风格)
for ((i=0; i<10; i++)); do
    echo "i = $i"
done

# 遍历文件
for file in *.txt; do
    echo "Processing $file"
done

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

# until 循环
count=0
until [ $count -ge 5 ]; do
    echo "Count: $count"
    ((count++))
done

# break 和 continue
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        continue
    fi
    if [ $i -eq 8 ]; then
        break
    fi
    echo $i
done

函数

# 定义函数
function greet() {
    echo "Hello, $1!"
}

# 或简写为
greet() {
    echo "Hello, $1!"
}

# 调用函数
greet "Alice"

# 返回值(0-255)
check_file() {
    if [ -f "$1" ]; then
        return 0
    else
        return 1
    fi
}

# 使用返回值
if check_file "data.txt"; then
    echo "File exists"
fi

# 函数返回字符串(通过 echo)
get_timestamp() {
    echo $(date +%Y%m%d_%H%M%S)
}

timestamp=$(get_timestamp)
echo "Timestamp: $timestamp"

数组

# 定义数组
arr=(apple banana cherry)

# 访问元素
echo ${arr[0]}  # apple
echo ${arr[1]}  # banana

# 所有元素
echo ${arr[@]}
echo ${arr[*]}

# 数组长度
echo ${#arr[@]}

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

# 添加元素
arr+=(date)

# 关联数组(Bash 4.0+)
declare -A config
config[host]="localhost"
config[port]=8080

echo ${config[host]}
echo ${config[port]}

字符串处理

str="Hello, World!"

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

# 子串
echo ${str:0:5}   # Hello
echo ${str:7}     # World!

# 替换
echo ${str/World/Shell}  # Hello, Shell!
echo ${str//o/0}         # Hell0, W0rld! (全局替换)

# 删除
echo ${str#Hello}   # , World! (删除前缀)
echo ${str%World!}  # Hello,  (删除后缀)

# 大小写转换
echo ${str^^}  # HELLO, WORLD! (全部大写)
echo ${str,,}  # hello, world! (全部小写)

管道与重定向

# 标准输出重定向
echo "hello" > output.txt      # 覆盖
echo "world" >> output.txt     # 追加

# 标准错误重定向
command 2> error.log

# 同时重定向标准输出和标准错误
command > output.log 2>&1
command &> output.log  # 简写

# 管道
cat file.txt | grep "keyword" | wc -l

# Here Document
cat << EOF > config.txt
server {
    listen 80;
    server_name example.com;
}
EOF

# Here String
grep "keyword" <<< "$string"

实战脚本

部署脚本

#!/bin/bash
set -e  # 遇到错误立即退出

APP_NAME="myapp"
VERSION="1.0.0"
DEPLOY_DIR="/opt/app"

echo "=== 开始部署 $APP_NAME v$VERSION ==="

# 1. 备份旧版本
if [ -d "$DEPLOY_DIR" ]; then
    echo "备份旧版本..."
    mv $DEPLOY_DIR ${DEPLOY_DIR}_backup_$(date +%Y%m%d_%H%M%S)
fi

# 2. 创建目录
echo "创建部署目录..."
mkdir -p $DEPLOY_DIR

# 3. 解压文件
echo "解压应用..."
tar -xzf ${APP_NAME}-${VERSION}.tar.gz -C $DEPLOY_DIR

# 4. 安装依赖
echo "安装依赖..."
cd $DEPLOY_DIR
npm install --production

# 5. 启动服务
echo "启动服务..."
pm2 restart $APP_NAME || pm2 start app.js --name $APP_NAME

echo "=== 部署完成 ==="

日志轮转脚本

#!/bin/bash

LOG_DIR="/var/log/myapp"
MAX_DAYS=7

# 压缩旧日志
find $LOG_DIR -name "*.log" -mtime +1 -exec gzip {} \;

# 删除过期日志
find $LOG_DIR -name "*.gz" -mtime +$MAX_DAYS -delete

# 发送 HUP 信号,让进程重新打开日志文件
pkill -HUP myapp

echo "日志轮转完成"

系统监控脚本

#!/bin/bash

# CPU 使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)

# 内存使用率
mem_usage=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100}')

# 磁盘使用率
disk_usage=$(df -h / | tail -1 | awk '{print $5}' | cut -d'%' -f1)

# 告警阈值
CPU_THRESHOLD=80
MEM_THRESHOLD=85
DISK_THRESHOLD=90

# 检查并告警
if (( $(echo "$cpu_usage > $CPU_THRESHOLD" | bc -l) )); then
    echo "ALERT: CPU 使用率过高: ${cpu_usage}%"
fi

if (( $(echo "$mem_usage > $MEM_THRESHOLD" | bc -l) )); then
    echo "ALERT: 内存使用率过高: ${mem_usage}%"
fi

if [ $disk_usage -gt $DISK_THRESHOLD ]; then
    echo "ALERT: 磁盘使用率过高: ${disk_usage}%"
fi

错误处理

set 命令

#!/bin/bash

# 遇到错误立即退出
set -e

# 使用未定义变量时报错
set -u

# 管道命令任一失败即退出
set -o pipefail

# 组合使用
set -euo pipefail

# 调试模式
set -x

trap 命令

#!/bin/bash

# 清理函数
cleanup() {
    echo "清理临时文件..."
    rm -f /tmp/temp_*
}

# 捕获退出信号
trap cleanup EXIT

# 捕获错误
trap 'echo "错误发生在第 $LINENO 行"' ERR

# 捕获中断(Ctrl+C)
trap 'echo "脚本被中断"; exit 1' INT

# 主逻辑
echo "执行任务..."
# ...

错误处理示例

#!/bin/bash

# 方式1:检查退出状态
if ! command; then
    echo "命令执行失败"
    exit 1
fi

# 方式2:使用 || 操作符
command || { echo "命令执行失败"; exit 1; }

# 方式3:捕获输出和错误
output=$(command 2>&1) || {
    echo "错误信息: $output"
    exit 1
}

最佳实践

脚本模板

#!/bin/bash
#
# 脚本名称: deploy.sh
# 功能描述: 自动化部署脚本
# 作者: Alice
# 日期: 2024-01-01
#

set -euo pipefail

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查依赖
check_dependencies() {
    local deps=("git" "docker" "kubectl")
    for cmd in "${deps[@]}"; do
        if ! command -v $cmd &> /dev/null; then
            log_error "$cmd 未安装"
            exit 1
        fi
    done
}

# 主函数
main() {
    log_info "开始执行部署..."
    
    check_dependencies
    
    # 业务逻辑
    
    log_info "部署完成"
}

# 执行主函数
main "$@"

安全建议

  1. 使用引号:防止空格和特殊字符问题
rm -rf "$dir"  # 而不是 rm -rf $dir
  1. 检查参数
if [ $# -ne 2 ]; then
    echo "Usage: $0 <arg1> <arg2>"
    exit 1
fi
  1. 使用绝对路径
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
  1. 避免使用eval:存在注入风险

本章小结

Shell 脚本是后台开发必备技能,用于自动化部署、系统监控、日志处理等场景。

关键要点

  • ✅ 掌握变量、条件、循环、函数基础语法
  • ✅ 使用 set 和 trap 进行错误处理
  • ✅ 熟悉管道、重定向等高级特性
  • ✅ 编写可维护的脚本(注释、日志、错误处理)

扩展阅读


💡 思考题

  1. $@$* 有什么区别?
  2. set -e 的作用是什么?什么时候需要用?
  3. 如何在脚本中实现优雅的错误处理和清理?

⏮️ 上一节:Python实战 | ⏏️ 返回目录