我使用Linux。我有一个包含许多文件的目录,我想使用grep
,tail
和通配符扩展*
来打印每个文件中最后出现的:
Input: <some command>
Expected Output:
<last occurrence of pattern in file 1>
<last occurrence of pattern in file 2>
...
<last occurrence of pattern in file N>
我现在尝试的是grep "pattern" * | tail -n 1
,但是输出只包含一行,这是最后一个文件中最后一次出现pattern,我认为原因是因为*
通配符扩展发生在命令流水线之前,所以tail
只运行一次。
是否存在一些Bash语法,以便我可以实现预期的结果,即让tail
为每个文件运行?
- 我知道我总是可以用for循环来解决这个问题,我只是好奇这个问题是否可以用一个更简洁的命令来解决。
我也尝试过grep -m1 "pattern" <(tac *)
,似乎前面提到的推理仍然适用:通配符扩展仅适用于与其关联的立即命令,并且“outer”命令仅运行一次。
3条答案
按热度按时间ogq8wdun1#
通配符在任何命令运行前都会在命令行上展开。例如,如果您的目录中有
foo
和bar
文件,并运行grep pattern * | tail -n1
,则bash会将其转换为grep pattern foo bar | tail -n1
并运行。由于grep只有一个输出流,因此只有一个输入流需要跟踪,并且它会打印该流的最后一行。如果你想搜索每个文件并分别打印grep输出的最后一行,你可以使用一个循环:
非循环解决方案的问题是
tail
本身并不知道一个文件的输出在哪里结束,另一个文件的输出在哪里开始,甚至不知道管道的另一端涉及到文件。它只知道输入来自某个地方,它必须打印输入的最后一行。如果你不想循环,您必须使用更强大的工具,如awk
,并且可能需要使用grep将匹配文件的名称放在前面(如果匹配多个文件,或使用-H
)来分隔每个文件输出的开始和结束。但是,编写一个awk
程序来跟踪当前文件以了解其输出何时结束并打印其最后一行,在循环解决方案如此简单的情况下,这样做可能会付出更多的努力而不值得。pzfprimi2#
您可以使用xargs实现您想要的功能。对于您的示例,它将是:
可以保存您不必编写循环。
ffx8fchx3#
你可以用
awk
来完成这个任务,尽管(正如tjm3772在他们的回答中指出的)它实际上比shellfor
循环更复杂。说明:当它找到匹配行(
$0~pattern
)时,它将该行存储在line
变量({line=$0}
)中(这意味着在文件末尾,line
将保存最后一个匹配行)。(Note:如果您只想在程序中包含一个文本模式,请删除
-v pattern="YourPatternHere"
部分,并仅用/YourPatternHere/
替换$0~pattern
)没有简单的触发器在每个文件的末尾打印匹配项,因此该部分被分为两部分:如果它是文件的第一行,并且
line
是由于与前一个文件匹配而设置的((FNR==1 && line!="")
),则打印line
,然后清除它,以便不会将其误认为是当前文件中的匹配项({print line; line=""}
)。最后,在最终文件(END
)的末尾,打印在最后一个文件中找到的匹配项(如果存在)({if (line!="") print line}
)。另外,请注意print-at-beginning-of-new-file测试 * 必须 * 在检查匹配行之前,否则如果新文件的第一行匹配,它会非常混乱。
所以......是的,shell
for
循环更简单(也更容易正确)。