我们正在尝试使用 grep 和 sed 从我们的服务器的数千个文件中删除一行注入代码:
<script type='text/javascript' src='https://dest.collectfasttracks.com/y.js'></script>
根据我们的搜索,它似乎只直接注入了文件的第一部分。
我们测试的代码是:
grep -r -H "collectfasttracks" * | xargs sed -i '/<script type/=/'text/javascript/' src/=/'https/:\//dest/.collectfasttracks/.com\/y/.js/'/>/<\/script/>/d'' '{} \;
但它失败并出现错误:
sed: -e expression #1, char 16: extra characters after command
也许我们遗漏了 sed 命令中的某些内容。
答案1
这里不止一个问题。
命令中的每个单引号 (
'
) 都会被 shell 看到,并在引号删除阶段被删除。请参见printf '%s\n' '/<script type/=/'text/javascript/' src/=/'https/:\//dest/.collectfasttracks/.com\/y/.js/'/>/<\/script/>/d'' '{}
输出中没有单引号,这意味着没有单引号
printf
。似乎您尝试使用斜线 (/
) 转义一些单引号。问题:- 转义任何内容的正确字符是反斜杠 (
\
); - 单引号反斜杠无论如何都不会转义任何内容,因为单引号中没有转义,所以 shell 会按字面意思考虑任何单引号字符串。
要使单引号字符在引号删除阶段继续存在,您需要将其双引号化。如果您需要对字符串的其他部分使用单引号,请按如下方式操作:
printf '%s\n' 'single-quoted part'"'"'single-quoted as well'"'"'and again single-quoted'
这里
printf
仅显示哪些引号保留并进入命令。实际上,您将使用sed
或xargs
任何其他方式。如果您可以使用双引号括住整个字符串,它的可读性会更强:
printf '%s\n' "double-quoted part'double-quoted as well'and again double-quoted"
笔记
double-quoted as well
是双引号尽管相邻有单引号。就你的情况而言,我认为你可以用双引号括住整个字符串。一般来说,你应该记住,解析双引号字符串的 shell 会特别处理反引号 (
`
)、美元符号 ($
) 和反斜杠\
。- 转义任何内容的正确字符是反斜杠 (
sed
使用正则表达式,您似乎知道需要转义某些字符(例如.
),以使工具按字面意思处理它们。但您再次使用了大部分/
,这与转义无关。令人惊讶的是,您很少使用\
。请注意,上一点是关于在 shell 上下文中转义;现在我们讨论的是 中的转义
sed
。这是两个不同的问题。如果您决定用双引号括住整个字符串,那么请记住,有时\
对于 shell 来说很特殊。比较这个问题。我的回答解释了 shell 和 都echo
可以\
特殊处理。在您的例子中是 shell 和sed
但一般来说,问题是相似的,因为sed
都echo
可以\
特殊处理。看来您的总体思路是使用
/pattern/ d
脚本sed
。如果pattern
包含斜杠,则需要对其进行转义。您不仅未能对其进行转义,而且还引入了新的斜杠以试图“转义”其他字符。实际上,实际模式就是 just<script type
,下面=
是命令。该工具抱怨后面的内容=
。您的
pattern
地址包含斜线。为了避免转义斜线,您可以在指定地址时使用其他字符。比较这个答案。例如地址规范可以像这样:\@pattern@
现在您不需要转义斜线
pattern
(但如果有的话,您需要转义@
)。{}
与前面的字符串连接。printf
我的第一点显示了这一点。我想这不是你的本意。在您的代码中
{}
只是文字。如果您使用或但未使用,则{}
情况会有所不同。之后不是选项。xargs -I{} …
xargs -i …
-i
sed
xargs
\;
(或者甚至{} \;
)似乎取自的语法find … -exec … \;
。如果您sed
没有抛出错误,它将被视为;
要操作的文件的名称。grep -r -H …
打印路径(文件名)和匹配的行。您只想grep -r -l …
获取路径。*
被 shell 扩展,它可能会触发argument list too long
错误。你没有收到错误,所以这不是你的情况的问题。一般来说,这是可能的,所以使用.
而不是*
可能是一个好主意。区别:- 的扩展
*
省略了点文件;而的遍历.
则不省略。 - 的扩展
*
可以将符号链接作为命令行参数传递给grep
。默认情况下grep -r
会跳过符号链接,除非在命令行中明确提供它们。
- 的扩展
如果您想使用
*
,请考虑使用./*
。这很重要,因为 case*
会扩展为看起来像选项的内容。或者使用--
。如果任何文件名包含换行符(一般文件名可以),将此类字符串通过管道传输到需要将文件名作为行的命令将导致命令失败或行为异常。为避免这种情况,您需要将名称作为以空字符结尾的字符串传递,前提是您使用的工具仅支持此类操作模式。您
xargs
可能支持-0
,您grep
可能支持-z
,但这无济于事,因为-z
它不会影响 的输出grep -l
(至少grep
我的 Kubuntu 中的 GNU 就是这种情况;这不是唯一的问题,但已经足够了)。xargs
有时,与其将路径通过管道传输到 ,不如使用find … -exec …
。xargs
解释引号和反斜杠,除非您使用-0
(如果支持)或-d
(如果支持)。路径通常可能包含引号和/或反斜杠。仅出于这个原因,-0
即使换行符不是问题,您也应该使用。如上所述,grep -l
不会生成以空字符结尾的输出,但您仍然可以使用xargs -d '\n'
(如果-d
支持)来抑制对引号和反斜杠的解释。这种方法无法解决文件名中换行符的问题,但它可以解决可能被解释为引号或反斜杠的问题xargs
。
假设您想要*
而不是.
并且路径中没有换行符,这是改进的命令:
grep -rl "collectfasttracks" ./* | xargs -d '\n' sed -i "
\@<script type='text/javascript' src='https://dest\.collectfasttracks\.com/y\.js'></script>@ d
"
如果换行符可能是一个问题,那么find
正确的解决方案是:
find ./* -type f -exec grep -q "collectfasttracks" {} \; -exec sed -i "
\@<script type='text/javascript' src='https://dest\.collectfasttracks\.com/y\.js'></script>@ d
" {} +
它之所以有效是因为-exec … \;
也是一个测试。在这种情况下,退出状态0
表示grep
测试成功。
请注意,解决方案xargs
只运行一个grep
。然后将尽可能xargs
多的路径传递给单个;如果需要,将生成更多进程。这将表现得非常好。sed
sed
该解决方案将find
产生一个grep
任何文件。由于-exec … {} +
(与 相比-exec … \;
),进程数sed
将与 一样少xargs
,但大量的grep
s 仍会减慢整个解决方案的速度。另一方面,此解决方案非常便携,并且在文件名方面没有缺陷(它可以与任何文件名一起使用)。
如果您确定有许多文件匹配,而只有少数文件不匹配,则删除grep
可能是一个好主意:
find ./* -type f -exec sed -i "
\@<script type='text/javascript' src='https://dest\.collectfasttracks\.com/y\.js'></script>@ d
" {} +
在这种情况下,sed
将不必要地处理一些文件,但它可能比grep
为许多文件生成一个文件更快。
警告:所有上述解决方案都能够将多个参数(文件路径)传递给sed
。这对性能有好处,但我的测试(使用 GNU sed 4.4)表明,一个文件的问题(例如无法创建临时文件)可能会导致该工具中止并且不会处理剩余的文件。要完全独立地处理文件,您需要每个文件一个。您可以通过或(即而不是)sed
来实现这一点。… | xargs -n 1 … sed …
find … -exec sed … {} \;
\;
+