Shell脚本编程教程:从入门到实战

Shell脚本编程封面 - 终端命令行与脚本代码图

一、Shell脚本基础概念

什么是Shell?

Shell是Unix/Linux系统的命令解释器,它是用户与操作系统内核之间的桥梁。我们平时在终端输入的命令,实际上都是由Shell来解释执行的。常见的Shell类型包括:

  • Bash (Bourne Again Shell):Linux默认Shell,功能最强大
  • Zsh (Z Shell):功能丰富,可定制性强
  • Fish (Friendly Interactive Shell):用户友好,语法简单
Shell脚本示例配图 - Bash脚本代码展示

第一个Shell脚本

让我们从最简单的例子开始:

bash

#!/bin/bash
# 这是我的第一个Shell脚本
echo "Hello, Shell World!"

# 输出当前日期和时间
echo "当前时间是:$(date)"

保存为hello.sh,然后执行:

bash

chmod +x hello.sh    # 添加执行权限
./hello.sh           # 运行脚本

提示#!/bin/bash称为shebang,它告诉系统使用哪个解释器来执行脚本。

二、变量与数据类型

定义和使用变量

Shell中的变量定义非常灵活,不需要声明类型:

bash

#!/bin/bash

# 定义字符串变量
name="Linux"
version="Ubuntu 22.04"

# 定义数字变量
count=100

# 使用变量(两种方式)
echo "系统名称: $name"
echo "版本信息: ${version}"

# 只读变量
readonly PI=3.14159

# 删除变量
temp="临时数据"
unset temp

特殊变量

Shell提供了一些特殊变量来处理脚本参数:

bash

#!/bin/bash

echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "所有参数: $@"
echo "参数个数: $#"
echo "当前进程ID: $$"
echo "上一个命令退出状态: $?"

# 运行示例:./script.sh arg1 arg2

变量替换

bash

#!/bin/bash

var="hello"

# 默认值替换
echo "${var:-"默认值"}"      # 输出: hello
echo "${unset_var:-"默认值"}" # 输出: 默认值

# 设置默认值
echo "${var:="新值"}"        # 输出: hello,var保持不变
echo "${unset_var:="新值"}"  # 输出: 新值,unset_var被赋值

# 检查变量是否已设置
echo "${var:?"变量未设置"}"   # 输出: hello
# echo "${unset:?"变量未设置"}" # 会报错退出

三、条件判断与流程控制

基础条件判断

Shell中使用test命令或[ ]进行条件判断:

bash

#!/bin/bash

# 数值比较
num1=10
num2=20

if [ $num1 -eq $num2 ]; then
    echo "相等"
elif [ $num1 -lt $num2 ]; then
    echo "$num1 小于 $num2"
else
    echo "$num1 大于 $num2"
fi

# 常见比较操作符
# -eq: 等于 (equal)
# -ne: 不等于 (not equal)
# -lt: 小于 (less than)
# -le: 小于等于 (less or equal)
# -gt: 大于 (greater than)
# -ge: 大于等于 (greater or equal)

字符串判断

bash

#!/bin/bash

str1="hello"
str2="world"
str3="hello"

# 字符串比较
if [ "$str1" = "$str3" ]; then
    echo "字符串相等"
fi

if [ -z "$empty_str" ]; then
    echo "字符串为空"
fi

if [ -n "$str1" ]; then
    echo "字符串非空"
fi

# 常见字符串操作符
# =: 相等
# !=: 不等
# -z: 字符串长度为0
# -n: 字符串长度不为0

文件测试

bash

#!/bin/bash

file="/path/to/file.txt"

if [ -e "$file" ]; then
    echo "文件存在"
fi

if [ -f "$file" ]; then
    echo "是普通文件"
fi

if [ -d "$file" ]; then
    echo "是目录"
fi

if [ -r "$file" ]; then
    echo "可读"
fi

if [ -w "$file" ]; then
    echo "可写"
fi

if [ -x "$file" ]; then
    echo "可执行"
fi

case语句

对于多条件判断,case语句更加清晰:

bash

#!/bin/bash

grade="B"

case $grade in
    "A")
        echo "优秀"
        ;;
    "B")
        echo "良好"
        ;;
    "C")
        echo "及格"
        ;;
    *)
        echo "未知等级"
        ;;
esac

# 通配符示例
read -p "请输入命令: " cmd

case $cmd in
    start)
        echo "启动服务..."
        ;;
    stop)
        echo "停止服务..."
        ;;
    restart)
        echo "重启服务..."
        ;;
    *)
        echo "未知命令"
        ;;
esac

四、循环结构

for循环

bash

#!/bin/bash

# 遍历列表
for fruit in apple banana orange; do
    echo "我喜欢吃: $fruit"
done

# 使用C风格语法
for ((i=0; i<5; i++)); do
    echo "计数器: $i"
done

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

# 使用seq命令
for num in $(seq 1 5); do
    echo "数字: $num"
done

# 简化写法
for i in {1..10}; do
    echo $i
done

while循环

bash

#!/bin/bash

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

# 读取文件行
while read line; do
    echo "读取到: $line"
done < file.txt

# 无限循环(配合break使用)
while true; do
    read -p "输入q退出: " input
    if [ "$input" = "q" ]; then
        break
    fi
    echo "你输入了: $input"
done

# until循环(条件为假时继续)
num=1
until [ $num -gt 5 ]; do
    echo "until循环: $num"
    ((num++))
done

循环控制

bash

#!/bin/bash

