2010-05-08 2 views
14

Как я могу получить конкретную строку в текстовом файле 3 гигабайта. Все линии имеют:Получение одной строки в огромном файле с bash

  • одинаковой длины и
  • разграничены \n.

И мне нужно иметь возможность получить любую линию по требованию.

Как это можно сделать? Необходимо вернуть только одну строку.

ответ

18

Если все строки имеют одинаковую длину, лучший способ, безусловно, будет использовать 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 будет искать позицию, которую вы хотите в файле, и читать только одну строку данных из файла.

+0

+1. В основном то же самое, что и мое более позднее решение после добавления фрагмента фиксированного размера записи в вопрос, но имеет явное преимущество в том, что вам не нужно писать свою собственную программу. – paxdiablo

+1

Это так дерзко. Хех. dd, мне это нравится. – JavaRocky

3

Если это не фиксированный рекорд длины файла и не делать какие-то индексации на линии начинается, лучше всего будет просто использовать:

head -n N filespec | tail -1 

где N номер строки вы хотите.

Это, к сожалению, не самый эффективный фрагмент кода для файла 3Gb, но есть способы сделать его лучше.

Если файл не изменяется слишком часто, вы можете захотеть его проиндексировать. Под этим я имею в виду наличие другого файла с смещениями строки в нем как фиксированные записи длины.

Так файл:

0000000000 
0000000017 
0000000092 
0000001023 

даст вам быстрый способ найти каждую строку. Просто умножьте желаемый номер строки на размер записи индекса и найдите там в индексном файле.

Затем используйте значение в этом месте для поиска в основном файле, чтобы вы могли читать до следующего символа новой строки.

Итак, для строки 3 вы должны искать в индексном файле 33 (длина индексной записи - 10 символов плюс еще одна для новой строки). Чтение значения там, 0000000092, даст вам смещение для использования в основной файл.

Конечно, это не так полезно, если файл изменяется часто, хотя, если вы можете контролировать, что происходит, когда что-то добавляется, вы все равно можете добавить смещения к индексу эффективно. Если вы не должны, то вам нужно будет повторно индексировать всякий раз, когда дата последнего изменения индекса будет раньше, чем дата основного файла.


И, основываясь на вашем обновлении:

Update: Если это имеет значение, все строки имеют одинаковую длину.

С помощью этой дополнительной информации вам не нужен указатель - вы можете просто сразу же найти нужное место в главном файле, умножив длину записи на длину записи (при условии, что значения соответствуют вашим значениям типы данных).

Так что-то вроде псевдокода:

def getline(fhandle,reclen,recnum): 
    seek to position reclen*recnum for file fhandle. 
    read reclen characters into buffer. 
    return buffer. 
+0

У камеры есть лучшее решение, но я оставлю это здесь для случая, когда записи не фиксированы. – paxdiablo

+0

Спасибо за это, очень информативно. – JavaRocky

14

head -10 file | tail -1 возвращает линию 10, вероятно, медленно, хотя.

из here

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

Получается более полезным, если вы ищете строку 32696. – Amanda

2

Альтернатива awk, где 3 - номер строки.

awk 'NR == 3 {print; exit}' file.txt 
+0

лучше распечатать и выйти, поэтому awk не просматривает остальную часть файла. – ghostdog74

+0

Очень хорошая точка – Jamie

1

Быстрый Perl один лайнер будет хорошо работать для этого тоже ...

$ perl -ne 'if (YOURLINENUMBER..YOURLINENUMBER) {print $_; last;}' /path/to/your/file 
2

Использование q с sed сделать поиск остановку после того, как линия была напечатана.

sed -n '11723{p;q}' filename 

Python (проверка минимальной ошибки):

#!/usr/bin/env python 
import sys 

# by Dennis Williamson - 2010-05-08 
# for http://stackoverflow.com/questions/2794049/getting-one-line-in-a-huge-file-with-bash 

# 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) 

проверки Аргумент должен быть намного лучше, и есть место для многих других улучшений.

Смежные вопросы