until循环 #

一、until循环基础 #

1.1 基本语法 #

bash
until [ 条件 ]; do
    命令
done

until循环与while循环相反:当条件为假时执行循环体,直到条件为真时退出。

1.2 while与until对比 #

bash
#!/bin/bash

# while循环:条件为真时执行
count=1
while [ $count -le 5 ]; do
    echo "while: $count"
    ((count++))
done

# until循环:条件为假时执行
count=1
until [ $count -gt 5 ]; do
    echo "until: $count"
    ((count++))
done

# 两者输出相同,但条件相反

1.3 简单示例 #

bash
#!/bin/bash

# 基本计数
count=1
until [ $count -gt 5 ]; do
    echo "计数: $count"
    ((count++))
done

# 使用 (( ))
num=0
until (( num >= 5 )); do
    echo "数字: $num"
    ((num++))
done

二、until循环应用 #

2.1 等待条件满足 #

bash
#!/bin/bash

# 等待服务启动
echo "等待nginx服务启动..."
until systemctl is-active --quiet nginx; do
    echo "等待中..."
    sleep 1
done
echo "nginx服务已启动"

# 等待文件创建
file="/tmp/signal.txt"
echo "等待文件 $file 创建..."
until [ -f "$file" ]; do
    sleep 1
done
echo "文件已创建"

# 等待进程结束
pid=$$
echo "等待进程 $pid 结束..."
until ! kill -0 $pid 2>/dev/null; do
    sleep 1
done
echo "进程已结束"

2.2 等待网络连接 #

bash
#!/bin/bash

# 等待网络可用
wait_for_network() {
    local host="$1"
    local max_attempts=30
    local attempt=0
    
    echo "等待网络连接: $host"
    
    until ping -c 1 "$host" &>/dev/null; do
        ((attempt++))
        if [ $attempt -ge $max_attempts ]; then
            echo "网络连接超时"
            return 1
        fi
        echo "尝试 $attempt/$max_attempts..."
        sleep 1
    done
    
    echo "网络已连接"
    return 0
}

wait_for_network "google.com"

# 等待端口可用
wait_for_port() {
    local host="$1"
    local port="$2"
    
    until nc -z "$host" "$port" 2>/dev/null; do
        echo "等待端口 $host:$port..."
        sleep 1
    done
    
    echo "端口 $port 已可用"
}

wait_for_port "localhost" 8080

2.3 等待数据库连接 #

bash
#!/bin/bash

# 等待MySQL启动
wait_for_mysql() {
    local host="${1:-localhost}"
    local port="${2:-3306}"
    local user="${3:-root}"
    
    echo "等待MySQL服务..."
    
    until mysql -h"$host" -P"$port" -u"$user" -e "SELECT 1" &>/dev/null; do
        echo "MySQL未就绪,等待..."
        sleep 2
    done
    
    echo "MySQL已就绪"
}

wait_for_mysql

# 等待Redis启动
wait_for_redis() {
    local host="${1:-localhost}"
    local port="${2:-6379}"
    
    echo "等待Redis服务..."
    
    until redis-cli -h "$host" -p "$port" ping &>/dev/null; do
        echo "Redis未就绪,等待..."
        sleep 1
    done
    
    echo "Redis已就绪"
}

wait_for_redis

2.4 重试机制 #

bash
#!/bin/bash

# 带重试的命令执行
retry_until_success() {
    local max_attempts=$1
    local interval=$2
    shift 2
    local command="$@"
    
    local attempt=1
    
    until eval "$command"; do
        if [ $attempt -ge $max_attempts ]; then
            echo "达到最大重试次数: $max_attempts"
            return 1
        fi
        
        echo "尝试 $attempt 失败,${interval}秒后重试..."
        sleep "$interval"
        ((attempt++))
    done
    
    echo "成功!"
    return 0
}

retry_until_success 5 2 "curl -f http://example.com/api"

三、until循环高级用法 #

3.1 复杂条件判断 #

bash
#!/bin/bash

# 等待多个条件同时满足
wait_for_all() {
    local services=("nginx" "mysql" "redis")
    
    until systemctl is-active --quiet "${services[0]}" && \
          systemctl is-active --quiet "${services[1]}" && \
          systemctl is-active --quiet "${services[2]}"; do
        echo "等待所有服务启动..."
        sleep 2
    done
    
    echo "所有服务已启动"
}

# 等待任意条件满足
wait_for_any() {
    local files=("file1.txt" "file2.txt" "file3.txt")
    
    until [ -f "${files[0]}" ] || \
          [ -f "${files[1]}" ] || \
          [ -f "${files[2]}" ]; do
        echo "等待任意文件创建..."
        sleep 1
    done
    
    echo "有文件已创建"
}

3.2 带超时的等待 #

bash
#!/bin/bash

# 带超时的等待
wait_with_timeout() {
    local condition="$1"
    local timeout=$2
    local elapsed=0
    
    until eval "$condition"; do
        if [ $elapsed -ge $timeout ]; then
            echo "等待超时"
            return 1
        fi
        
        sleep 1
        ((elapsed++))
        echo "已等待 ${elapsed}/${timeout} 秒"
    done
    
    echo "条件满足"
    return 0
}

# 等待文件创建(最多30秒)
wait_with_timeout "[ -f /tmp/test.txt ]" 30

# 等待服务启动(最多60秒)
wait_with_timeout "systemctl is-active --quiet nginx" 60

3.3 嵌套until循环 #

bash
#!/bin/bash

# 等待多个服务依次启动
services=("nginx" "mysql" "redis")

for service in "${services[@]}"; do
    echo "启动 $service..."
    systemctl start "$service"
    
    until systemctl is-active --quiet "$service"; do
        echo "等待 $service 启动..."
        sleep 1
    done
    
    echo "$service 已启动"
