如果从脚本文件或命令行执行,find 和 grep 会产生不同的输出

如果从脚本文件或命令行执行,find 和 grep 会产生不同的输出

我使用这些命令在给定文件路径的情况下在多个 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

它输出每个 pdf 中的每一行。输出: 输出每个 pdf 中的每一行

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会失败(无关紧要);然后你的文件就没了;然后#开始评论,无论如何。

解决方案

这是传递你的正确{}方法$phrasesh 安全地

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" \;

相关内容