做批量任务的时候,你有没有遇到过这种情况:任务跑完了,不知道哪些成功了、哪些失败了、失败的原因是什么?特别是用xargs配合timeout的时候,退出码的收集就成了个头疼的问题。今天这篇文章,就教你写一个实用的xargs并行任务timeout退出码收集脚本,让异常追踪变得清清楚楚。
为什么需要收集退出码?
xargs配合timeout使用的时候,退出码可不是简单的0和1。它能告诉你很多信息:
- 退出码0:任务正常完成
- 退出码123:xargs自己出了问题
- 退出码124:timeout超时了,任务被强制杀死
- 退出码125:xargs执行出错了,可能是权限问题或者命令不存在
- 退出码126/127:命令无法执行
- 其他非0退出码:任务本身返回的错误
如果不用脚本收集这些信息,你只能靠人工去猜到底是哪一步出了问题,非常浪费时间。
基础版本:简单记录每个任务的退出码
先来看一个最基础的实现思路。xargs的-I参数可以帮我们把每个输入项单独处理,配合一个记录函数就能收集退出码:
#!/bin/bash
TEMP_DIR=$(mktemp -d)
RESULT_FILE="$TEMP_DIR/results.txt"
FAILED_FILE="$TEMP_DIR/failed.txt"
process_item() {
local item="$1"
local start_time=$(date +%s)
timeout 5 your_command "$item"
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
echo "[$(date +"%Y-%m-%d %H:%M:%S")] Item: $item | Exit: $exit_code | Duration: ${duration}s" >> "$RESULT_FILE"
if [ $exit_code -ne 0 ]; then
echo "Item: $item | Exit: $exit_code | Duration: ${duration}s" >> "$FAILED_FILE"
fi
}
export -f process_item
export TEMP_DIR RESULT_FILE FAILED_FILE
cat input.txt | xargs -P4 -I {} bash -c "process_item" _ {}
echo "=== 执行完成 ==="
echo "总结果: $RESULT_FILE"
echo "失败记录: $FAILED_FILE"
进阶版本:带日志和详细信息的收集器
上面的版本比较简单,生产环境我们往往需要更详细的信息,比如任务开始时间、结束时间、执行耗时、输出内容等:
#!/bin/bash
LOG_DIR="./task_logs_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$LOG_DIR"
SUMMARY_FILE="$LOG_DIR/summary.csv"
echo "item,start_time,end_time,duration,exit_code,status" > "$SUMMARY_FILE"
process_with_logging() {
local item="$1"
local log_file="$LOG_DIR/${item//\//_}.log"
local start_time=$(date +%s.%N)
local start_human=$(date +"%Y-%m-%d %H:%M:%S")
timeout 10 your_command "$item" > "$log_file" 2>&1
local exit_code=$?
local end_time=$(date +%s.%N)
local end_human=$(date +"%Y-%m-%d %H:%M:%S")
local duration=$(echo "$end_time - $start_time" | bc)
local status="success"
case $exit_code in
124) status="timeout" ;;
125|126|127) status="execute_error" ;;
0) status="success" ;;
*) status="task_failed" ;;
esac
echo "\"$item\",\"$start_human\",\"$end_human\",\"$duration\",\"$exit_code\",\"$status\"" >> "$SUMMARY_FILE"
}
export -f process_with_logging
export LOG_DIR SUMMARY_FILE
cat input.txt | xargs -P8 -I {} bash -c "process_with_logging" _ {}
echo "=== 执行统计 ==="
awk -F',' 'NR>1 {total++; if($6=="timeout") timeouts++; else if($6=="success") successes++; sum+=$4} END {print "总数: " total; print "成功: " successes; print "超时: " timeouts}' "$SUMMARY_FILE"
生产可用版本:带重试和告警的收集脚本
真正的生产环境,我们需要考虑更多:网络不稳怎么办?任务卡住了怎么办?失败了要不要自动重试?下面是更完善的实现:
#!/bin/bash
MAX_RETRIES=2
TIMEOUT_SEC=30
PARALLEL_NUM=4
LOG_DIR="./batch_logs_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$LOG_DIR"
SUCCESS_COUNT=0
TIMEOUT_COUNT=0
FAILED_COUNT=0
ERROR_COUNT=0
log_result() {
local item="$1" attempt="$2" exit_code="$3" duration="$4"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
case $exit_code in
0) ((SUCCESS_COUNT++));;
124) ((TIMEOUT_COUNT++));;
125|126|127) ((ERROR_COUNT++));;
*) ((FAILED_COUNT++));;
esac
echo "[$timestamp] item=$item attempt=$attempt exit=$exit_code duration=${duration}s" >> "$LOG_DIR/results.log"
}
run_task() {
local item="$1" attempt=0
while [ $attempt -lt $MAX_RETRIES ]; do
((attempt++))
local start=$(date +%s)
timeout $TIMEOUT_SEC bash -c "your_command $item" > "$LOG_DIR/${item//\//_}_${attempt}.log" 2>&1
local exit_code=$? end=$(date +%s) duration=$((end-start))
log_result "$item" "$attempt" "$exit_code" "$duration"
if [ $exit_code -eq 0 ]; then return 0
elif [ $exit_code -ne 124 ]; then return $exit_code
fi
[ $attempt -lt $MAX_RETRIES ] && sleep 2
done
return $exit_code
}
export -f run_task
export SUCCESS_COUNT TIMEOUT_COUNT FAILED_COUNT ERROR_COUNT MAX_RETRIES TIMEOUT_SEC LOG_DIR
echo "开始批量任务..."
start_total=$(date +%s)
cat input.txt | xargs -P$PARALLEL_NUM -I {} bash -c "run_task" _ {}
end_total=$(date +%s)
echo "========== 执行报告 =========="
echo "总耗时: $((end_total - start_total)) 秒"
echo " 成功: $SUCCESS_COUNT"
echo " 超时: $TIMEOUT_COUNT"
echo " 失败: $FAILED_COUNT"
echo " 错误: $ERROR_COUNT"
常见问题排查
1. xargs并行执行时退出码丢失
有时候发现退出码都是0,但实际上任务失败了。这是因为xargs的退出码取决于最后执行的命令,而不是所有命令。使用-0参数配合find或者-I参数可以正确收集每个任务的退出码。
2. 超时任务的信号捕获
timeout命令默认发送SIGTERM信号,如果程序不响应,会再发SIGKILL。退出码124表示被SIGTERM杀死,137表示被SIGKILL(124+9)。在脚本中可以用trap捕获这些信号。
3. 并行数设置建议
并行数不是越大越好。太大会导致系统资源耗尽,反而拖慢速度。建议:
- IO密集型任务:并行数可以设大一些,比如8-16
- CPU密集型任务:并行数设为CPU核心数
- 混合型任务:根据系统负载动态调整
总结
xargs并行任务配合timeout的退出码收集,其实不是什么高深的技术,关键在于思路清晰:
- 先用-I参数隔离每个任务的执行
- 通过$?捕获每个任务的退出码
- 把结果写入文件,方便后续分析
- 配合统计命令生成执行报告
根据实际需求逐步完善脚本功能,从简单到复杂,从能用到好用,这才是正确的学习路径。
如果你的批量任务经常出问题,退出码收集一定要做。它能帮你快速定位问题,而不是一个个日志文件去翻。
相关推荐:
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论