Получение одной строки в огромном файле с bash
Как я могу получить определенную строку в текстовом файле 3 гигабайта. Все строки имеют:
- той же длины и
- разделяются символом
\n
.
И мне нужно получить любую строку по требованию.
Как это можно сделать? Необходимо вернуть только одну строку.
Ответы
Ответ 1
Если все строки имеют одинаковую длину, наилучшим способом будет использовать 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
будет искать позицию, которую вы хотите в файле, и читать только одну строка данных из файла.
Ответ 2
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
Ответ 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.
Ответ 4
Альтернатива awk, где 3 - номер строки.
awk 'NR == 3 {print; exit}' file.txt
Ответ 5
Используйте 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)
Подтверждение аргумента должно быть намного лучше, и есть место для многих других улучшений.
Ответ 6
Быстрый perl один вкладыш будет хорошо работать для этого тоже...
$ perl -ne 'if (YOURLINENUMBER..YOURLINENUMBER) {print $_; last;}' /path/to/your/file