1. Shell编程规范概述
Shell脚本作为系统管理员和开发人员的必备技能,其代码质量直接影响着脚本的可维护性和可靠性。一个规范的Shell脚本应当具备清晰的代码结构、合理的变量命名、完善的错误处理机制以及良好的可读性。
在Linux/Unix环境中,Shell脚本通常用于自动化运维、批量处理、系统监控等场景。根据我的经验,一个优秀的Shell脚本应当遵循以下基本原则:
- 统一使用#!/bin/bash作为解释器声明(除非有特殊需求)
- 脚本文件扩展名使用.sh
- 每行不超过80个字符(特殊情况下不超过120)
- 使用4个空格作为缩进(而非制表符)
- 关键操作添加注释说明
- 重要函数提供使用示例
提示:在实际项目中,我习惯在脚本开头添加一段元信息注释块,包含脚本用途、作者、创建日期、修改记录等信息。这个习惯让我在维护旧脚本时节省了大量时间。
2. 变量命名与使用规范
2.1 变量命名规则
Shell变量的命名需要遵循一些最佳实践:
-
全局变量使用大写字母和下划线组合,如:
bash复制CONFIG_FILE="/etc/app.conf" MAX_RETRY=3 -
局部变量使用小写字母和下划线组合:
bash复制local file_path="/tmp/data.log" -
避免使用单个字符作为变量名(循环计数器除外)
-
不要使用Shell保留字(如$PATH、$HOME等)作为变量名
-
布尔变量建议使用is_、has_等前缀:
bash复制is_debug=true has_error=false
2.2 变量声明与使用
在Shell中声明和使用变量时需要注意:
bash复制# 正确声明方式
name="value" # 等号两边不能有空格
readonly PI=3.14 # 声明只读变量
# 错误示例
name = "value" # 等号两边有空格会导致语法错误
引用变量时建议使用${var}形式而非$var,特别是在字符串拼接时:
bash复制file="log"
echo "Processing ${file}_1.txt" # 比"Processing $file_1.txt"更清晰
注意:未定义的变量在引用时不会报错,但可能导致意外行为。我建议使用set -u或在脚本开头添加set -o nounset来捕获未定义变量。
3. 变量作用域与生命周期
3.1 全局变量与局部变量
Shell变量的作用域规则比较特殊:
-
默认情况下,函数内定义的变量也是全局的
-
使用local关键字声明函数局部变量:
bash复制function process() { local counter=0 # 仅在此函数内有效 global_var=1 # 全局变量 } -
子Shell(通过()或|创建的)无法访问父Shell的变量
3.2 环境变量与普通变量
- 普通变量:仅在当前Shell会话中有效
- 环境变量:可被子进程继承,使用export声明:
bash复制export PATH="$PATH:/usr/local/bin"
查看所有环境变量:
bash复制env # 或 printenv
4. 特殊变量与参数处理
4.1 Shell内置特殊变量
这些变量在脚本中非常有用:
bash复制$0 # 脚本名称
$1-$9 # 脚本参数
$# # 参数个数
$* # 所有参数(作为一个字符串)
$@ # 所有参数(作为独立字符串)
$? # 上条命令的退出状态
$$ # 当前Shell的PID
$! # 最后一个后台进程的PID
4.2 参数处理技巧
shift命令可以移动位置参数,在处理多个参数时特别有用:
bash复制while [ $# -gt 0 ]; do
case "$1" in
-f|--file)
file="$2"
shift 2
;;
-v|--verbose)
verbose=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
5. 数组与关联数组
5.1 基本数组操作
Bash支持索引数组(4.0+版本还支持关联数组):
bash复制# 声明数组
files=(file1.txt file2.txt "file with space.txt")
# 访问元素
echo "First file: ${files[0]}"
# 获取所有元素
echo "All files: ${files[@]}"
# 数组长度
echo "File count: ${#files[@]}"
# 遍历数组
for file in "${files[@]}"; do
echo "Processing $file"
done
5.2 关联数组(Bash 4.0+)
关联数组(类似其他语言中的字典/哈希表):
bash复制declare -A user_info=(
[name]="Alice"
[age]=25
[email]="alice@example.com"
)
echo "User name: ${user_info[name]}"
6. 字符串处理技巧
Shell提供了丰富的字符串操作功能:
6.1 基本字符串操作
bash复制str="Hello World"
# 长度
echo ${#str} # 11
# 子串
echo ${str:6} # World
echo ${str:6:3} # Wor
# 替换
echo ${str/World/Shell} # Hello Shell
6.2 高级字符串处理
bash复制# 删除前缀/后缀
filename="archive.tar.gz"
echo ${filename%.*} # archive.tar
echo ${filename%%.*} # archive
echo ${filename#*.} # tar.gz
echo ${filename##*.} # gz
# 默认值
echo ${unset_var:-"default"} # 如果unset_var未设置或为空,使用"default"
7. 数值计算与类型处理
7.1 整数运算
Shell中默认只支持整数运算:
bash复制# 使用$(( ))语法
result=$(( 5 + 3 * 2 )) # 11
# 使用let命令
let "result=5+3*2"
# 自增操作
count=0
((count++))
7.2 浮点数运算
处理浮点数需要借助外部工具:
bash复制# 使用bc计算器
pi=$(echo "scale=2; 4*a(1)" | bc -l) # 3.14
# 计算平均值
avg=$(echo "scale=2; (5 + 8 + 3) / 3" | bc)
8. 输入输出与重定向
8.1 基本输入输出
bash复制# 输出
echo "Message" # 自动添加换行
printf "Value: %s\n" 42 # 格式化输出
# 输入
read -p "Enter name: " name
8.2 重定向技巧
bash复制# 标准输出和错误输出重定向
command > output.log 2>&1 # 合并重定向
command 2> error.log # 仅重定向错误
command &> all_output.log # Bash 4+的简化写法
# 追加模式
echo "New line" >> file.log
# 输入重定向
while read line; do
echo "Processing: $line"
done < input.txt
9. 错误处理与调试
9.1 错误处理机制
bash复制# 立即退出遇到错误
set -e
# 显示执行的命令(调试用)
set -x
# 管道命令中任一失败则整个失败
set -o pipefail
# 组合使用
set -euxo pipefail
9.2 自定义错误处理
bash复制function error_exit {
echo "$1" >&2
exit 1
}
# 使用示例
[ -f "$file" ] || error_exit "File not found: $file"
10. 实战案例:一个规范的Shell脚本模板
结合上述规范,下面是一个实用的脚本模板:
bash复制#!/bin/bash
#
# Script: system_info.sh
# Author: Your Name
# Date: $(date +%Y-%m-%d)
# Description: Collects basic system information
set -euo pipefail
# 配置常量
readonly OUTPUT_FILE="/tmp/system_info_$(date +%Y%m%d).log"
readonly MAX_RETRY=3
# 函数定义
function get_cpu_info() {
local model_name
model_name=$(grep -m1 "model name" /proc/cpuinfo | cut -d: -f2 | sed 's/^ *//')
echo "CPU Model: $model_name"
}
function get_memory_usage() {
local total_mem used_mem
total_mem=$(free -m | awk '/Mem:/ {print $2}')
used_mem=$(free -m | awk '/Mem:/ {print $3}')
echo "Memory Usage: ${used_mem}MB/${total_mem}MB"
}
# 主函数
function main() {
{
echo "===== System Information ====="
echo "Date: $(date)"
echo "Hostname: $(hostname)"
echo
get_cpu_info
get_memory_usage
} > "$OUTPUT_FILE"
echo "System info saved to $OUTPUT_FILE"
}
# 脚本入口
main "$@"
这个模板展示了:
- 清晰的脚本元信息
- 常量和变量定义
- 函数封装
- 错误处理设置
- 主函数结构
- 标准化的输出处理
在实际项目中,我通常会根据脚本复杂度添加日志记录、参数解析、帮助文档等功能。记住,好的Shell脚本应该像其他编程语言的代码一样被认真对待,遵循一致的编码规范。
