繁体   English   中英

对grep列出的*每个*项目执行操作

[英]perform an operation for *each* item listed by grep

如何分别对grep列出的每个项目执行操作?

背景:

我使用grep列出了包含特定模式的所有文件:

grep -l '<pattern>' directory/*.extension1

我想删除所有列出的文件,还要删除所有具有相同文件名但扩展名不同的文件: .extension2

我尝试使用管道,但它似乎将grep的输出作为一个整体。

在查找中有-exec选项,但是grep没有这样的东西。

如果我了解您的规格,则需要:

grep --null -l '<pattern>' directory/*.extension1 | \
    xargs -n 1 -0 -I{} bash -c 'rm "$1" "${1%.*}.extension2"' -- {}

这与@triplee的注释所描述的基本相同,只是它是换行安全的。

这里发生了什么?

--null grep将返回以null代替换行符的输出。 由于文件名中可以​​包含换行符,因此用换行符定界将无法安全地解析grep的输出,但是null在文件名中不是有效字符,因此是一个很好的定界符。

xargs将使用换行符分隔的项目流并执行给定的命令,将这些项目(每个参数一个)传递给给定的命令(如果没有给出命令,则echo )。 因此,如果您说:

printf 'one\ntwo three \nfour\n' | xargs echo

xargs将执行echo one 'two three' four 这对于文件名来说是不安全的,因为同样,文件名可能包含嵌入的换行符。

-0切换到xargs会将其从寻找换行符分隔符更改为空分隔符。 这使其与我们从grep --null获得的输出匹配,并使其可以安全地处理文件名列表。

通常, xargs只是将输入附加到命令的末尾。 -I切换到xargs会将其更改为用输入替换指定的替换字符串。 要获得想法,请尝试以下实验:

printf 'one\ntwo three \nfour\n' | xargs -I{} echo foo {} bar

并注意与早期的printf | xargs printf | xargs命令。

对于我的解决方案,我执行的命令是bash ,我将-c传递给该命令。 -c开关使bash在以下参数中执行命令(然后终止),而不是启动交互式shell。 下一个块'rm "$1" "${1%.*}.extension2"'-c的第一个参数,它是将由bash执行的脚本。 -c脚本参数之后的所有参数都将分配为脚本参数。 如果我要说的话:

bash -c 'echo $0' "Hello, world"

然后, Hello, worldHello, world分配给$0 (脚本的第一个参数),然后在脚本中echo它。

由于通常为脚本名称保留$0 ,因此我将一个虚拟值(在本例中为-- )作为第一个参数传递,然后代替第二个参数,我写了{} ,这是我为xargs指定的替换字符串。 在执行bash之前,此文件将由xargs替换,每个文件名均由grep的输出解析。

迷你shell脚本可能看起来很复杂,但是却很琐碎。 首先,整个脚本都用单引号引起来,以防止调用Shell对其进行解释。 在脚本中,我调用rm并将其传递给它删除两个文件名: $1参数,即上面替换替换字符串时传递的文件名,以及${1%.*}.extension2 后者是$1变量上的参数替换。 重要的部分是%.* ,其中表示

  • % “从变量末尾开始匹配,并删除与模式匹配的最短字符串。
  • .*模式是单个句点,后跟任何东西。

这样可以有效地从文件名中删除扩展名(如果有)。 您可以自己观察效果:

foo='my file.txt'
bar='this.is.a.file.txt'
baz='no extension'
printf '%s\n'"${foo%.*}" "${bar%.*}" "${baz%.*}"

由于扩展名已被剥离,因此我将所需的替代扩展名.extension2连接到剥离的文件名,以获得替代文件​​名。

如果这样做符合您的要求,则通过/ bin / sh传递输出。

grep -l 'RE' folder/*.ext1 | sed 's/\(.*\).ext1/rm "&" "\1.ext2"/'

或者,如果sed让您发痒:

grep -l 'RE' folder/*.ext1 | while read file; do
  echo rm "$file" "${file%.ext1}.ext2"
done

如果输出看起来像您要运行的命令,请删除echo

但是您也可以使用find来做到这一点:

find /path/to/start -name \*.ext1 -exec grep -q 'RE' {} \; -print | ...

其中...是sed脚本或从whiledone三行。

这里的想法是, find会...根据给定的限定符“查找”事物,即,事物与文件glob“ * .ext”匹配,并且“ exec”的结果成功。 -q告诉grep在{} (由find提供的文件)中查找RE,然后以TRUE或FALSE退出​​而不生成其自身的任何输出。

在find和使用grep进行搜索之间唯一真正的区别是,可以根据需要使用find的出色条件集合进一步缩小搜索范围。 man find详细信息。 默认情况下,find将递归到子目录中。

您可以将列表通过管道传递给xargs:

grep -l '<pattern>' directory/*.extension1 | xargs rm

至于第二组具有不同扩展名的文件,我会这样做(通常在进行xargs echo rm运行测试时使用xargs echo rm ;我尚未对其进行测试,它可能不适用于其中包含空格的文件名):

filelist=$(grep -l '<pattern>' directory/*.extension1)
echo $filelist | xargs rm
echo ${filelist//.extension1/.extension2} | xargs rm

将结果通过管道传递给xargs ,它将允许您为每个匹配项运行命令。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM