1. 为什么你需要掌握Bash条件分支
每次看到新手在终端里重复输入相同的命令序列时,我都忍不住想递给他一个Bash脚本。而脚本中最能体现智能化的部分,就是if else条件分支。想象一下你家的智能空调:室温高于28度自动制冷,低于18度自动制热,其他情况保持通风——这就是典型的条件分支逻辑。
我在自动化部署脚本中频繁使用条件判断。比如最近给团队写的自动化测试脚本,需要根据不同的测试类型(单元测试、集成测试、压力测试)执行不同的测试套件。没有条件分支的话,就得写三个独立的脚本文件,维护起来简直是噩梦。
Bash的条件判断语法确实有些反直觉,特别是对那些熟悉C/Java等语言的人来说。记得我第一次写Bash脚本时,因为漏写了then前面的分号,调试了半小时。但一旦掌握核心要点,你会发现它比多数编程语言的条件判断更灵活——特别是结合Linux命令使用时。
2. if else基础语法全解析
2.1 标准语法结构
先看这个每天都会用到的备份脚本片段:
if [[ ! -d "/backup" ]]; then mkdir -p /backup echo "备份目录已创建" else echo "备份目录已存在" fi这里有几个关键点需要注意:
if和[[之间必须要有空格,就像你叫朋友全名时会自然停顿一样- 条件判断使用双中括号
[[ ]]比单中括号[ ]更安全,能避免很多意外的语法解析错误 then必须另起一行,或者用分号与条件语句连接(如if [[条件]]; then)
2.2 多条件组合
当需要同时满足多个条件时,可以这样写:
if [[ -f "lockfile" ]] && [[ $(wc -l < log.txt) -gt 100 ]]; then echo "检测到锁文件且日志超过100行,执行清理" rm lockfile && truncate log.txt --size 0 fi&&表示逻辑与,||表示逻辑或。我建议总是用双中括号包裹每个独立条件,这样可读性更好。
3. 实战中的高级技巧
3.1 正则匹配的妙用
处理用户输入时,正则匹配能省去大量字符串处理代码。比如验证邮箱格式:
if [[ $email =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then echo "邮箱格式正确" else echo "非法邮箱地址" >&2 exit 1 fi这里的=~操作符会启用正则表达式匹配。注意模式字符串不要加引号,否则会被当作普通字符串。
3.2 文件测试的18般武艺
Bash内置了大量文件测试操作符,我整理了几个最实用的:
-e:文件是否存在(不区分类型)-f:是普通文件(非目录/设备文件)-d:是目录-s:文件存在且非空-r/-w/-x:是否可读/可写/可执行
比如部署脚本中经常要检查配置文件:
config="/etc/app.conf" if [[ ! -f "$config" ]]; then cp default.conf "$config" elif [[ ! -s "$config" ]]; then echo "配置文件为空,使用默认配置" >&2 cat default.conf > "$config" fi4. 避坑指南:我踩过的那些雷
4.1 空格是隐形杀手
这是我见过最常见的错误:
if [[$var == "value"]] # 错误![[后需要空格 if [[ $var=="value" ]] # 错误!==两侧需要空格正确的写法应该是:
if [[ $var == "value" ]] # 所有关键位置都有空格4.2 整数比较的陷阱
比较数字时,用-eq/-ne/-gt/-lt等操作符比用==/!=更可靠:
count=10 if (( count > 5 )); then # 使用双括号算术比较 echo "超过阈值" fi或者更传统的写法:
if [[ $count -gt 5 ]]; then echo "超过阈值" fi4.3 命令返回值判断
很多新手会这样判断命令是否成功:
if [[ $(grep "error" log.txt) ]]; then # 错误!空字符串也会被当作false正确的做法是直接检查命令返回值:
if grep -q "error" log.txt; then # -q参数抑制输出 echo "发现错误日志" fi记得上次我写自动化部署脚本时,因为没处理cd命令的返回值,导致后续命令在错误目录执行,把生产环境配置全删错了。现在我会强制检查每个关键命令:
cd /target || { echo "切换目录失败" >&2 exit 1 }5. 复杂条件处理的艺术
5.1 case语句的优雅替代
当有多个条件分支时,可以用case替代多层if-elif:
case $mode in start) start_service ;; stop) stop_service ;; restart) restart_service ;; *) echo "Usage: $0 {start|stop|restart}" >&2 exit 1 esac5.2 使用函数封装条件逻辑
对于复杂的条件判断,我建议封装成函数:
is_valid_ip() { local ip=$1 [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1 IFS='.' read -ra nums <<< "$ip" for num in "${nums[@]}"; do [[ $num -le 255 ]] || return 1 done return 0 } if is_valid_ip "$user_input"; then echo "有效IP地址" fi6. 调试技巧与性能优化
6.1 调试模式下的条件追踪
在脚本开头加上set -x可以显示每个条件的执行过程:
#!/bin/bash set -x if [[ $DEBUG == "true" ]]; then verbose_mode="--verbose" fi运行时会显示:
+ [[ '' == true ]]6.2 避免过度条件嵌套
当if嵌套超过三层时,就该考虑重构了。这是我重构过的一个典型例子:
# 重构前 if [[ $os == "Linux" ]]; then if [[ -f "/etc/redhat-release" ]]; then if grep -q "CentOS" /etc/redhat-release; then install_centos fi fi fi # 重构后 [[ $os != "Linux" ]] && exit 0 [[ ! -f "/etc/redhat-release" ]] && exit 0 grep -q "CentOS" /etc/redhat-release && install_centos7. 真实场景综合案例
7.1 自动化部署脚本
这是我为一个PHP项目写的部署脚本片段:
#!/bin/bash branch=$1 target_dir="/var/www/$branch" # 检查参数 if [[ -z "$branch" ]]; then echo "请指定分支名称" >&2 exit 1 fi # 检查目录冲突 if [[ -e "$target_dir" ]]; then if [[ ! -d "$target_dir" ]]; then echo "目标路径被文件占用" >&2 exit 1 fi if [[ $(ls -A "$target_dir") ]]; then read -p "目录非空,确认覆盖?[y/N] " confirm [[ $confirm == [yY] ]] || exit 0 fi fi # 执行部署 git clone -b "$branch" git@example.com:project.git "$target_dir" || { echo "克隆仓库失败" >&2 exit 1 }7.2 系统监控告警脚本
这个脚本每天通过cron运行,检查服务器状态:
#!/bin/bash # 内存检查 mem_free=$(free -m | awk '/Mem/{print $4}') if (( mem_free < 100 )); then send_alert "可用内存不足: ${mem_free}MB" fi # 磁盘检查 disk_usage=$(df -h / | awk 'NR==2{print $5}' | tr -d '%') if (( disk_usage > 90 )); then send_alert "根分区使用率: ${disk_usage}%" fi # 服务检查 if ! systemctl is-active --quiet nginx; then send_alert "Nginx服务异常" systemctl restart nginx fi写完这个脚本后,我们服务器的小问题都能在用户投诉前自动修复了。特别是那个内存检查,有次半夜发现内存泄漏,及时重启服务避免了一场灾难。