如何将 xargs 与 pdftotext 转换器耦合以在多个 pdf 文件中搜索

how to couple xargs with pdftotext converter to search inside multiple pdf files

我正在制作一个脚本,该脚本应该在目录中的所有 pdf 文件中进行搜索。我找到了一个名为 "pdftotext" 的转换文件,它使我能够在 pef 文件上使用 grep,但我只能对一个文件进行 运行。当我想 运行 它遍历目录中存在的所有文件时,它就会失败。有什么建议么 ?

这个works:for一个文件

pdftotext my_file.pdf - | grep 'hot'

这失败了:用于搜索 pdf 文件并转换为文本和 greping

SHELL PROMPT>find ~/.personal/tips -type f -iname "*" | grep -i "*.pdf" | xargs pdftotext |grep admin
pdftotext version 3.00
Copyright 1996-2004 Glyph & Cog, LLC
Usage: pdftotext [options] <PDF-file> [<text-file>]
  -f <int>          : first page to convert
  -l <int>          : last page to convert
  -layout           : maintain original physical layout
  -raw              : keep strings in content stream order
  -htmlmeta         : generate a simple HTML file, including the meta information
  -enc <string>     : output text encoding name
  -eol <string>     : output end-of-line convention (unix, dos, or mac)
  -nopgbrk          : don't insert page breaks between pages
  -opw <string>     : owner password (for encrypted files)
  -upw <string>     : user password (for encrypted files)
  -q                : don't print any messages or errors
  -cfg <string>     : configuration file to use in place of .xpdfrc
  -v                : print copyright and version info
  -h                : print usage information
  -help             : print usage information
  --help            : print usage information
  -?                : print usage information
SHELL PROMPT 139>

xargs 是这项工作的错误工具:find 内置了您需要的一切。

find ~/.personal/tips \
    -type f \
    -iname "*.pdf" \
    -exec pdftotext '{}' - ';' \
  | grep hot

就是说,如果您 确实 出于某种原因想要使用 xargs,正确的用法应该类似于...

find ~/.personal/tips \
    -type f \
    -iname "*.pdf" \
    -print0 \
  | xargs -0 -J % -n 1 pdftotext % - \
  | grep hot

注意:

  • find 命令使用 -print0 对其输出进行 NUL 分隔
  • xargs 命令使用 -0 对其输入进行 NUL 分隔(这也关闭了一些行为,这些行为会导致不正确处理名称中包含空格、文字引号字符等的文件名).
  • xargs命令使用-n 1为每个文件调用一次pdftotext
  • xargs 命令使用 -J % 指定替换应该发生的地方的印记,并在 pdftotext 命令行中适当地使用 %
find . -name '*.pdf' -print0 | xargs -0 -n1 -I '{}' pdftotext '{}' -

默认情况下,xargs 将尝试在 pdftotext 的命令行上放置尽可能多的行。你不想要那个。 您想要的是每次调用一个文件,后跟“-”。这可以通过 -n1(每次调用限制为一个参数)和 -I '{}'(使 {} 成为参数适合的占位符)来实现。

find 的 -print0 选项与 xargs 的 -0 选项相结合,使得两者都使用 '\0'(空字节)而不是换行符('\n')作为参数分隔符。

Xargs 与 -n1-I{} 这样使用在语义上几乎等同于 Charles Duffy 推荐的 find -exec。 Xargs 具有可以利用多核处理器的优势(它可以 运行 一次 pdftotext 的多个实例;您可以使用 -P 开关配置数量)。

通过正则表达式连接每个 pdf 中找到的所有代码并使用找到的代码重命名每个 pdf 文件名的答案。

要在PDF文件中搜索的shell正则表达式对应的代码示例

  • File1.pdf:X123456
  • File1.pdf:A1234567
  • File2.pdf:X003456
  • File2.pdf:A0034567

因此 File1 和 File2 文件将被重命名:

  • X123456_A1234567_File1.pdf
  • X003456_A0034567_File2.pdf

文件名批次find_codes_in_pdf_and_rename.sh

待执行 chmod +x find_codes_in_pdf_and_rename.sh

