我使用这些命令在给定文件路径的情况下在多个 pdf 中进行搜索:
>>find /home/ad0x/Documents/Skola/Flervariabel/Tentor -name '*.pdf' -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "phrase"' \;
phrase
您要在 PDF 中搜索的术语在 哪里。这按预期工作。我得到了单词“volym”的所有出现位置。
当我尝试在 .sh 脚本 (search.sh) 中执行同样的事情时
#!/bin/bash
read -p "Enter term to search for: " phrase
find /home/ad0x/Documents/Skola/Flervariabel/Tentor -name '*.pdf' -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \;
echo "Search completed"
>>./search.sh
>>Enter term to search for:volym
read
我怀疑它与如何解释输入有关,但我还没有在网上找到解决我的问题的方法。
答案1
直接罪魁祸首是$phrase
单引号。这不是唯一的问题。
会发生什么
这是相关的代码(请注意,我使用省略号…
来表示最不有趣的部分;这样的行是为了让人类理解,而不是直接在 shell 中执行):
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"' \;
解释脚本的 shell 保存phrase
变量的值;假设该值为volym
。在上面的命令中,单引号中的所有内容都保持不变,因为这是单引号的工作方式;因此$phrase
尚未展开。shell 仅解析\
它,告知它以下内容;
并非用于分隔命令,而应将其视为 的命令行参数find
。
当该find
实用程序运行时,它将其视为参数(从第 0 个开始,即其find
本身;每行一个参数,除非…
表示多个不太有趣的参数):
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color "$phrase"
;
请注意,倒数第二行是一个长论点。
假设foo.pdf
被找到,-exec
并将执行其工作。-exec
和之间的所有参数在被替换为;
之后成为新命令。 新命令将是(同样,从第 0 个参数开始;每行一个参数):{}
foo.pdf
sh
-c
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase"
因此sh
运行时,它会获取-c
并因此知道应该运行下一个参数,就像在命令行中输入一样:
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "$phrase"
这是扩展的时刻$phrase
。它扩展为空(最后一个单词变为""
),因为它尚未在此 shell 中设置。volym
如果您在脚本中导出变量,它将扩展为;但您没有。不过我不会导出;在我看来,在这种情况下导出会不必要地污染环境。
解决方案?尚未
放在$phrase
单引号外面似乎听起来是个好主意。在某些情况下,它会起作用。最简单的方法:
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'$phrase'"' \;
这是有缺陷的。根据这句话," ; -exec rm "{}
我们find
将看到以下论点:
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color ""
;
-exec
rm
"{}"
;
你的 PDF 不见了。人为的例子?也许吧。即使你是唯一一个使用该脚本的人,这种代码注入漏洞也没什么好处。
这是因为$phrase
根本没有被引用。您可能知道您应该几乎总是将变量放在双引号中。让我们这样做。一种改进的方法:
find … -exec sh -c 'pdftotext "{}" - | grep --with-filename --label="{}" --color "'"$phrase"'"' \;
" ; -exec rm "{}
按照这个短语find
可以看到:
find
…
-exec
sh
-c
pdftotext "{}" - | grep --with-filename --label="{}" --color "" ; -exec rm "{}"
;
看起来好一些;但仍然有缺陷,因为 forfoo.pdf
sh
会尝试运行:
pdftotext "foo.pdf" - | grep --with-filename --label="foo.pdf" --color "" ; -exec rm "foo.pdf"
最后一部分很可能会抛出错误,因为没有-exec
命令。如果短语是 会怎么样" ; rm "{}
?如果是 会怎么样" ; rm -rf ~/"
。
还有更多。让短语为volym
(相当安全),但命名您的 PDF 之一"; rm -rf ~ #.pdf
(这在少数文件系统中是可能的,包括 ext 系列)。{}
替换 -s后将sh
运行如下内容:
pdftotext "/home/ad0x/…/"; rm -rf ~ #.pdf" - | grep …
我猜pdftotext
会失败(无关紧要);然后你的文件就没了;然后#
开始评论,无论如何。
解决方案
这是传递你的正确{}
方法$phrase
sh
安全地:
find … -exec sh -c 'pdftotext "$1" - | grep --with-filename --label="$1" --color "$2"' dummy {} "$phrase" \;
当sh
执行给定的命令字符串时,$1
会扩展为find
替换的内容{}
,$2
会扩展为原始 shell 替换的内容$phrase
。在上下文中,sh
这些参数被正确引用,因此您无法再注入代码。(我的另一个答案解释dummy
)。
即使现在,仍有改进的空间。如果短语是 会怎样-f
?该grep
部分最终将是:
grep --with-filename --label="…" --color "-f"
它会抱怨缺少参数。用于--
指示选项的结束;-f
之后--
不会被视为选项。同样适用于pdftotext
(尽管在您的特定情况下,每个 PDF 路径都必须以开头,/home
因此不能将其解释为选项;但通常$1
可以扩展为看起来像选项的字符串)。我们的sh
调用已经免疫,因为sh
在命令字符串之前采用选项,并且我们的命令字符串不会被误认为是选项(仍然sh -c -- 'pdftotext …' …
不会造成伤害)。更强大的命令:
find … -exec sh -c 'pdftotext -- "$1" - | grep --with-filename --label="$1" --color -- "$2"' dummy {} "$phrase" \;