在bash中如何从一个巨大的文件中获取一行数据

22
我该如何在一个大小为3GB的文本文件中获取某一行?所有行的长度都相同,且用\n作为分隔符。我需要能够按需获取任何一行。请问应该如何操作?只需要返回一行即可。
6个回答

23
如果所有行的长度都相同,迄今为止最好的方法是使用dd(1)并给它一个跳过参数。
让块大小为每行的长度(包括换行符),然后您可以执行:
$ dd if=filename bs=<line-length> skip=<line_no - 1> count=1 2>/dev/null

这个想法是通过跳过之前的所有行 (skip=<line_no - 1>) 并读取一行 (count=1)。因为块大小设置为行长度 (bs=<line-length>),所以每个块实际上就是一行。将stderr重定向,这样你就不会在结尾得到烦人的统计信息。

这比通过一个程序流式传输所需行之前的所有行,然后将它们全部丢弃更有效,因为dd会跳转到文件中想要的位置并从文件中读取一行数据。


+1。基本上与我后来的解决方案相同,但具有明显的优势,不需要编写自己的程序。 - paxdiablo
1
那太有技术含量了。嘿嘿,我喜欢。 - JavaRocky

16

head -10 file | tail -1 返回文件的第10行,但速度可能较慢。

来源:这里

# print line number 52 
sed -n '52p' # method 1 
sed '52!d' # method 2 
sed '52q;d' # method 3, efficient on large files

当你正在寻找第32696行时,它会变得更加有用。 - Amanda

4
一个awk的替代方案,其中3是行号。
awk 'NR == 3 {print; exit}' file.txt

最好打印并退出,这样awk就不会继续遍历文件的剩余部分。 - ghostdog74

3
如果不是固定记录长度文件,并且您没有对行开头进行索引,那么最好的选择就是使用以下方法:
head -n N filespec | tail -1

其中N是您想要的行号。

不幸的是,对于一个3GB的文件,这不会是最佳性能的代码,但有办法让它变得更好。

如果文件变化不太频繁,您可能需要考虑对其进行索引。我的意思是有另一个文件,其中包含定长记录的行偏移量。

因此,该文件为:

0000000000
0000000017
0000000092
0000001023

会给你一个快速定位每一行的方法。只需将所需行号乘以索引记录大小,并在索引文件中寻找到该位置。
然后使用该位置上的值在主文件中查找,以便读取到下一个换行符为止。
因此,对于第3行,您将在索引文件中寻找33(索引记录长度为10个字符加上一个换行符)。读取那里的值0000000092,会给您在主文件中使用的偏移量。
当然,如果文件经常更改,这并不是很有用,尽管如果您可以控制追加时发生的事情,仍然可以有效地向索引添加偏移量。如果您不能控制,则必须在索引的最后修改日期早于主文件的日期时重新索引。

根据您的更新:

更新:如果重要的话,所有行都具有相同的长度。

有了这个额外的信息,您不需要索引 - 您可以通过将记录长度乘以记录长度立即定位到主文件中的正确位置(假设值适合您的数据类型)。
因此,类似于伪代码:
def getline(fhandle,reclen,recnum):
    seek to position reclen*recnum for file fhandle.
    read reclen characters into buffer.
    return buffer.

camh有更好的解决方案,但我会把这个留在这里,以备记录长度不固定的情况。 - paxdiablo

2
使用sedq一起来使搜索在打印出该行后停止。
sed -n '11723{p;q}' filename

Python (仅具有最小错误检查):

#!/usr/bin/env python
import sys

# by Dennis Williamson - 2010-05-08
# for https://dev59.com/bHE85IYBdhLWcg3wZyqt

# seeks the requested line in a file with a fixed line length

# Usage: ./lineseek.py LINE FILE

# Example: ./lineseek 11723 data.txt

EXIT_SUCCESS      = 0
EXIT_NOT_FOUND    = 1
EXIT_OPT_ERR      = 2
EXIT_FILE_ERR     = 3
EXIT_DATA_ERR     = 4

# could use a try block here
seekline = int(sys.argv[1])

file = sys.argv[2]

try:
    if file == '-':
        handle = sys.stdin
        size = 0
    else:
        handle = open(file,'r')
except IOError as e:
    print >> sys.stderr, ("File Open Error")
    exit(EXIT_FILE_ERR)

try:
    line = handle.readline()
    lineend = handle.tell()
    linelen = len(line)
except IOError as e:
    print >> sys.stderr, ("File I/O Error")
    exit(EXIT_FILE_ERR)

# it would be really weird if this happened
if lineend != linelen:
    print >> sys.stderr, ("Line length inconsistent")
    exit(EXIT_DATA_ERR)

handle.seek(linelen * (seekline - 1))

try:
    line = handle.readline()
except IOError as e:
    print >> sys.stderr, ("File I/O Error")
    exit(EXIT_FILE_ERR)

if len(line) != linelen:
    print >> sys.stderr, ("Line length inconsistent")
    exit(EXIT_DATA_ERR)

print(line)

参数验证应该更好,还有许多其他改进的空间。


1
一个快速的 Perl 一行代码也可以很好地解决这个问题...
$ perl -ne 'if (YOURLINENUMBER..YOURLINENUMBER) {print $_; last;}' /path/to/your/file

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