# break和continue
for i in {1..10}; do
    if [ $i -eq 3 ]; then
        continue  # 跳过当前迭代
    fi
    if [ $i -eq 8 ]; then
        break     # 退出循环
    fi
    echo "数字: $i"
done

五、函数定义与使用

函数基础

bash

#!/bin/bash

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

# 调用函数
greet "小明"
greet "小红"

# 带返回值的函数
function add() {
    local sum=$(($1 + $2))
    return $sum  # 返回值只能是0-255
}

add 10 20
result=$?
echo "计算结果: $result"

函数作用域

bash

#!/bin/bash

variable="全局变量"

function demo() {
    local local_var="局部变量"
    variable="修改后的全局变量"
    
    echo "函数内: $variable"
    echo "函数内: $local_var"
}

demo
echo "函数外: $variable"
# echo "函数外: $local_var"  # 这会报错

函数参数

bash

#!/bin/bash

function process() {
    echo "参数个数: $#"
    echo "所有参数: $@"
    echo "第一个参数: $1"
    echo "第二个参数: $2"
}

process "arg1" "arg2" "arg3"

六、实战案例

案例1:自动备份脚本

bash

#!/bin/bash
# 自动备份脚本

# 配置
BACKUP_DIR="/backup"
SOURCE_DIR="/data"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"

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

# 执行备份
if tar -czf "${BACKUP_DIR}/${BACKUP_FILE}" "$SOURCE_DIR" 2>/dev/null; then
    echo "备份成功: ${BACKUP_FILE}"
    
    # 计算备份大小
    size=$(du -h "${BACKUP_DIR}/${BACKUP_FILE}" | cut -f1)
    echo "备份大小: $size"
    
    # 清理7天前的备份
    find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
    echo "清理旧备份完成"
else
    echo "备份失败"
    exit 1
fi

案例2:批量重命名文件

bash

#!/bin/bash
# 批量重命名脚本

directory=$1
prefix=$2

if [ -z "$directory" ] || [ -z "$prefix" ]; then
    echo "用法: $0 <目录> <文件名前缀>"
    exit 1
fi

cd "$directory" || exit 1

count=1
for file in *; do
    if [ -f "$file" ]; then
        extension="${file##*.}"
        new_name="${prefix}_${count}.${extension}"
        mv "$file" "$new_name"
        echo "重命名: $file -> $new_name"
        ((count++))
    fi
done

echo "完成,共处理 $((count-1)) 个文件"

案例3:系统监控脚本

bash

#!/bin/bash
# 系统监控脚本

echo "========== 系统监控报告 =========="
echo "生成时间: $(date)"
echo ""

# CPU使用率
echo "【CPU使用率】"
top -bn1 | head -5

# 内存使用
echo ""
echo "【内存使用】"
free -h

# 磁盘使用
echo ""
echo "【磁盘使用】"
df -h | grep -E "^/dev"

# 进程Top5
echo ""
echo "【CPU占用前5的进程】"
ps aux --sort=-%cpu | head -6

# 网络连接
echo ""
echo "【网络连接统计】"
netstat -an | grep ESTABLISHED | wc -l
echo "当前建立连接数"

七、调试技巧

调试选项

bash

#!/bin/bash

# 启用调试模式
# -x: 打印每条命令及其参数
# -v: 打印输入的行
# -n: 检查语法但不执行

set -x  # 启用命令跟踪
echo "这是调试信息"
set +x  # 关闭命令跟踪

# 另一种方式:运行时指定
# bash -x script.sh

错误处理

bash

#!/bin/bash

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

# 未定义的变量视为错误
set -u

# 管道中任何一个命令失败,整个管道失败
set -o pipefail

# 使用trap捕获错误
function error_handler() {
    echo "错误发生在第 $1 行"
}

trap 'error_handler $LINENO' ERR

# 测试
ls /nonexistent  # 会触发错误处理

八、常见问题与最佳实践

最佳实践

  1. 总是使用引号:避免变量为空时出现问题
    bash# 正确 if [ "$var" = "value" ]; then # 错误 if [ $var = "value" ]; then
  2. 使用[[ ]]替代[ ]:更强大的条件判断
    bash# 支持更多操作 if [[ $str =~ pattern ]]; then
  3. 添加错误处理:让脚本更健壮
    bashset -euo pipefail
  4. 添加使用说明:方便他人和自己理解
    bashfunction usage() { echo "用法: $0 [选项]" echo "选项:" echo " -h: 显示帮助" echo " -v: 详细模式" }
  5. 使用日志记录:便于问题排查
    bashLOG_FILE="/var/log/script.log" function log() { echo "[$(date)] $1" | tee -a "$LOG_FILE" }

性能优化

bash

#!/bin/bash

# 避免子shell,使用内联操作
# 慢:
for item in $(cat file.txt); do
    process $item
done

# 快:
while read item; do
    process $item
done < file.txt

# 使用数组而非字符串拼接
array=()
for i in {1..1000}; do
    array+=("item_$i")
done

九、总结

Shell脚本是每个Linux使用者必须掌握的技能。通过本教程的学习,你应该已经掌握了:

  • Shell变量的定义和使用
  • 条件判断和流程控制
  • 循环结构的各种用法
  • 函数的定义和调用
  • 实际场景中的脚本编写
  • 调试技巧和最佳实践

继续练习,多写脚本解决实际问题,你会发现Shell脚本的强大之处。推荐进一步学习正则表达式、awk、sed等文本处理工具,它们与Shell脚本配合使用可以完成更复杂的任务。

相关资源

阅读更多

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注