[英]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, world
将Hello, 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脚本或从while
到done
三行。
这里的想法是, 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.