Shell脚本如何实现超时控制

wen 实用脚本 1

本文目录导读:

Shell脚本如何实现超时控制

  1. 使用 timeout 命令(最推荐)
  2. 使用 expect 工具
  3. 使用后台进程 + 定时器
  4. 使用 read 命令的超时功能
  5. 使用管道实现输出捕获
  6. 完整的超时控制函数库
  7. 特殊场景:ping命令的超时
  8. 选择建议

Shell脚本中实现超时控制有多种方法,以下是几种常见的实现方式:

使用 timeout 命令(最推荐)

# 基本用法:timeout [时间] 命令
timeout 5s sleep 10  # 5秒后终止sleep命令
echo "命令已超时或完成"
# 带错误处理
if timeout 5s sleep 10; then
    echo "命令在5秒内完成"
else
    echo "命令超时或被中断"
fi
# 使用不同的时间单位
timeout 5s     # 5秒
timeout 1m     # 1分钟
timeout 1h     # 1小时
timeout 1d     # 1天
# 发送特定信号
timeout -s SIGKILL 5s sleep 10  # 使用SIGKILL信号

使用 expect 工具

#!/bin/bash
# 使用expect实现超时控制
expect << EOF
set timeout 10  # 设置10秒超时
spawn long_running_command
expect {
    timeout { puts "命令超时"; exit 1 }
    eof     { puts "命令完成"; exit 0 }
}
EOF

使用后台进程 + 定时器

#!/bin/bash
# 通用超时函数
run_with_timeout() {
    local timeout=$1
    shift
    local cmd="$@"
    # 在后台执行命令
    ( $cmd ) &
    local cmd_pid=$!
    # 在后台执行超时监控
    ( sleep $timeout; kill $cmd_pid 2>/dev/null ) &
    local timeout_pid=$!
    # 等待命令完成
    wait $cmd_pid 2>/dev/null
    local result=$?
    # 杀掉超时进程(如果已经完成)
    kill $timeout_pid 2>/dev/null
    return $result
}
# 使用示例
run_with_timeout 5 "sleep 10"
if [ $? -eq 0 ]; then
    echo "命令在超时前完成"
else
    echo "命令超时"
fi

使用 read 命令的超时功能

#!/bin/bash
# 等待用户输入,带超时
echo "请在5秒内输入:"
if read -t 5 user_input; then
    echo "收到输入: $user_input"
else
    echo "超时"
fi
# 等待进程完成,带超时
timeout_read() {
    local timeout=$1
    local pid=$2
    # 循环检查进程状态
    while [ $timeout -gt 0 ]; do
        if ! kill -0 $pid 2>/dev/null; then
            return 0  # 进程正常结束
        fi
        sleep 1
        ((timeout--))
    done
    kill $pid 2>/dev/null
    return 1  # 超时
}

使用管道实现输出捕获

#!/bin/bash
run_with_timeout_and_capture() {
    local timeout=$1
    local output=""
    shift
    local cmd="$@"
    # 创建临时文件存储输出
    local tmpfile=$(mktemp)
    # 执行命令并捕获输出
    ( $cmd > "$tmpfile" 2>&1 ) &
    local cmd_pid=$!
    # 等待指定时间
    if ! wait $cmd_pid 2>/dev/null
    then
        # 如果还运行则杀掉
        kill $cmd_pid 2>/dev/null
        echo "命令超时" >&2
        cat "$tmpfile"
        rm -f "$tmpfile"
        return 1
    fi
    # 读取输出
    output=$(cat "$tmpfile")
    rm -f "$tmpfile"
    echo "$output"
    return 0
}
# 使用示例
result=$(run_with_timeout_and_capture 5 "sleep 3 && echo '完成'")
echo "结果: $result"

完整的超时控制函数库

#!/bin/bash
# 超时控制函数库
TIMEOUT_LIB_VERSION="1.0"
# 检查超时是否可用
check_timeout_available() {
    command -v timeout &>/dev/null
}
# 通用超时执行函数
execute_with_timeout() {
    local timeout=$1
    local default_signal=${2:-SIGTERM}
    shift 2
    local cmd="$@"
    # 优先使用timeout命令
    if check_timeout_available; then
        timeout -s $default_signal $timeout $cmd
        return $?
    fi
    # 备选方案:手动实现
    (
        $cmd &
        local cmd_pid=$!
        # 启动超时监控
        ( sleep $timeout; kill -$default_signal $cmd_pid 2>/dev/null ) &
        local timer_pid=$!
        wait $cmd_pid 2>/dev/null
        local result=$?
        # 清理定时器
        kill $timer_pid 2>/dev/null
        exit $result
    )
    return $?
}
# 带进度提示的超时执行
execute_with_progress() {
    local timeout=$1
    local message=${2:-"执行中"}
    shift 2
    local cmd="$@"
    $cmd &
    local cmd_pid=$!
    local count=0
    while [ $count -lt $timeout ]; do
        if ! kill -0 $cmd_pid 2>/dev/null; then
            echo -e "\n命令完成"
            wait $cmd_pid
            return $?
        fi
        echo -n "$message."
        sleep 1
        ((count++))
    done
    echo -e "\n超时,终止命令"
    kill $cmd_pid 2>/dev/null
    return 124
}
# 使用示例
echo "===== 测试1: 使用timeout命令 ====="
if execute_with_timeout 3 "sleep 5"; then
    echo "命令完成"
else
    echo "命令超时"
fi
echo -e "\n===== 测试2: 带进度提示 ====="
execute_with_progress 5 "正在执行" "sleep 10"

特殊场景:ping命令的超时

#!/bin/bash
# ping命令自带超时选项
ping -c 3 -W 5 8.8.8.8  # 3个包,每个超时5秒
# 或者使用-w选项作为全局超时
ping -c 3 -w 10 8.8.8.8  # 整体超时10秒

选择建议

  1. 标准Linux环境:优先使用 timeout 命令
  2. 兼容性要求高:使用后台进程+定时器方法
  3. 需要交互式控制:使用 expect 工具
  4. 简单场景:使用 read -t 或进程状态检查

每种方法都有其适用场景,选择时考虑:

  • 系统兼容性(timeout命令需要coreutils)
  • 是否需要捕获输出
  • 是否需要处理特定信号
  • 是否需要进度提示

抱歉,评论功能暂时关闭!