shell 我可以对整个命令管道进行Bash通配符扩展(*)吗?

zxlwwiss  于 2023-01-26  发布在  Shell
关注(0)|答案(3)|浏览(153)

我使用Linux。我有一个包含许多文件的目录,我想使用greptail和通配符扩展*来打印每个文件中最后出现的:

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”命令仅运行一次。

ogq8wdun

ogq8wdun1#

通配符在任何命令运行前都会在命令行上展开。例如,如果您的目录中有foobar文件,并运行grep pattern * | tail -n1,则bash会将其转换为grep pattern foo bar | tail -n1并运行。由于grep只有一个输出流,因此只有一个输入流需要跟踪,并且它会打印该流的最后一行。
如果你想搜索每个文件并分别打印grep输出的最后一行,你可以使用一个循环:

for file in * ; do
  grep pattern "${file}" | tail -n1
done

非循环解决方案的问题是tail本身并不知道一个文件的输出在哪里结束,另一个文件的输出在哪里开始,甚至不知道管道的另一端涉及到文件。它只知道输入来自某个地方,它必须打印输入的最后一行。如果你不想循环,您必须使用更强大的工具,如awk,并且可能需要使用grep将匹配文件的名称放在前面(如果匹配多个文件,或使用-H)来分隔每个文件输出的开始和结束。但是,编写一个awk程序来跟踪当前文件以了解其输出何时结束并打印其最后一行,在循环解决方案如此简单的情况下,这样做可能会付出更多的努力而不值得。

pzfprimi

pzfprimi2#

您可以使用xargs实现您想要的功能。对于您的示例,它将是:

ls * | xargs -n 1 sh -c 'grep "pattern" $0 | tail -n 1'

可以保存您不必编写循环。

ffx8fchx

ffx8fchx3#

你可以用awk来完成这个任务,尽管(正如tjm3772在他们的回答中指出的)它实际上比shell for循环更复杂。

awk -v pattern="YourPatternHere" '(FNR==1 && line!="") {print line; line=""}; $0~pattern {line=$0}; END {if (line!="") print line}'

说明:当它找到匹配行($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循环更简单(也更容易正确)。

相关问题