强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第08章 批量处理

第08章 批量处理

8.1 mogrify 批量操作

8.1.1 基本批量操作

# 批量缩放(⚠️ 直接覆盖原文件!)
gm mogrify -resize 800x600 *.jpg

# 批量转换格式
gm mogrify -format png *.jpg

# 批量设置质量
gm mogrify -quality 85 *.jpg

# 批量添加边框
gm mogrify -border 5 -bordercolor white *.jpg

8.1.2 使用 -path 避免覆盖

# 输出到指定目录(原文件不变)
mkdir -p output/
gm mogrify -path output/ -resize 800x600 input/*.jpg

# 保留目录结构
gm mogrify -path output/ -resize 800x600 \
  input/subdir1/*.jpg input/subdir2/*.jpg

8.1.3 mogrify 完整参数

参数说明示例
-path DIR输出目录-path output/
-format FMT输出格式-format png
-resize G缩放-resize 800x600
-quality N质量-quality 85
-strip去除元数据-strip
-auto-orientEXIF 校正-auto-orient
-normalize自动对比度-normalize
-sharpen G锐化-sharpen 0x1
-blur G模糊-blur 0x2
-rotate N旋转-rotate 90
-flip垂直翻转-flip
-flop水平翻转-flop
-crop G裁剪-crop 400x300+0+0
-gravity G锚点-gravity center
-trim去空白边-trim
-border G边框-border 5
-bordercolor C边框色-bordercolor white
-colorspace S色彩空间-colorspace Gray
-density NDPI-density 72
-filter F滤波器-filter Lanczos
-monitor进度显示-monitor

8.1.4 条件处理

# 仅处理特定尺寸以上的图片
for img in *.jpg; do
  W=$(gm identify -format "%w" "$img")
  if [ "$W" -gt 1920 ]; then
    gm convert "$img" -resize 1920x -quality 85 "resized/$img"
    echo "缩放: $img ($W -> 1920)"
  fi
done

# 仅处理特定格式
gm mogrify -path output/ -resize 800x600 *.png

# 仅处理大于特定大小的文件
find . -name "*.jpg" -size +1M -exec \
  gm mogrify -path large_output/ -resize 1200x {} \;

8.2 并行处理

8.2.1 GNU Parallel 并行

# 使用 GNU Parallel 加速批量处理
ls *.jpg | parallel -j 4 \
  gm convert {} -resize 800x600 -quality 85 output/{}

# 指定 CPU 核心数
ls *.jpg | parallel -j $(nproc) \
  gm convert {} -resize 800x600 output/{}

# 复杂处理流水线
ls *.jpg | parallel -j 4 '
  input={}
  output="output/$(basename $input)"
  gm convert "$input" \
    -auto-orient \
    -resize "1200x1200>" \
    -quality 85 \
    -strip \
    "$output"
'

8.2.2 xargs 并行

# xargs 并行(-P 参数)
ls *.jpg | xargs -P 4 -I {} \
  gm convert {} -resize 800x600 output/{}

# 处理子目录中的所有图片
find . -name "*.jpg" | xargs -P $(nproc) -I {} \
  gm convert {} -resize 800x600 output/{}

8.2.3 批量处理脚本框架

#!/bin/bash
# batch_process.sh — 高效批量处理框架
# 用法: ./batch_process.sh input_dir output_dir [jobs]

INPUT_DIR="${1:-.}"
OUTPUT_DIR="${2:-output}"
JOBS="${3:-$(nproc)}"

mkdir -p "$OUTPUT_DIR"

# 统计
TOTAL=$(find "$INPUT_DIR" -maxdepth 1 -name "*.jpg" | wc -l)
echo "找到 $TOTAL 张图片,使用 $JOBS 个并行任务"

# 处理函数
process_image() {
  local input="$1"
  local output="$OUTPUT_DIR/$(basename "$input")"
  gm convert "$input" \
    -auto-orient \
    -resize "1200x1200>" \
    -quality 85 \
    -strip \
    -unsharp-mask 0x0.5+0.5+0 \
    "$output" 2>/dev/null && echo "✅ $(basename "$input")" || echo "❌ $(basename "$input")"
}

export -f process_image
export OUTPUT_DIR

# 并行执行
find "$INPUT_DIR" -maxdepth 1 -name "*.jpg" | \
  parallel -j "$JOBS" process_image {}

echo ""
echo "处理完成!输出目录: $OUTPUT_DIR"
echo "共处理 $(ls "$OUTPUT_DIR"/*.jpg 2>/dev/null | wc -l) 张图片"

8.2.4 并行方式对比

方法优点缺点适用场景
for 循环简单、兼容性好单线程少量文件
xargs -P无需额外安装控制粒度粗中等批量
parallel功能强大、进度显示需安装大规模批量
mogrify最简单功能有限简单批量

8.3 高效脚本编写

8.3.1 进度显示

#!/bin/bash
# batch_with_progress.sh — 带进度条的批量处理

INPUT_DIR="$1"
OUTPUT_DIR="output"
mkdir -p "$OUTPUT_DIR"

FILES=($INPUT_DIR/*.jpg)
TOTAL=${#FILES[@]}
COUNT=0

for img in "${FILES[@]}"; do
  ((COUNT++))
  PERCENT=$((COUNT * 100 / TOTAL))
  PROGRESS=$((PERCENT / 2))
  BAR=$(printf '%*s' "$PROGRESS" '' | tr ' ' '█')
  SPACE=$(printf '%*s' $((50 - PROGRESS)) '' | tr ' ' '░')

  printf "\r[%s%s] %d/%d (%d%%) %s" \
    "$BAR" "$SPACE" "$COUNT" "$TOTAL" "$PERCENT" "$(basename "$img")"

  gm convert "$img" \
    -auto-orient -resize "800x600>" \
    -quality 85 -strip \
    "$OUTPUT_DIR/$(basename "$img")" 2>/dev/null
done

echo ""
echo "✅ 全部完成!"

8.3.2 错误处理与日志

#!/bin/bash
# batch_with_logging.sh — 带错误处理的批量处理

LOG_FILE="batch_$(date +%Y%m%d_%H%M%S).log"
OUTPUT_DIR="output"
mkdir -p "$OUTPUT_DIR"

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

SUCCESS=0
FAILED=0

log "开始批量处理"
log "输出目录: $OUTPUT_DIR"

for img in *.jpg; do
  [ -f "$img" ] || continue

  if gm convert "$img" \
    -auto-orient -resize "800x600>" \
    -quality 85 -strip \
    "$OUTPUT_DIR/$img" 2>>"$LOG_FILE"; then
    ((SUCCESS++))
    log "✅ 成功: $img"
  else
    ((FAILED++))
    log "❌ 失败: $img"
  fi
done

log "处理完成: 成功=$SUCCESS, 失败=$FAILED"
log "日志: $LOG_FILE"

8.4 内存管理

8.4.1 内存限制设置

# 查看当前资源限制
gm convert -list resource

# 设置环境变量限制内存使用
export MAGICK_MEMORY_LIMIT=2GiB      # 内存使用限制
export MAGICK_MAP_LIMIT=4GiB         # 内存映射限制
export MAGICK_DISK_LIMIT=16GiB       # 磁盘临时文件限制
export MAGICK_FILE_LIMIT=768         # 打开文件数限制
export MAGICK_AREA_LIMIT=128MP       # 最大像素面积
export MAGICK_TMPDIR=/dev/shm/gm-tmp # 临时文件目录

# 在命令行中临时设置
MAGICK_MEMORY_LIMIT=1GiB gm convert huge_input.jpg output.jpg

8.4.2 内存限制参数说明

环境变量说明推荐值(生产环境)
MAGICK_MEMORY_LIMIT总内存限制2GiB-8GiB
MAGICK_MAP_LIMITmmap 映射限制4GiB-16GiB
MAGICK_DISK_LIMIT临时文件总大小8GiB-32GiB
MAGICK_FILE_LIMIT最大打开文件数768-2048
MAGICK_AREA_LIMIT最大图像面积128MP-256MP
MAGICK_TMPDIR临时目录/dev/shm (RAM盘)

8.4.3 内存估算

估算公式(近似值):
内存 ≈ 宽 × 高 × 通道数 × (quantum-depth/8) × 2

示例:
8000×6000 RGB JPEG (quantum-depth=16):
  8000 × 6000 × 3 × (16/8) × 2 = 576,000,000 bytes ≈ 576MB

4000×3000 RGBA PNG (quantum-depth=16):
  4000 × 3000 × 4 × 2 × 2 = 192,000,000 bytes ≈ 192MB

8.4.4 大图处理策略

# 策略 1:分块处理
# 对于超大图像,先缩小再处理
gm convert huge_20000x15000.jpg \
  -resize 25% \
  -blur 0x2 \
  -resize 400% \
  output.jpg

# 策略 2:使用 -limit 临时限制
gm convert -limit memory 1GiB -limit map 2GiB \
  huge_input.jpg output.jpg

# 策略 3:减少色彩深度
gm convert huge_input.jpg -depth 8 -quality 85 output.jpg

# 策略 4:使用 map 减少内存占用
gm convert -map huge_input.jpg output.jpg

8.5 性能优化

8.5.1 临时文件优化

# 使用 RAM 盘(/dev/shm)作为临时目录
export MAGICK_TMPDIR=/dev/shm/gm-tmp
mkdir -p /dev/shm/gm-tmp

# 对比:SSD vs RAM 盘
# /tmp (SSD): ~500MB/s 写入
# /dev/shm (RAM): ~5000MB/s 写入
# 大量临时文件操作时性能提升 5-10x

8.5.2 流式处理

# 流式处理(减少磁盘 I/O)
# 错误方式:多次读写
gm convert input.jpg -resize 800 /tmp/step1.jpg
gm convert /tmp/step1.jpg -quality 85 /tmp/step2.jpg
gm convert /tmp/step2.jpg -strip output.jpg

# 正确方式:单次处理
gm convert input.jpg -resize 800 -quality 85 -strip output.jpg

8.5.3 减少中间步骤

# 不好:创建中间文件
gm convert input.jpg -resize 800x600 /tmp/resized.jpg
gm convert /tmp/resized.jpg -rotate 90 /tmp/rotated.jpg
gm convert /tmp/rotated.jpg -quality 85 output.jpg
rm -f /tmp/resized.jpg /tmp/rotated.jpg

# 好:管道处理
gm convert input.jpg -resize 800x600 -rotate 90 -quality 85 output.jpg

# 好:使用 subshell(需要多步操作时)
gm convert input.jpg \
  \( -clone 0 -resize 400x300 \) \
  \( -clone 0 -resize 200x150 \) \
  -delete 0 \
  output_%d.jpg

8.5.4 批量处理性能对比

处理方式100 张图片耗时内存峰值
串行 for 循环~45s200MB
xargs -P 4~12s800MB
parallel -j 4~11s800MB
mogrify~40s150MB
parallel + RAM盘~8s800MB

8.6 实战场景

场景:图片 CDN 服务

#!/bin/bash
# cdn_resize.sh — CDN 图片动态处理
# 用法: ./cdn_resize.sh <input> <width> <height> <quality> <format>

INPUT="$1"
WIDTH="${2:-800}"
HEIGHT="${3:-600}"
QUALITY="${4:-85}"
FORMAT="${5:-jpg}"

OUTPUT="cdn_${WIDTH}x${HEIGHT}_q${QUALITY}.${FORMAT}"

gm convert "$INPUT" \
  -filter Lanczos \
  -resize "${WIDTH}x${HEIGHT}>" \
  -gravity center \
  -extent "${WIDTH}x${HEIGHT}" \
  -quality "$QUALITY" \
  -strip \
  -interlace Plane \
  -sampling-factor 4:2:0 \
  "$OUTPUT"

# 输出文件大小信息
SIZE=$(stat -f%z "$OUTPUT" 2>/dev/null || stat -c%s "$OUTPUT")
echo "$OUTPUT: ${SIZE} bytes"

场景:自动化图片审核流水线

#!/bin/bash
# image_audit.sh — 自动化图片审核与优化流水线

INPUT_DIR="uploads/"
AUDIT_DIR="audit/"
APPROVED_DIR="approved/"
REJECTED_DIR="rejected/"
LOG="audit.log"

mkdir -p "$AUDIT_DIR" "$APPROVED_DIR" "$REJECTED_DIR"

log() { echo "[$(date '+%H:%M:%S')] $1" >> "$LOG"; }

for img in "$INPUT_DIR"*.{jpg,jpeg,png,webp}; do
  [ -f "$img" ] || continue
  BASENAME=$(basename "$img")

  # 审核检查
  INFO=$(gm identify -format "%w %h %b %m" "$img" 2>/dev/null)
  [ -z "$INFO" ] && { log "❌ 无法读取: $BASENAME"; continue; }

  W=$(echo $INFO | awk '{print $1}')
  H=$(echo $INFO | awk '{print $2}')

  # 检查最小尺寸
  if [ "$W" -lt 200 ] || [ "$H" -lt 200 ]; then
    log "❌ 尺寸不足: $BASENAME (${W}x${H})"
    cp "$img" "$REJECTED_DIR"
    continue
  fi

  # 检查是否过大
  if [ "$W" -gt 8000 ] || [ "$H" -gt 8000 ]; then
    log "⚠️ 尺寸过大,自动缩放: $BASENAME (${W}x${H})"
    gm convert "$img" -resize "4000x4000>" "$AUDIT_DIR$BASENAME"
  else
    cp "$img" "$AUDIT_DIR$BASENAME"
  fi

  # 优化处理
  gm mogrify -path "$APPROVED_DIR" \
    -auto-orient -strip -quality 85 \
    "$AUDIT_DIR$BASENAME"

  log "✅ 通过: $BASENAME"
done

log "审核完成: 通过=$(ls $APPROVED_DIR | wc -l), 拒绝=$(ls $REJECTED_DIR | wc -l)"

场景:定时图片清理与归档

#!/bin/bash
# image_archive.sh — 定时清理与归档旧图片

ARCHIVE_DIR="archive/$(date +%Y/%m)"
TEMP_DIR="temp_uploads/"
KEEP_DAYS=30

mkdir -p "$ARCHIVE_DIR"

# 找到超过保留天数的文件
find "$TEMP_DIR" -name "*.jpg" -mtime +$KEEP_DAYS | while read img; do
  BASENAME=$(basename "$img")
  # 压缩归档
  gm convert "$img" -quality 70 -strip "$ARCHIVE_DIR/$BASENAME"
  # 删除原文件
  rm "$img"
  echo "归档: $BASENAME"
done

# 清理空目录
find "$TEMP_DIR" -type d -empty -delete 2>/dev/null

8.7 高级批量技巧

8.7.1 使用文件列表批量处理

# 从文件列表读取
cat filelist.txt | while read img; do
  gm convert "$img" -resize 800x600 "output/$(basename "$img")"
done

# 生成文件列表(带尺寸信息)
gm identify -format "%f %wx%h\n" *.jpg > image_list.txt

# 按尺寸筛选后处理
awk '$2 > 1920 {print $1}' image_list.txt | while read img; do
  gm convert "$img" -resize 1920x "resized/$img"
done

8.7.2 条件批量处理

# 根据文件大小选择不同质量
for img in *.jpg; do
  SIZE=$(stat -c%s "$img" 2>/dev/null || stat -f%z "$img")
  if [ "$SIZE" -gt 5242880 ]; then
    QUALITY=70  # >5MB 的用低质量
  elif [ "$SIZE" -gt 1048576 ]; then
    QUALITY=80  # >1MB 的用中等质量
  else
    QUALITY=90  # <1MB 的用高质量
  fi
  gm convert "$img" -quality "$QUALITY" "optimized/$img"
done

8.7.3 多尺寸批量输出

#!/bin/bash
# multi_size_batch.sh — 批量生成多尺寸
# 用法: ./multi_size_batch.sh input_dir

INPUT_DIR="$1"
SIZES=("150x150" "400x300" "800x600" "1200x900")
SIZE_NAMES=("thumb" "small" "medium" "large")

for img in "$INPUT_DIR"/*.jpg; do
  [ -f "$img" ] || continue
  BASENAME=$(basename "${img%.*}")

  for i in "${!SIZES[@]}"; do
    SIZE="${SIZES[$i]}"
    NAME="${SIZE_NAMES[$i]}"
    mkdir -p "output/$NAME"
    gm convert "$img" \
      -resize "${SIZE}>" \
      -gravity center -extent "${SIZE}" \
      -quality 85 -strip \
      "output/$NAME/${BASENAME}.jpg" &
  done

  # 等待当前图片的所有尺寸处理完成
  wait
done

echo "批量处理完成!"
for NAME in "${SIZE_NAMES[@]}"; do
  echo "  $NAME: $(ls output/$NAME/ | wc -l) 张"
done

8.8 批量处理操作速查表

场景命令
批量缩放gm mogrify -path out/ -resize 800 *.jpg
批量转格式gm mogrify -format png *.jpg
批量去元数据gm mogrify -strip *.jpg
批量自动旋转gm mogrify -auto-orient *.jpg
并行处理ls *.jpg | parallel -j4 gm convert ... {}
条件处理结合 for 循环和 if 判断
多尺寸输出遍历尺寸数组
流式处理合并命令到单个 gm convert
RAM 盘优化MAGICK_TMPDIR=/dev/shm

8.9 本章小结

要点说明
mogrify 适合简单批量配合 -path 避免覆盖
GNU Parallel 是最佳并行工具-j $(nproc) 充分利用 CPU
RAM 盘优化临时文件/dev/shm 提升 5-10x I/O
流式处理减少磁盘 I/O一步完成多步操作
设置 MAGICK_* 限制避免 OOM 和磁盘爆满
错误处理和日志很重要生产环境必须

扩展阅读

  1. GNU Parallel 教程
  2. Bash 高级脚本编写
  3. Linux 内存管理与 tmpfs
  4. GraphicsMagick 资源管理
  5. Shell 脚本最佳实践

上一章第07章 图像特效 下一章第09章 图像格式详解