能否在单个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
相关产品推荐
相关产品推荐

