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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9682721/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 01:46:43  来源:igfitidea点击:

perform an operation for *each* item listed by grep

bashlistshellgrepexec

提问by speendo

How can I perform an operation for each item listed by grep individually?

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

Background:

背景:

I use grep to list all files containing a certain pattern:

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

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

I want to delete all listed files but alsoall files having the same file name but a different extension: .extension2.

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

I tried using the pipe, but it seems to take the output of grep as a whole.

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

In find there is the -execoption, but grep has nothing like that.

在 find 中有-exec选项,但 grep 没有这样的选项。

回答by sorpigal

If I understand your specification, you want:

如果我理解你的规格,你想要:

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

This is essentially the same as what @triplee's comment describes, except that it's newline-safe.

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

What's going on here?

这里发生了什么?

grepwith --nullwill return output delimited with nulls instead of newline. Since file names can have newlines in them delimiting with newline makes it impossible to parse the output of grepsafely, but null is not a valid character in a file name and thus makes a nice delimiter.

grepwith--null将返回以空值而不是换行符分隔的输出。由于文件名中可以​​有换行符,用换行符分隔使得无法grep安全地解析输出,但 null 不是文件名中的有效字符,因此是一个很好的分隔符。

xargswill take a stream of newline-delimited items and execute a given command, passing as many of those items (one as each parameter) to a given command (or to echoif no command is given). Thus if you said:

xargs将采用以换行符分隔的项目流并执行给定的命令,将尽可能多的项目(一个作为每个参数)传递给给定的命令(echo如果没有给出命令,则传递给)。因此,如果你说:

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

xargswould execute echo one 'two three' four. This is not safe for file names because, again, file names might contain embedded newlines.

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

The -0switch to xargschanges it from looking for a newline delimiter to a null delimiter. This makes it match the output we got from grep --nulland makes it safe for processing a list of file names.

-0交换机xargs从找换行分隔符为空分界符改变它。这使得它与我们得到的输出相匹配,grep --null并且可以安全地处理文件名列表。

Normally xargssimply appends the input to the end of a command. The -Iswitch to xargschanges this to substitution the specified replacement string with the input. To get the idea try this experiment:

通常xargs简单地将输入附加到命令的末尾。的-I开关来xargs改变这对代换指定替换字符串与输入。要获得这个想法,请尝试以下实验:

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

And note the difference from the earlier printf | xargscommand.

并注意与先前printf | xargs命令的区别。

In the case of my solution the command I execute is bash, to which I pass -c. The -cswitch causes bash to execute the commands in the following argument (and then terminate) instead of starting an interactive shell. The next block 'rm "$1" "${1%.*}.extension2"'is the first argument to -cand is the script which will be executed by bash. Any arguments following the script argument to -care assigned as the arguments to the script. This, if I were to say:

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

bash -c 'echo 
foo='my file.txt'
bar='this.is.a.file.txt'
baz='no extension'
printf '%s\n'"${foo%.*}" "${bar%.*}" "${baz%.*}"
' "Hello, world"

Then Hello, worldwould be assigned to $0(the first argument to the script) and inside the script I could echoit back.

然后Hello, world将被分配给$0(脚本的第一个参数)并且在脚本内部我可以echo返回。

Since $0is normally reserved for the script name I pass a dummy value (in this case --) as the first argument and, then, in place of the second argument I write {}, which is the replacement string I specified for xargs. This will be replaced by xargswith each file name parsed from grep's output before bashis executed.

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

The mini shell script might look complicated but it's rather trivial. First, the entire script is single-quoted to prevent the calling shell from interpreting it. Inside the script I invoke rmand pass it two file names to remove: the $1argument, which was the file name passed when the replacement string was substituted above, and ${1%.*}.extension2. This latter is a parameter substitution on the $1variable. The important part is %.*which says

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

  • %"Match from the end of the variable and remove the shortest string matching the pattern.
  • .*The pattern is a single period followed by anything.
  • %“从变量的末尾匹配并删除与模式匹配的最短字符串。
  • .*该模式是单个句点后跟任何内容。

This effectively strips the extension, if any, from the file name. You can observe the effect yourself:

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

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

Since the extension has been stripped I concatenate the desired alternate extension .extension2to the stripped file name to obtain the alternate file name.

由于扩展名已被剥离,我将所需的备用扩展名连接.extension2到剥离的文件名以获得备用文件名。

回答by ghoti

If this does what you want, pipe the output through /bin/sh.

如果这符合您的要求,请通过 /bin/sh 管道输出。

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

Or if sed makes you itchy:

或者如果 sed 让你发痒:

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

Remove echoif the output looks like the commands you want to run.

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

But you can do this with findas well:

但是你也可以这样做find

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

where ...is either the sed script or the three lines from whileto done.

...sed 脚本或从whileto 的三行在哪里done

The idea here is that findwill ... well, "find" things based on the qualifiers you give it -- namely, that things match the file glob "*.ext", AND that the result of the "exec" is successful. The -qtells grep to look for RE in {}(the file supplied by find), and exit with a TRUE or FALSE without generating any of its own output.

这里的想法是find……好吧,根据您提供的限定符“找到”事物——即,事物与文件 glob“*.ext”相匹配,并且“exec”的结果是成功的。将-q告诉grep来找找RE中{}(由提供的文件find),并退出与真或假,而不会产生任何它自己的输出。

The only real difference between doing this in find vs doing it with grep is that you get to use find's awesome collection of conditions to narrow down your search further if required. man findfor details. By default, find will recurse into subdirectories.

在 find 中执行此操作与使用 grep 执行此操作之间的唯一真正区别在于,如果需要,您可以使用 find 出色的条件集合来进一步缩小搜索范围。 man find详情。默认情况下, find 将递归到子目录中。

回答by Eduardo Ivanec

You can pipe the list to xargs:

您可以将列表通过管道传输到 xargs:

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

As for the second set of files with a different extension, I'd do this (as usual use xargs echo rmwhen testing to make a dry run; I haven't tested it, it may not work correctly with filenames with spaces in them):

至于具有不同扩展名的第二组文件,我会这样做(xargs echo rm在测试进行试运行时通常使用;我没有测试过它,它可能无法在文件名中有空格的情况下正常工作):

##代码##

回答by Anders Abel

Pipe the result to xargs, it will allow you to run a command for each match.

将结果传送到xargs,它将允许您为每个匹配运行一个命令。