1. 引言
Bash(Bourne Again SHell)是 Linux 和 macOS 系统中最常用的命令行解释器,也是编写自动化脚本的强大工具。掌握 Bash 脚本的基础语法是进行系统管理、任务自动化和 DevOps 工作的必备技能。
本文将系统性地介绍 Bash 脚本的基础语法,从最简单的脚本结构到变量、运算符、流程控制等核心概念,帮助读者快速上手 Bash 脚本编程。
2. Bash 脚本基础结构
2.1 脚本文件与执行权限
一个基本的 Bash 脚本通常以 .sh 为扩展名,文件开头需要指定解释器:
#!/bin/bash
# 这是一个 Bash 脚本示例
echo "Hello, World!"执行权限设置:
# 给脚本添加执行权限
chmod +x script.sh
# 执行脚本
./script.sh
# 或者使用 bash 命令直接执行
bash script.sh2.2 注释
Bash 使用 # 符号表示注释:
# 这是单行注释
: '
这是多行注释
可以跨越多行
'
# 行内注释
echo "Hello" # 这是行内注释3. 变量与数据类型
3.1 变量定义与使用
#!/bin/bash
# 定义变量(等号两边不能有空格)
name="John"
age=25
PI=3.14159
# 使用变量
echo "Name: $name"
echo "Age: $age"
echo "PI: $PI"
# 使用花括号避免歧义
echo "My name is ${name}Doe"3.2 变量类型
Bash 主要支持以下变量类型:
字符串变量:
str1="Hello"
str2='World'
str3=$str1$str2 # 字符串拼接
echo "$str3" # 输出: HelloWorld整数变量:
num1=10
num2=20
sum=$((num1 + num2)) # 算术运算
echo "Sum: $sum" # 输出: Sum: 30数组变量:
# 定义数组
fruits=("apple" "banana" "orange")
# 访问数组元素
echo "First fruit: ${fruits[0]}" # 输出: apple
echo "All fruits: ${fruits[@]}" # 输出所有元素
# 数组长度
echo "Number of fruits: ${#fruits[@]}" # 输出: 3
# 关联数组(Bash 4.0+)
declare -A person
person["name"]="Alice"
person["age"]=30
echo "Name: ${person["name"]}" # 输出: Alice3.3 特殊变量
#!/bin/bash
echo "脚本名称: $0" # 脚本名称
echo "第一个参数: $1" # 第一个参数
echo "第二个参数: $2" # 第二个参数
echo "所有参数: $@" # 所有参数列表
echo "参数个数: $#" # 参数个数
echo "上一个命令退出状态: $?" # 退出状态码(0表示成功)
echo "当前进程ID: $$" # 当前进程ID
echo "后台进程ID: $!" # 最后一个后台进程ID3.4 环境变量与局部变量
#!/bin/bash
# 环境变量(全局)
export GLOBAL_VAR="I'm global"
# 局部变量(默认)
local_var="I'm local"
# 在函数中使用 local 声明局部变量
my_function() {
local func_var="I'm local to function"
echo "$func_var"
}
my_function
echo "$func_var" # 这里会输出空,因为 func_var 是函数局部变量4. 输入与输出
4.1 基本输出
#!/bin/bash
# echo 命令
echo "Hello, World!"
echo -e "Line 1\nLine 2" # -e 启用转义字符
echo -n "No newline" # -n 不换行
# printf 命令(格式化输出)
printf "Name: %s\n" "Alice"
printf "Age: %d, Height: %.2f\n" 25 175.54.2 用户输入
#!/bin/bash
# read 命令读取用户输入
echo "请输入您的名字:"
read name
echo "Hello, $name!"
# 单行读取
read -p "请输入年龄: " age
echo "年龄: $age"
# 读取多个值
read -p "请输入姓名和年龄: " name age
echo "姓名: $name, 年龄: $age"
# 静默读取(用于密码)
read -s -p "请输入密码: " password
echo -e "\n密码已输入"4.3 文件重定向
#!/bin/bash
# 输出重定向
echo "Hello" > output.txt # 覆盖写入
echo "World" >> output.txt # 追加写入
# 输入重定向
wc -l < output.txt # 统计行数
# 错误重定向
command_not_exist 2> error.log # 将错误信息重定向到文件
command 2>&1 # 将错误重定向到标准输出
command &> all_output.log # 将标准和错误输出都重定向
# Here Document
cat << EOF
这是多行文本
第二行
第三行
EOF5. 运算符
5.1 算术运算符
#!/bin/bash
a=10
b=3
# 使用 $(( )) 进行算术运算
echo "加法: $((a + b))" # 13
echo "减法: $((a - b))" # 7
echo "乘法: $((a * b))" # 30
echo "除法: $((a / b))" # 3(整数除法)
echo "取余: $((a % b))" # 1
echo "指数: $((a ** b))" # 1000
# 使用 let 命令
let "c = a + b"
echo "c = $c" # 13
# 使用 expr 命令(较旧的方式)
result=$(expr $a + $b)
echo "expr 结果: $result" # 135.2 关系运算符
#!/bin/bash
x=10
y=20
# 数值比较
if [ $x -eq $y ]; then echo "x 等于 y"; fi
if [ $x -ne $y ]; then echo "x 不等于 y"; fi
if [ $x -lt $y ]; then echo "x 小于 y"; fi
if [ $x -le $y ]; then echo "x 小于等于 y"; fi
if [ $x -gt $y ]; then echo "x 大于 y"; fi
if [ $x -ge $y ]; then echo "x 大于等于 y"; fi
# 字符串比较
str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then echo "字符串相等"; fi
if [ "$str1" != "$str2" ]; then echo "字符串不相等"; fi
if [ -z "$str1" ]; then echo "字符串为空"; fi
if [ -n "$str1" ]; then echo "字符串非空"; fi
if [ "$str1" \< "$str2" ]; then echo "str1 在字典序中小于 str2"; fi5.3 逻辑运算符
#!/bin/bash
a=10
b=20
c=30
# 逻辑与
if [ $a -lt $b ] && [ $b -lt $c ]; then
echo "a < b < c"
fi
# 逻辑或
if [ $a -gt $b ] || [ $a -lt $c ]; then
echo "a > b 或 a < c"
fi
# 逻辑非
if ! [ $a -eq $b ]; then
echo "a 不等于 b"
fi
# 使用 [[ ]] 的扩展语法
if [[ $a -lt $b && $b -lt $c ]]; then
echo "使用 [[ ]] 语法: a < b < c"
fi5.4 文件测试运算符
#!/bin/bash
file="/etc/passwd"
dir="/tmp"
# 文件测试
if [ -e "$file" ]; then echo "文件存在"; fi
if [ -f "$file" ]; then echo "是普通文件"; fi
if [ -d "$dir" ]; then echo "是目录"; fi
if [ -s "$file" ]; then echo "文件大小大于0"; fi
if [ -r "$file" ]; then echo "文件可读"; fi
if [ -w "$file" ]; then echo "文件可写"; fi
if [ -x "$file" ]; then echo "文件可执行"; fi
# 特殊文件测试
if [ -L "$file" ]; then echo "是符号链接"; fi
if [ -b "$file" ]; then echo "是块设备文件"; fi
if [ -c "$file" ]; then echo "是字符设备文件"; fi
if [ -p "$file" ]; then echo "是命名管道"; fi6. 字符串操作
6.1 字符串长度与子串
#!/bin/bash
str="Hello World"
# 字符串长度
echo "长度: ${#str}" # 11
# 提取子串
echo "从索引2开始: ${str:2}" # llo World
echo "从索引2开始取5个字符: ${str:2:5}" # llo W
# 从右边开始提取
echo "最后5个字符: ${str: -5}" # World6.2 字符串替换
#!/bin/bash
str="apple orange apple banana"
# 替换第一个匹配
echo "${str/apple/pear}" # pear orange apple banana
# 替换所有匹配
echo "${str//apple/pear}" # pear orange pear banana
# 前缀匹配替换
echo "${str/#apple/pear}" # pear orange apple banana
# 后缀匹配替换
echo "${str/%banana/grape}" # apple orange apple grape6.3 字符串删除
#!/bin/bash
str="Hello World Hello Bash"
# 删除最短匹配的前缀
echo "${str#Hello}" # " World Hello Bash"
# 删除最长匹配的前缀
echo "${str##*Hello}" # " Bash"
# 删除最短匹配的后缀
echo "${str% Bash}" # "Hello World Hello"
# 删除最长匹配的后缀
echo "${str%% World*}" # "Hello"6.4 大小写转换
#!/bin/bash
str="Hello World"
# 转换为大写
echo "${str^^}" # HELLO WORLD
echo "${str^^[hw]}" # HEllo World (仅h,w转大写)
# 转换为小写
echo "${str,,}" # hello world
echo "${str,,[HW]}" # hello world (仅H,W转小写)
# 首字母大写
echo "${str^}" # Hello World7. 数组操作
7.1 数组基本操作
#!/bin/bash
# 定义数组
colors=("red" "green" "blue" "yellow")
# 访问元素
echo "第一个颜色: ${colors[0]}" # red
echo "所有颜色: ${colors[@]}" # red green blue yellow
# 修改元素
colors[1]="purple"
echo "修改后: ${colors[@]}" # red purple blue yellow
# 添加元素
colors+=("orange")
echo "添加后: ${colors[@]}" # red purple blue yellow orange
# 删除元素
unset colors[2]
echo "删除后: ${colors[@]}" # red purple yellow orange7.2 数组遍历
#!/bin/bash
fruits=("apple" "banana" "orange" "grape")
# 使用 for 循环遍历
echo "方法1: 直接遍历"
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
echo -e "\n方法2: 使用索引"
for i in "${!fruits[@]}"; do
echo "索引 $i: ${fruits[i]}"
done
echo -e "\n方法3: C风格for循环"
for ((i=0; i<${#fruits[@]}; i++)); do
echo "水果 $((i+1)): ${fruits[i]}"
done7.3 关联数组
#!/bin/bash
# 声明关联数组(Bash 4.0+)
declare -A student
# 添加键值对
student["name"]="Alice"
student["age"]=20
student["grade"]="A"
# 访问值
echo "学生姓名: ${student["name"]}"
echo "学生年龄: ${student["age"]}"
# 遍历关联数组
echo -e "\n所有信息:"
for key in "${!student[@]}"; do
echo "$key: ${student[$key]}"
done8. 函数
8.1 函数定义与调用
#!/bin/bash
# 函数定义
greet() {
echo "Hello, $1!"
}
# 函数调用
greet "Alice" # 输出: Hello, Alice!
greet "Bob" # 输出: Hello, Bob!
# 带返回值的函数
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
# 调用并获取返回值
result=$(add 10 20)
echo "10 + 20 = $result" # 输出: 10 + 20 = 308.2 函数参数
#!/bin/bash
# 函数参数
show_info() {
echo "函数名称: $0" # 脚本名称,不是函数名称
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "所有参数: $@"
echo "参数个数: $#"
}
show_info "Alice" 25 "Engineer"
# 使用 shift 处理参数
process_args() {
while [ $# -gt 0 ]; do
echo "处理参数: $1"
shift
done
}
process_args arg1 arg2 arg38.3 函数返回值
#!/bin/bash
# 使用 return 返回状态码
is_even() {
local num=$1
if [ $((num % 2)) -eq 0 ]; then
return 0 # 成功/真
else
return 1 # 失败/假
fi
}
# 检查返回值
if is_even 10; then
echo "10 是偶数"
else
echo "10 是奇数"
fi
# 使用 echo 返回数据
get_user_info() {
local username=$1
echo "姓名: $username"
echo "年龄: 30"
echo "职业: 工程师"
}
# 捕获多行输出
info=$(get_user_info "Alice")
echo "用户信息:"
echo "$info"9. 流程控制基础
9.1 条件判断
#!/bin/bash
# if 语句
age=18
if [ $age -ge 18 ]; then
echo "成年人"
fi
# if-else 语句
if [ $age -ge 18 ]; then
echo "成年人"
else
echo "未成年人"
fi
# if-elif-else 语句
score=85
if [ $score -ge 90 ]; then
echo "优秀"
elif [ $score -ge 80 ]; then
echo "良好"
elif [ $score -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi9.2 循环结构
#!/bin/bash
# for 循环
echo "for 循环示例:"
for i in 1 2 3 4 5; do
echo "数字: $i"
done
# while 循环
echo -e "\nwhile 循环示例:"
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
((count++))
done
# until 循环
echo -e "\nuntil 循环示例:"
count=1
until [ $count -gt 5 ]; do
echo "计数: $count"
((count++))
done9.3 case 语句
#!/bin/bash
# case 语句
fruit="apple"
case $fruit in
"apple")
echo "这是苹果"
;;
"banana")
echo "这是香蕉"
;;
"orange"|"grape")
echo "这是橘子或葡萄"
;;
*)
echo "未知水果"
;;
esac10. 实用示例
10.1 简单的文件备份脚本
#!/bin/bash
# 文件备份脚本
BACKUP_DIR="/backup"
SOURCE_FILE="/etc/passwd"
BACKUP_FILE="$BACKUP_DIR/passwd_backup_$(date +%Y%m%d).bak"
# 检查备份目录
if [ ! -d "$BACKUP_DIR" ]; then
echo "创建备份目录: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
fi
# 备份文件
if