【问题标题】:Parallelize nested for loop in GNU Parallel在 GNU Parallel 中并行化嵌套的 for 循环
【发布时间】:2013-09-20 12:11:37
【问题描述】:

我有一个用于 OCR PDF 文件的小 bash 脚本(稍微修改了this 脚本)。每个文件的基本流程是:

对于 pdf 文件中的每一页:

  1. 将页面转换为 TIFF 图像 (imegamagick)
  2. OCR 图像(正方体)
  3. 猫结果到文本文件

脚本:

FILES=/home/tgr/OCR/input/*.pdf
for f in $FILES
do

  FILENAME=$(basename "$f") 
  ENDPAGE=$(pdfinfo $f | grep "^Pages: *[0-9]\+$" | sed 's/.* //')
  OUTPUT="/home/tgr/OCR/output/${FILENAME%.*}.txt"
  RESOLUTION=1400
  touch $OUTPUT
  for i in `seq 1 $ENDPAGE`; do
      convert -monochrome -density $RESOLUTION $f\[$(($i - 1 ))\] page.tif
      echo processing file $f, page $i
      tesseract page.tif tempoutput -l ces
      cat tempoutput.txt >> $OUTPUT
  done

  rm tempoutput.txt
  rm page.tif
done

由于分辨率高且 tesseract 只能使用一个核心,这个过程非常缓慢(转换一个 PDF 文件大约需要 3 分钟)。

因为我有数千个 PDF 文件,我想我可以使用 parallel 来使用所有 4 个内核,但我不知道如何使用它。在examples 我看到了:

Nested for-loops like this:

  (for x in `cat xlist` ; do
    for y in `cat ylist` ; do
      do_something $x $y
    done
  done) | process_output
can be written like this:

parallel do_something {1} {2} :::: xlist ylist | process_output

不幸的是,我无法弄清楚如何应用它。如何并行化我的脚本?

【问题讨论】:

    标签: bash parallel-processing tesseract gnu-parallel


    【解决方案1】:

    由于您有 1000 多个 PDF 文件,因此并行处理 PDF 文件而不是并行处理单个文件中的页面可能就足够了。

    function convert_func {
      f=$1
      FILENAME=$(basename "$f") 
      ENDPAGE=$(pdfinfo $f | grep "^Pages: *[0-9]\+$" | sed 's/.* //')
      OUTPUT="/home/tgr/OCR/output/${FILENAME%.*}.txt"
      RESOLUTION=1400
      touch $OUTPUT
      for i in `seq 1 $ENDPAGE`; do
          convert -monochrome -density $RESOLUTION $f\[$(($i - 1 ))\] $$.tif
          echo processing file $f, page $i
          tesseract $$.tif $$ -l ces
          cat $$.txt >> $OUTPUT
      done
    
      rm $$.txt
      rm $$.tif
    }
    
    export -f convert_func
    
    parallel convert_func ::: /home/tgr/OCR/input/*.pdf
    

    观看介绍视频以快速了解: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

    浏览教程(man parallel_tutorial 或 http://www.gnu.org/software/parallel/parallel_tutorial.html)。你命令行 爱你。

    阅读示例 (LESS=+/EXAMPLE: man parallel)。

    【讨论】:

      【解决方案2】:

      你可以有这样的脚本。

      #!/bin/bash
      
      function convert_func {
          local FILE=$1 RESOLUTION=$2 PAGE_INDEX=$3 OUTPUT=$4
          local TEMP0=$(exec mktemp --suffix ".00.$PAGE_INDEX.tif")
          local TEMP1=$(exec mktemp --suffix ".01.$PAGE_INDEX")
          echo convert -monochrome -density "$RESOLUTION" "${FILE}[$(( PAGE_INDEX - 1 ))]" "$TEMP0"  ## Just for debugging purposes.
          convert -monochrome -density "$RESOLUTION" "${FILE}[$(( PAGE_INDEX - 1 ))]" "$TEMP0"
          echo "processing file $FILE, page $PAGE_INDEX"  ## I think you mean to place this before the line above.
          tesseract "$TEMP0" "$TEMP1" -l ces
          cat "$TEMP1".txt >> "$OUTPUT"  ## Lines may be mixed up from different processes here and a workaround may still be needed but it may no longer be necessary if outputs are small enough.
          rm -f "$TEMP0" "$TEMP1"
      }
      
      export -f convert_func
      
      FILES=(/home/tgr/OCR/input/*.pdf)
      
      for F in "${FILES[@]}"; do
          FILENAME=${F##*/}
          ENDPAGE=$(exec pdfinfo "$F" | grep '^Pages: *[0-9]\+$' | sed 's/.* //')
          OUTPUT="/home/tgr/OCR/output/${FILENAME%.*}.txt"
          RESOLUTION=1400
          touch "$OUTPUT"  ## This may no longer be necessary. Or probably you mean to truncate it instead e.g. : > "$OUTPUT"
      
          for (( I = 1; I <= ENDPAGE; ++I )); do
              printf "%s\xFF%s\xFF%s\xFF%s\x00" "$F" "$RESOLUTION" "$I" "$OUTPUT"
          done | parallel -0 -C $'\xFF' -j 4 -- convert_func '{1}' '{2}' '{3}' '{4}'
      done
      

      它导出一个可由parallel 导入的函数,对参数进行适当的清理,并提供独特的临时文件以使并行处理成为可能。

      更新。这将首先保存多个临时文件的输出,然后将它们连接到一个主输出文件。

      #!/bin/bash
      
      shopt -s nullglob
      
      function convert_func {
          local FILE=$1 RESOLUTION=$2 PAGE_INDEX=$3 OUTPUT=$4 TEMPLISTFILE=$5
      
          local TEMP_TIF=$(exec mktemp --suffix ".01.$PAGE_INDEX.tif")
          local TEMP_TXT_BASE=$(exec mktemp --suffix ".02.$PAGE_INDEX")
      
          echo "processing file $FILE, page $PAGE_INDEX"
      
          echo convert -monochrome -density "$RESOLUTION" "${FILE}[$(( PAGE_INDEX - 1 ))]" "$TEMP_TIF"  ## Just for debugging purposes.
          convert -monochrome -density "$RESOLUTION" "${FILE}[$(( PAGE_INDEX - 1 ))]" "$TEMP_TXT_BASE"
      
          tesseract "$TEMP_TIF" "$TEMP_TXT_BASE" -l ces
      
          echo "$PAGE_INDEX"$'\t'"${TEMP_TXT_BASE}.txt" >> "$TEMPLISTFILE"
      
          rm -f "$TEMP_TIF"
      }
      
      export -f convert_func
      
      FILES=(/home/tgr/OCR/input/*.pdf)
      
      for F in "${FILES[@]}"; do
          FILENAME=${F##*/}
          ENDPAGE=$(exec pdfinfo "$F" | grep '^Pages: *[0-9]\+$' | sed 's/.* //')
          BASENAME=${FILENAME%.*}
          OUTPUT="/home/tgr/OCR/output/$BASENAME.txt"
          RESOLUTION=1400
      
          TEMPLISTFILE=$(exec mktemp --suffix ".00.$BASENAME")
          : > "$TEMPLISTFILE"
      
          for (( I = 1; I <= ENDPAGE; ++I )); do
              printf "%s\xFF%s\xFF%s\xFF%s\x00" "$F" "$RESOLUTION" "$I" "$OUTPUT"
          done | parallel -0 -C $'\xFF' -j 4 -- convert_func '{1}' '{2}' '{3}' '{4}' "$TEMPLISTFILE"
      
          while IFS=$'\t' read -r __ FILE; do
              cat "$FILE"
              rm -f "$FILE"
          done < <(exec sort -n "$TEMPLISTFILE") > "$OUTPUT"
      
          rm -f "$TEMPLISTFILE"
      done
      

      【讨论】:

      • 谢谢,需要一些时间来测试一下。由于单个文件的多页是并行处理的,如何保证Page1的OCR结果先于Page2的OCR结果进入输出文件?
      • @TomasGreif 您需要改用临时文件,并且仅当页面 id 已经离开 4 个作业时,才将这些临时文件的内容附加到主 txt 文件中。同样在循环结束后,追加剩余的临时文件。
      • 我更新了。显然我的想法实际上是不可能应用的。这次需要先保存文件,然后再将它们连接到一个输出。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多