Shell脚本基础

最后更新: 2026-01-13 作者: Linux Team
页面目录
目录

Shell脚本概述

什么是Shell脚本?

Shell脚本是将多个Shell命令组合成一个可执行文件的文本文件,用于自动化重复性任务。

┌─────────────────────────────────────────────────────────────┐
│                    第一个Shell脚本                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   #!/bin/bash                                               │
│   # 我的第一个脚本                                          │
│                                                             │
│   echo "Hello, World!"                                     │
│   echo "Today is $(date +%Y-%m-%d)"                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

脚本创建与执行

# 创建脚本
vim script.sh

# 添加执行权限
chmod +x script.sh

# 执行脚本
./script.sh
bash script.sh           # 不需要执行权限

# 执行方式
sh script.sh            # 用sh执行
bash script.sh          # 用bash执行
source script.sh         # 在当前shell执行(会加载变量)
. script.sh              # 同source

Shebang

#!/bin/bash             # Bash脚本
#!/bin/sh               # POSIX脚本
#!/usr/bin/env bash     # 跨平台方式
#!/bin/bash -x          # 调试模式
#!/bin/bash -e          # 遇错即停

变量

变量基础

# 定义变量
name="Linux"
version=6.1
pi=3.14159

# 使用变量
echo $name
echo ${name}
echo "Welcome to ${name}!"

# 读取用户输入
read -p "Enter your name: " name
echo "Hello, $name"

# 只读变量
readonly PI=3.14159
declare -r CONST="constant"

# 删除变量
unset name

变量类型

# 字符串
str1="Hello"
str2='World'              # 单引号不解析变量
str3="Value: $str1"      # 双引号解析变量

# 数字
num1=100
num2=$((10 + 5))         # 算术运算
num3=$((num1 * 2))

# 数组
arr=(one two three four)
arr[0]="first"
echo ${arr[0]}
echo ${arr[@]}           # 所有元素
echo ${#arr[@]}          # 数组长度

# 关联数组
declare -A capitals
capitals["China"]="Beijing"
capitals["Japan"]="Tokyo"
echo ${capitals[China]}

特殊变量

$0          # 脚本名称
$1-$9       # 第1-9个参数
${10}       # 第10个参数
$#          # 参数个数
$@          # 所有参数(独立)
$*          # 所有参数(合并)
$?          # 上个命令的退出状态
$$          # 当前进程ID
$!          # 上个后台进程ID
$-          # 当前set的选项

环境变量

# 查看环境变量
env
printenv
echo $PATH

# 设置环境变量
export VAR="value"
VAR="value" && export VAR

# 追加到PATH
export PATH="$PATH:/new/path"

# 删除环境变量
unset VAR

条件判断

test命令

# 文件测试
[ -f file.txt ]          # 是普通文件
[ -d dir ]                # 是目录
[ -e file ]               # 存在
[ -r file ]               # 可读
[ -w file ]               # 可写
[ -x file ]               # 可执行
[ -L file ]               # 是符号链接
[ file1 -nt file2 ]      # file1比file2新
[ file1 -ot file2 ]      # file1比file2旧

# 字符串测试
[ -z "$str" ]            # 字符串为空
[ -n "$str" ]            # 字符串非空
[ "$s1" = "$s2" ]        # 相等
[ "$s1" != "$s2" ]       # 不等

# 数值测试
[ $n1 -eq $n2 ]          # 等于
[ $n1 -ne $n2 ]          # 不等
[ $n1 -gt $n2 ]          # 大于
[ $n1 -ge $n2 ]          # 大于等于
[ $n1 -lt $n2 ]          # 小于
[ $n1 -le $n2 ]          # 小于等于

# 逻辑操作
[ -f file ] && [ -r file ]
[ -f file ] || [ -d file ]
[ ! -f file ]

if语句

# 基本语法
if [ condition ]; then
    commands
fi

# if-else
if [ -f "$file" ]; then
    echo "File exists"
else
    echo "File not found"
fi

# if-elif-else
if [ "$score" -ge 90 ]; then
    echo "A"
elif [ "$score" -ge 80 ]; then
    echo "B"
elif [ "$score" -ge 70 ]; then
    echo "C"
else
    echo "D"
fi

# 嵌套if
if [ -f "$file" ]; then
    if [ -r "$file" ]; then
        cat "$file"
    else
        echo "Not readable"
    fi
fi

高级条件

# 使用[[ ]](推荐)
[[ -f file ]]                    # 文件存在
[[ -z "$str" ]]                  # 字符串为空
[[ "$str" =~ pattern ]]          # 正则匹配
[[ "$str" == *pattern* ]]        # 通配符匹配

# 正则表达式
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Valid email"
fi

# 组合条件
[[ -f file && -r file ]]         # AND
[[ -f file || -d file ]]         # OR
[[ ! -f file ]]                  # NOT

case语句

# 基本语法
case $variable in
    pattern1)
        commands
        ;;
    pattern2)
        commands
        ;;
    *)
        default commands
        ;;
