Shell 脚本是用于在 Unix/Linux 命令行解释器(Shell)中自动化执行一系列命令的文本文件。掌握其基础语法对于系统管理、任务自动化和开发运维至关重要。
Shebang 行 (必需):
#!/bin/bash
#!
告诉系统使用哪个解释器执行脚本。/bin/bash
指定使用 Bash Shell(最常见)。也可以是 /bin/sh
, /bin/zsh
, /usr/bin/python3
等。注释:
# 这是一个单行注释
: '
这是一个
多行注释
(Bash 风格)
'
<<COMMENT
这也是一个
多行注释
(Heredoc 风格)
COMMENT
#
开头的行是单行注释(除了 Shebang)。: ' ... '
或 Here Document < 技巧实现。
定义与赋值:
variable_name=value # 等号两边不能有空格!
name="John Doe" # 包含空格的值必须用引号括起来
count=42 # 数字不需要引号
files=$(ls) # 命令替换:将命令输出赋值给变量
files=`ls` # 旧式命令替换(反引号),建议用 $()
today=$(date +%F) # 将格式化后的日期赋值给变量
=
两边不能有空格。使用变量:
echo $name # 使用变量:$变量名
echo "Hello, $name" # 双引号内变量会被替换(扩展)
echo 'Hello, $name' # 单引号内变量不会被替换,原样输出
echo "Count: ${count}" # 使用 {} 明确变量边界,推荐总是使用
echo "Files: ${files[@]}" # 访问数组所有元素
echo "Arg 1: $1" # 位置参数:$1, $2, ... $9, ${10}, ...
$variable_name
或 ${variable_name}
获取变量值。"
:允许变量扩展、命令替换和转义字符。'
:所有内容原样输出,不进行任何替换或转义。`
或 $()
:用于命令替换,执行命令并将其输出作为值。位置参数:
$0
:脚本名称本身。$1
, $2
, $3
, …:传递给脚本的第一个、第二个、第三个参数。$#
:传递给脚本的参数个数。$@
:所有位置参数的列表(每个参数作为独立的字符串)。$*
:所有位置参数合并成一个字符串。shift
:将位置参数向左移动($1
丢弃,$2
变成 $1
,依此类推),常用于循环处理参数。特殊变量:
$?
:上一个命令的退出状态码(0 表示成功,非 0 表示失败)。$$
:当前 Shell 进程的进程 ID (PID)。$!
:上一个在后台运行的命令的 PID。$-
:当前 Shell 的选项标志。数组:
fruits=("Apple" "Banana" "Orange") # 定义数组
echo ${fruits[0]} # 访问第一个元素:Apple
echo ${fruits[@]} # 访问所有元素
echo ${#fruits[@]} # 数组元素个数:3
fruits[3]="Grape" # 添加元素
unset fruits[1] # 删除第二个元素(Banana)
输出 (echo
, printf
):
echo "Hello World" # 基本输出
echo -e "Line 1\nLine 2" # -e 启用转义字符(如 \n 换行)
printf "Name: %-10s Age: %d\n" "$name" $age # 格式化输出,类似 C 语言
输入 (read
):
read -p "Enter your name: " username # -p 显示提示符
read -s -p "Password: " password # -s 静默输入(不回显)
echo "Hello, $username!" # 使用读取的值
read first second # 读取多个值到不同变量
read -a array # 读取一行输入到数组 array
if
, case
, test
/[ ]
/[[ ]]
)test
命令与方括号 [ ]
:
if [ "$name" = "Alice" ]; then # 字符串相等比较。注意空格和引号!
echo "Hi Alice!"
elif [ $age -gt 18 ]; then # 数字比较:-gt (大于), -lt (小于), -eq (等于), -ne (不等于), -ge (大于等于), -le (小于等于)
echo "You are an adult."
elif [ -f "/path/to/file" ]; then # 文件测试:-f (是普通文件), -d (是目录), -e (存在), -r (可读), -w (可写), -x (可执行)
echo "File exists."
else
echo "Who are you?"
fi
[
是 test
命令的别名,]
是必须的结尾符。[
]
之间必须有空格。=
或 !=
,数字比较用 -eq
, -ne
等操作符。"$name"
),防止变量为空或包含空格导致语法错误。双括号 [[ ]]
(Bash 扩展,更推荐):
if [[ $name == "Alice" && $age -ge 18 ]]; then # 逻辑与 (&&), 或 (||)
echo "Welcome Alice!"
elif [[ -f "$file" && -s "$file" ]]; then # 文件存在且非空
echo "File is valid."
elif [[ "$str" =~ ^[A-Za-z]+$ ]]; then # 正则表达式匹配 (=~)
echo "String is alphabetic."
fi
[ ]
更强大、更安全(例如在字符串比较中不需要额外引号防止空变量)。==
或 !=
右侧可用通配符 *
, ?
, []
)。=~
)。&&
和 ||
可以直接在 [[ ... ]]
内使用。case
语句 (多路分支):
case $fruit in
"Apple")
echo "It's an apple."
;;
"Banana" | "Orange") # 匹配多个模式
echo "It's a banana or orange."
;;
*) # 默认匹配所有其他情况
echo "Unknown fruit."
;;
esac
)
结束。;;
结束。|
用于分隔多个模式。*
是通配符,匹配任何值。for
, while
, until
)for
循环:
# 遍历列表
for color in red green blue; do
echo "Color: $color"
done
# 遍历命令输出结果(按空格/换行符分隔)
for file in $(ls *.txt); do
echo "Processing: $file"
done
# C 语言风格 (Bash)
for ((i=0; i<10; i++)); do
echo "Number: $i"
done
# 遍历数组元素
for item in "${fruits[@]}"; do
echo "Fruit: $item"
done
while
循环 (当条件为真时循环):
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++)) # 算术运算:(()) 或 let count++
done
# 逐行读取文件
while IFS= read -r line; do # IFS= 防止前导/尾随空格被修剪,-r 防止反斜杠转义
echo "Line: $line"
done < "input.txt" # 输入重定向
until
循环 (当条件为假时循环):
count=10
until [ $count -eq 0 ]; do # 当 $count 等于 0 时停止
echo "Countdown: $count"
((count--))
done
定义与调用:
# 定义函数
greet() {
local greeting="Hello" # local 定义局部变量(仅在函数内有效)
echo "$greeting, $1!"
}
# 调用函数
greet "World" # 传递参数 "World"(在函数内通过 $1 访问)
result=$(greet "Alice") # 捕获函数输出
()
。{ }
内。$1
, $2
, … 在函数内部访问。local
关键字定义局部变量,避免污染全局作用域。return N
返回一个退出状态码(0 表示成功,1-255 表示失败)。要返回数据,请使用 echo
输出并在调用处用 $(...)
捕获。退出脚本 (exit
):
if [ ! -f "required.txt" ]; then
echo "Error: File not found!" >&2 # 输出到标准错误
exit 1 # 非 0 退出码表示错误
fi
exit 0 # 成功退出
命令组合:
(command1; command2)
: 在子Shell中执行命令序列。{ command1; command2; }
: 在当前Shell中执行命令序列(注意 {
后必须有空格,最后一条命令后必须有 ;
或换行,}
前必须有 ;
或换行)。逻辑操作符 (&&
, ||
):
command1 && command2 # 仅当 command1 成功(退出码 0)才执行 command2
command1 || command2 # 仅当 command1 失败(退出码非 0)才执行 command2
command > file
:将 command
的标准输出重定向到 file
(覆盖)。command >> file
:将 command
的标准输出重定向到 file
(追加)。command < file
:将 file
的内容作为 command
的标准输入。command 2> file
:将 command
的标准错误重定向到 file
(覆盖)。command 2>> file
:将 command
的标准错误重定向到 file
(追加)。command > file 2>&1
或 command &> file
:将标准输出和标准错误都重定向到 file
(覆盖)。command >> file 2>&1
或 command &>> file
:将标准输出和标准错误都重定向到 file
(追加)。command | command2
:将 command
的标准输出通过管道传递给 command2
的标准输入。引号:
'
: 强引用,内部所有字符(包括 $
和 \
)都失去特殊意义,原样输出。"
: 弱引用,允许变量扩展 ($var
)、命令替换 ($(...)
或 `...`
) 和转义字符 (\n
, \t
, \"
, \\
)。是最常用且安全的选择(尤其当变量值可能包含空格时)。`
: 用于命令替换(同 $()
,但 $()
更清晰且支持嵌套)。算术运算:
a=5
b=3
sum=$((a + b)) # 推荐:(( ... )) 表达式计算
((product = a * b)) # 计算结果可赋值给变量(变量前无 $)
let "diff = a - b" # let 命令
increment=$((a++)) # 后置自增
decrement=$((--b)) # 前置自减
调试:
bash -x script.sh
: 在执行时显示每条命令及其展开后的参数(极其有用)。bash -n script.sh
: 只检查脚本语法错误,不执行。set -x
(开始) / set +x
(结束)。#!/bin/bash
# 计算 1 到 N 的平方和
# 获取用户输入
read -p "Enter a number (N): " n
# 检查输入是否是数字
if ! [[ "$n" =~ ^[0-9]+$ ]]; then
echo "Error: Please enter a positive integer." >&2
exit 1
fi
# 初始化变量
sum=0
counter=1
# 使用 while 循环计算平方和
while [ $counter -le $n ]; do
square=$((counter * counter))
sum=$((sum + square))
((counter++))
done
# 输出结果
echo "The sum of squares from 1 to $n is: $sum"
exit 0
#!/bin/bash
(或明确指定所需 Shell)。"$var"
),除非你明确知道不需要(非常罕见)。cp "$source" "$dest"
)。[[ ]]
代替 [ ]
(Bash 中),功能更强更安全。if command; then ...
或 command || handle_error
)。set -euo pipefail
或类似选项 (在脚本开头) 增强健壮性:
set -e
: 命令失败(非 0 退出码)立即退出脚本。set -u
: 遇到未定义变量时报错并退出。set -o pipefail
: 管道中任何一个命令失败,整个管道视为失败。rm -rf
,尤其当路径包含变量时。可以先 echo
要删除的内容检查。local
声明函数内的局部变量。$(...)
代替反引号 `...`
进行命令替换。read -r
读取输入行,防止反斜杠转义。Shell 脚本来解决各种自动化任务,不同的 Shell (如 sh
, bash
, zsh
, ksh
) 可能有细微差别,Bash 是最通用的选择。