在GNU Parallel中并行化嵌套for循环

时间:2013-09-20 12:11:37

标签: bash parallel-processing tesseract gnu-parallel

我有一个小的bash脚本来OCR PDF文件(略微修改this脚本)。每个文件的基本流程为:

对于pdf文件中的每个页面:

  1. 将页面转换为TIFF图像(imegamagick)
  2. OCR图像(tesseract)
  3. Cat结果到文本文件
  4. 脚本:

    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
    

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

2 个答案:

答案 0 :(得分:7)

由于您有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)。

答案 1 :(得分: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