done

echo "所有服务已启动"

四、实战示例 #

4.1 服务健康检查 #

bash
#!/bin/bash

health_check() {
    local service=$1
    local max_attempts=30
    local attempt=0
    
    echo "健康检查: $service"
    
    until systemctl is-active --quiet "$service"; do
        ((attempt++))
        
        if [ $attempt -ge $max_attempts ]; then
            echo "服务 $service 启动失败"
            return 1
        fi
        
        echo "等待 $service... ($attempt/$max_attempts)"
        sleep 1
    done
    
    echo "服务 $service 运行正常"
    return 0
}

# 检查多个服务
services=("nginx" "mysql" "redis")
all_ok=true

for service in "${services[@]}"; do
    if ! health_check "$service"; then
        all_ok=false
    fi
done

if $all_ok; then
    echo "所有服务正常"
else
    echo "部分服务异常"
fi

4.2 文件监控 #

bash
#!/bin/bash

# 等待文件出现并处理
wait_and_process() {
    local watch_dir="$1"
    local pattern="$2"
    
    echo "监控目录: $watch_dir"
    echo "匹配模式: $pattern"
    
    while true; do
        # 等待匹配的文件出现
        until ls "$watch_dir"/$pattern 1>/dev/null 2>&1; do
            sleep 1
        done
        
        # 处理文件
        for file in "$watch_dir"/$pattern; do
            echo "处理文件: $file"
            # 处理逻辑
            mv "$file" "$file.processed"
        done
    done
}

wait_and_process "/tmp/input" "*.txt"

4.3 数据库初始化等待 #

bash
#!/bin/bash

wait_for_db() {
    local host="${DB_HOST:-localhost}"
    local port="${DB_PORT:-3306}"
    local user="${DB_USER:-root}"
    local password="${DB_PASSWORD:-}"
    local max_attempts=60
    local attempt=0
    
    echo "等待数据库初始化..."
    
    until mysql -h"$host" -P"$port" -u"$user" -p"$password" -e "SELECT 1" &>/dev/null; do
        ((attempt++))
        
        if [ $attempt -ge $max_attempts ]; then
            echo "数据库连接超时"
            exit 1
        fi
        
        echo "等待数据库... ($attempt/$max_attempts)"
        sleep 2
    done
    
    echo "数据库已就绪"
    
    # 执行初始化脚本
    echo "执行初始化脚本..."
    mysql -h"$host" -P"$port" -u"$user" -p"$password" < init.sql
}

wait_for_db

4.4 容器启动等待 #

bash
#!/bin/bash

wait_for_container() {
    local container="$1"
    local timeout=60
    local elapsed=0
    
    echo "等待容器 $container 启动..."
    
    until docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null | grep -q "true"; do
        if [ $elapsed -ge $timeout ]; then
            echo "容器启动超时"
            return 1
        fi
        
        sleep 1
        ((elapsed++))
        printf "\r等待中... %ds/%ds" $elapsed $timeout
    done
    
    echo ""
    echo "容器 $container 已启动"
    return 0
}

# 等待容器健康
wait_for_healthy() {
    local container="$1"
    local timeout=120
    local elapsed=0
    
    until docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null | grep -q "healthy"; do
        if [ $elapsed -ge $timeout ]; then
            echo "容器健康检查超时"
            return 1
        fi
        
        sleep 2
        ((elapsed+=2))
        printf "\r健康检查中... %ds/%ds" $elapsed $timeout
    done
    
    echo ""
    echo "容器 $container 健康"
    return 0
}

wait_for_container "mysql"
wait_for_healthy "mysql"

五、until与while的选择 #

5.1 选择指南 #

场景 推荐使用
条件为真时执行 while
条件为假时执行 until
等待条件变为真 until
等待条件变为假 while
自然语言表达 until更直观

5.2 示例对比 #

bash
#!/bin/bash

# 等待文件创建:until更直观
# until版本
until [ -f /tmp/ready ]; do
    sleep 1
done
echo "文件已创建"

# while版本
while [ ! -f /tmp/ready ]; do
    sleep 1
done
echo "文件已创建"

# 等待文件删除:while更直观
# while版本
while [ -f /tmp/lock ]; do
    sleep 1
done
echo "锁已释放"

# until版本
until [ ! -f /tmp/lock ]; do
    sleep 1
done
echo "锁已释放"

六、until循环最佳实践 #

6.1 避免无限等待 #

bash
#!/bin/bash

# 不推荐:可能无限等待
until [ -f /tmp/file ]; do
    sleep 1
done

# 推荐:添加超时
timeout=60
elapsed=0
until [ -f /tmp/file ]; do
    if [ $elapsed -ge $timeout ]; then
        echo "等待超时"
        exit 1
    fi
    sleep 1
    ((elapsed++))
done

6.2 添加进度提示 #

bash
#!/bin/bash

# 推荐:显示等待进度
attempt=0
max_attempts=30

until systemctl is-active --quiet nginx; do
    ((attempt++))
    
    if [ $attempt -ge $max_attempts ]; then
        echo "服务启动超时"
        exit 1
    fi
    
    printf "\r等待服务启动... %d/%d" $attempt $max_attempts
    sleep 1
done

echo ""
echo "服务已启动"

七、总结 #

7.1 until循环要点 #

要点 说明
基本语法 until [ 条件 ]; do … done
执行条件 条件为假时执行
退出条件 条件为真时退出
与while关系 条件相反
典型用途 等待条件满足

7.2 下一步 #

你已经掌握了until循环,接下来让我们学习 循环控制,了解break和continue的使用!

最后更新:2026-03-27