You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在单个BASH脚本中为函数实现多线程?备份场景下如何操作?

单Bash脚本实现多进程备份并跟踪依赖结果的解决方案

针对你的需求,以下是几种无需拆分多个脚本、可在单个Bash脚本内实现多进程备份,同时跟踪每个任务结果并处理后续依赖逻辑的方案:

方案1:函数封装任务+临时文件存储结果

将每个备份任务封装为函数,后台执行时把任务状态(是否有更新、rsync退出码)写入对应临时文件,主进程等待所有任务完成后,逐个读取文件结果处理后续逻辑。

#!/bin/bash

# 定义单个备份任务函数
backup_task() {
    local src="$1"
    local dest="$2"
    local task_id="$3"
    local log_file="/tmp/backup_${task_id}.log"
    local result_file="/tmp/backup_${task_id}.result"

    # 执行rsync并记录日志
    rsync -a --itemize-changes "$src" "$dest" > "$log_file" 2>&1
    local rsync_exit_code=$?

    # 判断是否有文件更新:rsync的--itemize-changes输出中,非.开头的行代表变更
    local update_status="NO_UPDATE"
    if grep -v '^\.' "$log_file" | grep -q '^[><]'; then
        update_status="UPDATED"
    fi

    # 将结果写入临时文件:第一行是更新状态,第二行是rsync退出码
    echo "$update_status" > "$result_file"
    echo "$rsync_exit_code" >> "$result_file"
}

# --------------------------
# 启动多进程备份任务
# --------------------------
# 任务1
backup_task "/data/src/app1" "/backup/dest/app1" "app1" &
# 任务2
backup_task "/data/src/db1" "/backup/dest/db1" "db1" &
# 任务3
backup_task "/data/src/config" "/backup/dest/config" "config" &

# 等待所有后台备份任务完成
wait

# --------------------------
# 处理每个任务的后续逻辑
# --------------------------
for task_id in app1 db1 config; do
    result_file="/tmp/backup_${task_id}.result"
    log_file="/tmp/backup_${task_id}.log"
    
    update_status=$(head -n1 "$result_file")
    rsync_exit=$(tail -n1 "$result_file")

    if [ "$update_status" = "UPDATED" ]; then
        echo "[$task_id] 检测到更新,开始创建归档包..."
        tar -czf "/backup/archives/${task_id}_$(date +%Y%m%d).tar.gz" "/backup/dest/${task_id}"
    else
        echo "[$task_id] 无文件更新,跳过归档,写入报告..."
        echo "[$(date +'%F %T')] 备份任务${task_id}:无文件变更" >> "/backup/reports/daily_backup.log"
    fi

    # 清理临时文件
    rm "$log_file" "$result_file"
done

方案2:命名管道传递结果(无临时文件)

用命名管道替代临时文件传递任务结果,主进程启动一个后台子进程持续读取管道内容,任务完成后直接将结果写入管道,实现任务完成一个处理一个的流式逻辑。

#!/bin/bash

# 创建命名管道
RESULT_FIFO="/tmp/backup_results.fifo"
mkfifo "$RESULT_FIFO"

# 启动后台结果处理进程
(
    while read -r task_id update_status rsync_exit; do
        if [ "$update_status" = "UPDATED" ]; then
            echo "[$task_id] 有更新,生成归档..."
            tar -czf "/backup/archives/${task_id}_$(date +%Y%m%d).tar.gz" "/backup/dest/${task_id}"
        else
            echo "[$task_id] 无更新,记录报告..."
            echo "[$(date +'%F %T')] 任务${task_id}:无文件变更" >> "/backup/reports/daily_backup.log"
        fi
    done < "$RESULT_FIFO"
) &
PROCESSOR_PID=$!

# 定义备份任务函数
backup_task() {
    local src="$1"
    local dest="$2"
    local task_id="$3"
    local log_file="/tmp/backup_${task_id}.log"

    rsync -a --itemize-changes "$src" "$dest" > "$log_file" 2>&1
    local rsync_exit=$?

    local update_status="NO_UPDATE"
    if grep -v '^\.' "$log_file" | grep -q '^[><]'; then
        update_status="UPDATED"
    fi

    # 将结果写入管道
    echo "$task_id $update_status $rsync_exit" > "$RESULT_FIFO"
    rm "$log_file"
}

# 启动多备份任务
backup_task "/data/src/app1" "/backup/dest/app1" "app1" &
backup_task "/data/src/db1" "/backup/dest/db1" "db1" &
backup_task "/data/src/config" "/backup/dest/config" "config" &

# 等待所有备份任务完成
wait

# 关闭管道并等待结果处理进程结束
rm "$RESULT_FIFO"
wait "$PROCESSOR_PID"

方案3:PID关联任务上下文(逐个处理完成的任务)

用Bash关联数组将进程PID与任务ID、源/目标路径绑定,主进程逐个等待PID完成,直接通过数组获取对应任务的上下文信息,无需额外存储文件。

#!/bin/bash

# 定义关联数组存储任务信息:PID -> 任务ID,任务ID -> 源/目标路径
declare -A pid_to_task
declare -A task_src
declare -A task_dest

# 备份任务函数
backup_task() {
    local src="$1"
    local dest="$2"
    local task_id="$3"
    local log_file="/tmp/backup_${task_id}.log"

    rsync -a --itemize-changes "$src" "$dest" > "$log_file" 2>&1
    local rsync_exit=$?

    # 写入状态文件
    local status_file="/tmp/backup_${task_id}.status"
    if grep -v '^\.' "$log_file" | grep -q '^[><]'; then
        echo "UPDATED" > "$status_file"
    else
        echo "NO_UPDATE" > "$status_file"
    fi
    echo "$rsync_exit" >> "$status_file"
    rm "$log_file"
}

# 添加并启动任务
task_id="app1"
task_src[$task_id]="/data/src/app1"
task_dest[$task_id]="/backup/dest/app1"
backup_task "${task_src[$task_id]}" "${task_dest[$task_id]}" "$task_id" &
pid_to_task[$!]="$task_id"

task_id="db1"
task_src[$task_id]="/data/src/db1"
task_dest[$task_id]="/backup/dest/db1"
backup_task "${task_src[$task_id]}" "${task_dest[$task_id]}" "$task_id" &
pid_to_task[$!]="$task_id"

# 逐个等待任务完成并处理
for pid in "${!pid_to_task[@]}"; do
    wait "$pid"
    task_id="${pid_to_task[$pid]}"
    status_file="/tmp/backup_${task_id}.status"
    
    update_status=$(head -n1 "$status_file")
    rsync_exit=$(tail -n1 "$status_file")

    if [ "$update_status" = "UPDATED" ]; then
        echo "[$task_id] 完成更新,归档中..."
        tar -czf "/backup/archives/${task_id}_$(date +%Y%m%d).tar.gz" "${task_dest[$task_id]}"
    else
        echo "[$task_id] 无更新,写入报告..."
        echo "[$(date +'%F %T')] 任务${task_id}:无文件变更" >> "/backup/reports/daily_backup.log"
    fi

    rm "$status_file"
done

方案选择建议

  • 临时文件方案:逻辑最直观,适合任务数量较少的场景,调试和排查问题更方便
  • 命名管道方案:无临时文件残留,适合任务较多、希望流式处理结果的场景
  • PID关联方案:可实时处理已完成的任务,无需等待所有任务结束,适合对时效性有要求的场景

内容的提问来源于stack exchange,提问作者Richard T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:07:05