执行并输出到屏幕和日志(sed 在 Windows 下使用 CR+LF 可读)。 ./find_codes_in_pdf_and_rename.sh 2>&1 | tee | sed -u 's/$/\r/' 2>&1 | tee find_codes_in_pdf_and_rename.sh_$(date "+%Y_%m_%d_%Hh_%M_%S").log

#!/bin/bash -e


PrevFile=""
PrevCodes=""
mycmd1=""
mycmd2=""

DIRPrevFile="."
DIRFile="."

BASEFile=""

# look for files where the extension is pdf
# -print0 to have character zero to manage file name with space

find /my_path/ -iname "*.pdf" -print0 |  
# head for debug only two files, -z for print0 
# # head -z -n 2 |  
# sort, -z for print0 
sort -z| 
# exclude filename with code yet in filename, -z for print0
grep -z -v   -E   ".*[\s\.\/][A-Z][0-9]{6,7}.*" | 
# list filename:code
xargs -0 pdfgrep  -i  --only-matching  --with-filename -e "([A-Z]{1}[0-9]{6,7})"  2>&1 |
# exclude  "pdfgrep: Could not open"
tee| grep -v "pdfgrep: Could not open" |
# exclude empty lines
grep -v -e '^$' |
# find path of filename in regexp code group 1 
# and code in regexp code group 3 
# and keep only that in the list with the character ':' at the middle. 
# It's partially redundant if pdfgrep works well with --only-matching
sed --regexp-extended -e   's/(.):(.*)([A-Z][0-9]{6,7})(.*)/:/gm' |
uniq| {
   while read line
   do
       File=$( echo "$line" |cut -d\: -f1 )
       code=$( echo "$line" |cut -d\: -f2 )

       #echo File $File
       #echo code $code

       if [ "$PrevFile" == "" ]
       then
           PrevFile=$File
       fi

       if [ "$PrevFile" == "$File" ] && [ -n "$PrevCodes" ]
       then
           # concatenate all previous code to current code for the same filename 
           PrevCodes="${PrevCodes} ${code}"
       else
           PrevCodes=$code
       fi
       # uniques codes
       PrevCodes=$(echo  $PrevCodes | tr ' ' '\n' | sort | uniq | tr '\n' ' ')
  
       # echo $PrevCodes
       DIRPrevFile=`dirname "${PrevFile}"`
       DIRFile=`dirname "${File}"`
       #echo $DIRPrevFile   
   
       if [ "${DIRPrevFile}/${PrevFile}" != "${DIRFile}/${File}" ]
       then
           # computed at the previous loop of filename
           # echo "MVFake ${mycmd1}" "${mycmd2}"
           set -x
           mv "${mycmd1}" "${mycmd2}"
           set +x
        fi
   
        # to remove old PDF extension
        BASEFile=$(echo `basename  "${File}" .pdf` )
   
        # mycmd1: old filename
        mycmd1="$File"
   
        # concatenate all codes with the old filename, and replace . and space with _
        target=$(echo "${PrevCodes} ${BASEFile}" | sed "s/[ .]/_/g" ) 
        mycmd2=$(echo "${DIRPrevFile}/${target}.pdf" )

        PrevFile=$File
    done
    # echo "MVFake ${mycmd1}" "${mycmd2}"
    set -x
    mv "${mycmd1}" "${mycmd2}"
    set +x
}

这是一个 Linux 问题,因此主要是如何使用命令行在 Linux 中搜索所有 pdf 文件以查找“hot”。

对于 windows 用户,您需要使用 for 或 forfiles 稍微不同的语法来递归目录,例如:-

forfiles /P "C:\Users\WDAGUtilityAccount\Desktop\SandBox" /S /M *.pdf /C "cmd /c pdftotext @file  - |find /I \" hot \"

然而,这会产生大量混合输出,包括许多 pdf 错误与有效输出混合在一起,例如

Syntax Warning: Invalid Font Weight
Syntax Warning: Invalid Font Weight
identifies hot (frequently executed) bytecode sequences, records
their time in hot loops. Even in dynamically typed languages, we
....
.....

但是有一个更简单的方法,那就是(首先确保你安装了 pdf iFilter)只需在文件搜索中添加“hot”,所以我们在这里找到了所有沙盒文件夹中有 26 个结果。