在多个文件中使用grep搜索,获取最后一行

47

我目前遇到了一些grep命令的问题。

我已经找到了只显示grep搜索结果中最后一行的方法:


grep PATERN FILE_NAME | tail -1

我也找到了一种方法,在多个选择的文件中进行grep搜索:

find . -name "FILE_NAME" | xargs -I name grep PATERN name

现在我想为每个单独的文件仅获取 grep 结果的最后一行。 我尝试了这个:

 find . -name "FILE_NAME" | xargs -I name grep PATERN name | tail -1

这个只返回我最后一个文件的最后一个值,但我想要每个文件的最后一个匹配模式。

11个回答

48
for f in $(find . -name "FILE_NAME"); do grep PATTERN $f | tail -1; done

1
遗憾的是,如果您正在搜索整个文件并仅返回最后一个结果,则对于大文件或复杂模式搜索效率不高。您可以在for循环中尝试以下操作: tac file | grep -m1 -oP '(?<=tag>).*(?=</tag>)' | head -n 1 甚至使用以下方式 grep -m1 -oP '(?<=tag>).*(?=</tag>)' <<(tac file) - kisna
2
查看为什么循环遍历find的输出是不好的实践?。find有-exec选项可以做到这一点,即使文件名包含空格/换行符等也可以正常工作... - Sundeep

11

Sort命令有一个uniq选项,允许你从多行中选择一行。试试这个:

grep PATTERN FILENAMES* | tac | sort -u -t: -k1,1

说明: Grep会为文件中的每个匹配项返回一行。它的输出如下:

$ grep match file*
file1.txt:match
file1.txt:match2
file2.txt:match3
file2.txt:match4

我们需要的是从输出中获取两行信息:

$ ???
file1.txt:match2
file2.txt:match4

你可以把它看作一种表格,其中第一列是文件名,第二列是匹配项,列分隔符是“:”字符。
我们的第一个管道将输出反转:
$ grep match file* | tac
file2.txt:match4
file2.txt:match3
file1.txt:match2
file1.txt:match

我们需要对第二个管道进行排序,命令是:提取第一个唯一行(-u),按照第一个键(-k1,1,从第1列到第1列的键)进行分组,并使用“:”作为分隔符将数据拆分成列。它还会对输出进行排序!其输出如下:
$ grep match file* | tac sort -u -t: -k1,1
file1.txt:match2
file2.txt:match4

1
太好了。这比被接受的答案更短更快。 - rashid
我猜你的最后一个命令应该是 grep match file* | tac | sort -u -t: -k1,1(即第二个管道符号缺失)。 - ZaydH

2

可以使用awk而不是grep来完成另一种替代方案。一个符合Posix标准的版本如下:

Original Answer: 最初的回答

awk '(FNR==1)&&s{print s; s=""}/PATTERN/{s=$0}END{if(s) print s}' file1 file2 file3 ...

使用GNU awk,您可以使用ENDFILE。最初的回答中提到了这一点。
awk 'BEGINFILE{s=""}/PATTERN/{s=$0}ENDFILE{if(s) print s}' file1 file2 file3 ...

0
你可以从grep的-B(前面)参数开始。例如,要获取匹配前5行:
duli@i5 /etc/php5/apache2 $ grep -i -B5 timezone php.ini 
[CLI Server]
; Whether the CLI web server uses ANSI color coding in its terminal output.
cli_server.color = On

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
;date.timezone =

0

获取每个文件的最后一行(以文件名为前缀)。然后,根据模式过滤输出。

find . -name "*" -exec tail -v -n1 {} \; | grep "some_string" -B1

在 macOS 上,你必须以稍微不同的方式来做。
find . -name "*" | xargs tail -1 | grep "some_string" -B1

0

另一种找到最后一行的方法是反转文件并输出第一个匹配项。

find . -name "FILE_NAME" | xargs -I name sh -c 'tac name|sed -n "/PATTERN/{p;q}"'

0
你也可以使用find执行命令:

find . -name "<file-name-to-find>" -exec grep "<pattern-to-match>" "{}" ";" | tail -1

“{}”是文件名,请注意在编写命令时避免使用shell globbing和expansion。

0

七年晚了,参加派对的人已经走了。修改命令行的方法很慢:

find . -name "FILE_NAME" | xargs -I name sh -c "grep PATERN name | tail -1"

如果您需要在每行中显示文件名:
find . -name "FILE_NAME" | xargs -I name sh -c "grep -H PATERN name | tail -1"

-1

有一种解决方案无需循环,可以得到OP想要的结果。

find . -type f -exec sh -c "fgrep print {} /dev/null |tail -1" \;

./tway.pl:print map(lambda x : x[1], filter(lambda x : x[0].startswith('volume'), globals().items()))
./txml.py:           print("%s does not exist: %s\n" % (host, error))
./utils.py:print combine_dicts(a, b, operator.mul)
./xml_example.py:print ET.tostring(root, method="text")

与使用 tail -1 相比,每个文件的行数过多,但证明了上述方法可行。

find . -type f -exec sh -c "fgrep print {} /dev/null" \;

给出:

./tway.pl:print map(lambda x : x[1], filter(lambda x : x[0].startswith('volume'), globals().items()))
./txml.py:           print("%s resolved to --> %s\n" % (host, ip))
./txml.py:           print("%s does not exist: %s\n" % (host, error))
./utils.py:print "a", a
./utils.py:print "b", b
./utils.py:print combine_dicts(a, b, operator.mul)
./xml_example.py:    print ">>"
./xml_example.py:    print ET.tostring(e, method="text")
./xml_example.py:    print "<<"
./xml_example.py:print ET.tostring(root, method="text")

编辑 - 如果您不想在输出中包含文件名,请删除 /dev/null。


-1

sed 版本

# As soon as we find pattern
# we save that line in hold space
save_pattern_line='/PATTERN/{h;d}'

# switch pattern and hold space
switch_spaces='x'

# At the end of the file
# if the pattern is in the pattern space
# (which we swapped with our hold space)
# switch again, print and exit
eof_print='${/PATTERN/{x;p;d}}'

# Else, switch pattern and hold space
switch_spaces='x'

find . -name 'FILE_NAME' |
  xargs sed -s -n -e $save_pattern_line \
    -e $switch_spaces \
    -e $eof_print \
    -e $switch_spaces

脚本中变量的使用很奇怪,而且未加引号的变量使用是一个错误。 - tripleee
@tripleee 为什么这是个 bug? - CervEd
何时在shell变量周围加引号 - tripleee
@tripleee,这里没有空格,但存在shell解释的特殊字符,使用双引号将是个错误。使用变量可能不是惯例,但它可以使sed脚本各部分在脚本中有清晰注释。如果有其他改进答案的建议,欢迎提出。 - CervEd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接