Shell 脚本编程是 Linux 和 Unix 系统管理、自动化任务的核心工具之一。通过 Shell 脚本,你可以自动化重复性操作、简化复杂流程、提高系统管理效率,甚至构建完整的自动化运维工具。本文将带你从基础到进阶,全面学习 Shell 脚本编程,涵盖语法、结构、调试、最佳实践等内容。
Shell 是命令行解释器,是用户与操作系统内核之间的桥梁。它接收用户输入的命令,并调用相应的程序或服务来执行。
常见的 Shell 有:
本文以 Bash 为例进行讲解。
在 Linux 或 macOS 系统中,Bash 通常已经预装。可以通过以下命令查看当前 Shell:
echo $SHELL
如果你使用的是 Windows,可以安装:
创建一个名为 hello.sh
的文件:
#!/bin/bash
# 这是我的第一个 Shell 脚本
echo "Hello, World!"
给脚本添加执行权限:
chmod +x hello.sh
执行脚本:
./hello.sh
#!/bin/bash
被称为 shebang,用于告诉系统该脚本应使用哪个解释器来执行。不同的 shebang 可以指定不同的 Shell,例如:
#!/bin/sh
:使用 Bourne Shell#!/usr/bin/env python
:使用 Python 解释器运行脚本Shell 中变量不需要声明类型,赋值时等号两侧不能有空格:
name="Qwen"
echo "Hello, $name"
可以使用 ${name}
来避免歧义:
echo "Hello, ${name}_user"
将命令的输出结果赋值给变量:
current_date=$(date)
echo "当前时间是:$current_date"
也可以使用反引号实现相同功能:
current_date=`date`
读取用户输入:
read -p "请输入你的名字:" username
echo "你好,$username"
输出重定向:
echo "Hello" > output.txt # 覆盖写入
echo "World" >> output.txt # 追加写入
age=20
if [ $age -ge 18 ]; then
echo "你已成年"
else
echo "你还未成年"
fi
运算符 | 含义 |
---|---|
-eq |
等于 |
-ne |
不等于 |
-lt |
小于 |
-le |
小于等于 |
-gt |
大于 |
-ge |
大于等于 |
str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then
echo "字符串相等"
else
echo "字符串不相等"
fi
case $1 in
start)
echo "启动服务"
;;
stop)
echo "停止服务"
;;
*)
echo "未知命令"
;;
esac
for i in {1..5}; do
echo "第 $i 次循环"
done
遍历数组:
names=("Alice" "Bob" "Charlie")
for name in "${names[@]}"; do
echo "Hello, $name"
done
count=1
while [ $count -le 5 ]; do
echo "计数:$count"
count=$((count + 1))
done
count=1
until [ $count -gt 5 ]; do
echo "计数:$count"
count=$((count + 1))
done
greet() {
echo "你好,$1"
}
greet "Alice"
greet "Bob"
add() {
local result=$(( $1 + $2 ))
echo $result
}
sum=$(add 3 5)
echo "3 + 5 = $sum"
函数参数通过 $1
, $2
, ... 传递:
log() {
echo "[$(date +%H:%M:%S)] $1"
}
log "脚本开始执行"
fruits=("apple" "banana" "cherry")
echo "第一个水果是:${fruits[0]}"
echo "所有水果是:${fruits[@]}"
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
fruits+=("orange")
unset fruits[1]
echo "水果数量:${#fruits[@]}"
echo "脚本名称:$0"
echo "第一个参数:$1"
echo "所有参数:$@"
echo "参数个数:$#"
脚本的退出状态码用于表示执行是否成功:
exit 0 # 成功
exit 1 # 错误
可以通过 $?
获取上一个命令的退出状态:
ls /nonexistent
echo "上一个命令的状态码:$?"
if [ -f "file.txt" ]; then
echo "文件存在"
fi
常见测试操作符:
操作符 | 含义 |
---|---|
-f |
是否为文件 |
-d |
是否为目录 |
-r |
是否可读 |
-w |
是否可写 |
-x |
是否可执行 |
touch newfile.txt
rm -f file.txt
mv oldname.txt newname.txt
cat file.txt
使用 -x
参数调试脚本:
bash -x script.sh
或者在脚本开头加上:
set -x
关闭调试:
set +x
set -u
防止使用未定义变量。set -e
在出现错误时立即退出脚本。trap
捕获信号,进行清理操作:trap "echo '脚本被中断'; exit 1" INT
getopts
解析命令行参数:while getopts "a:b:c" opt; do
case $opt in
a)
echo "选项 a 的值:$OPTARG"
;;
b)
echo "选项 b 的值:$OPTARG"
;;
c)
echo "选项 c 被设置"
;;
\?)
echo "无效选项:-$OPTARG"
;;
esac
done
#!/bin/bash
# 设置变量
backup_dir="/backup"
source_dir="/home/user/documents"
timestamp=$(date +%Y%m%d%H%M%S)
backup_file="$backup_dir/backup_$timestamp.tar.gz"
# 创建备份目录(如果不存在)
mkdir -p $backup_dir
# 执行备份
tar -czf $backup_file $source_dir
# 检查是否成功
if [ $? -eq 0 ]; then
echo "备份完成:$backup_file"
else
echo "备份失败"
exit 1
fi
if [[ "hello123" =~ ^[a-zA-Z0-9]+$ ]]; then
echo "匹配成功"
fi
declare -A user_info
user_info["name"]="Alice"
user_info["age"]=25
echo "用户姓名:${user_info[name]}"
(
cd /tmp
touch testfile
)
cat << EOF > output.txt
这是第一行
这是第二行
EOF
可以将常用函数放入一个 .sh
文件中作为库文件:
# utils.sh
log() {
echo "[$(date +%H:%M:%S)] $1"
}
在主脚本中引用:
source utils.sh
log "脚本开始执行"