esac

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

# 模式匹配
case $fruit in
    apple|banana)      # 或
        echo "Common fruit"
        ;;
    *)
        echo "Other"
        ;;
esac

循环

for循环

# 基本语法
for item in list; do
    echo $item
done

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

# 遍历数组
arr=(one two three)
for item in "${arr[@]}"; do
    echo $item
done

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

# 遍历目录
for dir in /home/*/; do
    echo "User directory: $dir"
done

# 读取文件行
while IFS= read -r line; do
    echo "$line"
done < file.txt

while循环

# 基本语法
while [ condition ]; do
    commands
done

# 读取用户输入
while true; do
    read -p "Enter a number (q to quit): " num
    [ "$num" = "q" ] && break
    echo $((num * 2))
done

# 读取文件
while IFS= read -r line; do
    echo "$line"
done < file.txt

# 计数器
count=0
while [ $count -lt 10 ]; do
    echo $count
    ((count++))
done

until循环

# 条件为false时继续
until [ condition ]; do
    commands
done

# 示例:等待服务启动
until curl -s http://localhost:8080 > /dev/null; do
    echo "Waiting for service..."
    sleep 2
done
echo "Service is ready!"

循环控制

# 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 -eq 5 ]; then
        continue
    fi
    echo $i
done

# 嵌套循环
for i in {1..3}; do
    for j in {1..3}; do
        if [ $j -eq 2 ]; then
            break 2    # 跳出两层循环
        fi
        echo "$i-$j"
    done
done

函数

函数定义

# 基本语法
function_name() {
    commands
    return value
}

# 或
function function_name {
    commands
    return value
}

# 示例
greet() {
    echo "Hello, $1!"
}
greet "World"

# 返回值
get_sum() {
    local sum=$(( $1 + $2 ))
    return $sum    # 只能返回0-255
}
get_sum 10 20
echo $?            # 获取返回值

函数参数

# 通过位置参数传递
my_func() {
    echo "Arg 1: $1"
    echo "Arg 2: $2"
    echo "All args: $@"
    echo "Arg count: $#"
}
my_func "hello" "world"

# 返回值方式
get_result() {
    local result="computed value"
    echo "$result"
}
result=$(get_result)

函数库

# common.sh - 函数库文件
#!/bin/bash

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

error() {
    echo "[ERROR] $1" >&2
}

check_file() {
    if [ ! -f "$1" ]; then
        error "File not found: $1"
        return 1
    fi
    return 0
}

# 使用函数库
source ./common.sh
log "Starting script"
check_file "/path/to/file" || exit 1

局部变量

# 局部变量
my_function() {
    local var="local"
    echo $var
}

# 全局变量
global_var="I'm global"

算术运算

# $(( ))
echo $((10 + 5))            # 15
echo $((10 - 5))            # 5
echo $((10 * 5))            # 50
echo $((10 / 5))            # 2
echo $((10 % 3))            # 1
echo $((10 ** 2))           # 100 (幂运算)

# 复合赋值
((i++))
((i--))
((i+=5))
((i-=5))
((i*=2))
((i/=2))

# expr命令
expr 10 + 5
expr 10 \* 5               # 乘法需要转义

# bc计算器(浮点数)
echo "scale=2; 10 / 3" | bc
echo "sqrt(100)" | bc -l

字符串处理

# 字符串长度
str="Hello World"
echo ${#str}

# 字符串切片
echo ${str:0}               # Hello World
echo ${str:6}               # World
echo ${str:0,5}             # Hello
echo ${str: -5}             # World (注意空格)

# 字符串替换
echo ${str/hello/hi}        # 替换第一个
echo ${str//o/O}            # 替换所有

# 删除子串
echo ${str#H*o}             # 从开头删除最短匹配
echo ${str##H*o}           # 从开头删除最长匹配
echo ${str%o*d}            # 从结尾删除最短匹配
echo ${str%%o*d}           # 从结尾删除最长匹配

# 字符串转换
echo ${str,,}              # 转小写
echo ${str^^}              # 转大写

数组操作

# 定义数组
arr=(one two three four)
arr[0]="first"
arr+=("five")              # 添加元素

# 访问元素
echo ${arr[0]}
echo ${arr[@]}             # 所有元素
echo ${!arr[@]}            # 所有索引

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

# 切片
echo ${arr[@]:1:2}         # 从索引1开始,取2个

# 关联数组
declare -A user
user["name"]="John"
user["email"]="john@example.com"

脚本调试

# 选项
set -x              # 显示执行的命令
set -v              # 显示原始输入
set -e              # 命令失败时退出
set -u              # 使用未定义变量时报错
set -n              # 检查语法但不执行
set -o pipefail    # 管道中任何命令失败都退出

# 在脚本中使用
#!/bin/bash
set -x              # 调试开启
commands
set +x              # 调试关闭

# 检测错误
set -euo pipefail
# -e: 遇错退出
# -u: 未定义变量报错
# -o pipefail: 管道失败报错

实战脚本

系统信息脚本

#!/bin/bash
# system_info.sh - 系统信息报告

set -euo pipefail

echo "=================================="
echo "       System Information"
echo "=================================="
echo

# 主机名
echo "Hostname: $(hostname)"
echo "Uptime: $(uptime -p)"
echo

# 系统信息
echo "OS: $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME)"
echo "Kernel: $(uname -r)"
echo "Architecture: $(uname -m)"
echo

# CPU
echo "CPU: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2 | sed 's/^ *//')"
echo "CPU Cores: $(nproc)"
echo

