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!"
课后练习
实践任务
- 编写一个计算器脚本,支持加减乘除运算
- 编写一个日志分析脚本,统计日志中的错误数量
- 编写一个批量文件处理脚本
- 创建包含多个函数的函数库
- 使用set -x调试脚本
- 编写完整的系统信息报告脚本
下一篇预告:我们将学习Shell脚本进阶,掌握正则表达式、函数高级用法和数组操作。