# 内存
free -h | grep Mem | awk '{printf "Memory: %s / %s\n", $3, $2}'
echo

# 磁盘
df -h | grep -E '^/dev/' | awk '{printf "Disk %s: %s / %s (Used: %s)\n", $1, $3, $2, $5}'
echo

# 网络
echo "IP Addresses:"
ip -4 addr show | grep inet | awk '{printf "  %s\n", $2}'
echo

# Top 5 进程
echo "Top 5 Processes by Memory:"
ps aux --sort=-%mem | head -6 | tail -5 | awk '{printf "  %s %s%% %s\n", $11, $4, $6}'

备份脚本

#!/bin/bash
# backup.sh - 自动备份脚本

set -euo pipefail

# 配置
SOURCE_DIRS="/home /etc /var/www"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
RETENTION_DAYS=7

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

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

log "Starting backup..."

tar -czf "${BACKUP_DIR}/${BACKUP_FILE}" $SOURCE_DIRS 2>/dev/null

# 验证备份
if [ -f "${BACKUP_DIR}/${BACKUP_FILE}" ]; then
    SIZE=$(du -h "${BACKUP_DIR}/${BACKUP_FILE}" | cut -f1)
    log "Backup completed: ${BACKUP_FILE} (${SIZE})"
else
    log "ERROR: Backup failed!"
    exit 1
fi

# 清理旧备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +${RETENTION_DAYS} -delete
log "Old backups cleaned (older than ${RETENTION_DAYS} days)"

log "Backup finished successfully!"

课后练习

实践任务
  1. 编写一个计算器脚本,支持加减乘除运算
  2. 编写一个日志分析脚本,统计日志中的错误数量
  3. 编写一个批量文件处理脚本
  4. 创建包含多个函数的函数库
  5. 使用set -x调试脚本
  6. 编写完整的系统信息报告脚本

下一篇预告:我们将学习Shell脚本进阶,掌握正则表达式、函数高级用法和数组操作。