Я только что столкнулся с проблемой вырезания нескольких строк из большого (гигабайтного) файла и, зная о потенциальной загрузке процессора при попытке прочитать его в памяти, я хотел вместо этого отредактировать его на месте... и поэтому у меня возникли следующие вопросы:
- Как удалить определенные строки (используя номера строк) в файле?
- Есть ли способ изменить файл на месте?
... и далее также эти:
Однако я задумался о другом: я полагаю (но не уверен), что любая файловая система (например, ext3
) должна использовать что-то вроде связанного списка, чтобы иметь возможность описывать что-то вроде фрагментов файла, сопоставленных с областями диска.
Таким образом, должно быть возможно сделать что-то вроде этого — например, у меня есть bigfile.dat
такой файл (цифры должны указывать смещение в байтах, но выровнять их немного сложно):
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
L 1\n L 2\n L 3\n L 4\n L 5\n L 6\n
Затем этот файл, в принципе, можно загрузить в терминальное приложение для просмотра — представим, что мы вызываем инструмент editsegments bigfile.dat
и скажем, что он выглядит примерно так же, как less -N bigfile.dat
отображался бы тот же файл (с номерами строк):
1 1 L 1
2 2 L 2 *
3 3 L 3
4 4 L 4 *
5 5 L 5
6 6 L 6
bigfile.dat (END)
Допустим, я мог бы ввести там команду (например, d
для удаления строк), щелкнуть другую клавишу или мышью там, где указано выше, *
— это означает, что все между строками 2 и 4 должно быть удалено. Затем программа ответила бы следующим образом:
1 1 L 1
2 5 L 5
3 6 L 6
bigfile.dat (END)
Теперь мы видим, что в самом левом первом столбце отображается «новый» номер строки (после вырезания), во втором столбце — «старый» номер строки (до вырезания), а затем следует фактическое содержимое строки.
Теперь, как я себе представляю, что происходит после editsegments
выхода из этого псевдоприложения, так это то, что, во-первых, bigfile.dat
оно остается нетронутым; однако теперь в том же каталоге будет также дополнительный текстовый файл, скажем bigfile.dat.segments
, с таким содержимым:
d 4:15 # line 2-4
bigfile.dat.iedit
... и, кроме того, появится специальный файл (назовем его «символической ссылкой») .
Итак, по сути, результатом всего этого будет то, что если я сейчас попытаюсь открыть bigfile.dat.iedit
что-то вроде less -N bigfile.dat.iedit
, я захочу получить «отредактированное» содержимое:
1 L 1
2 L 5
3 L 6
bigfile.dat (END)
... чего, я полагаю, можно было бы добиться, каким-то образом указав операционной системе, что при $FILE.iedit
открытии сначала $FILE.segments
следует открыть и прочитать файл; это d 4:15
дало бы указание, что байты с 4 по 15 в исходном файле следует опустить, что привело бы к чему-то вроде:
0 1 2 3 4 5 6 7 8 9 10 11 12,3,4 15 16 17 18 19 20 21 22 23
L 1\n
L
2
\n
L
3
\n
L
4
\n
L 5\n
L 6\n
0 1 2 3 ------------------------------->16 17 18 19 20 21 22 23
Другими словами -предполагаячто в концепции файла файловой системы каждый байт содержимого также содержит «ссылку» на следующий байт в цепочке — должна быть возможность дать указание файловой системе создать новый связанный список на основе скрипта и предоставить содержимое, представленное этим измененным связанным списком, через специальный файл (символическую ссылку или канал).
Вот что я имел в виду под «скриптованным» в названии — то, что «новый» связанный список может контролироваться файлом скрипта ( $FILE.segments
), редактируемым пользователем в текстовом редакторе (или генерируемым приложением front-end). Под «многопроходным» я имел в виду тот факт, что bigfile.dat
в этом процессе он вообще не изменяется; так что я мог бы отредактировать первый (исходный) гигабайт сегодня, сохранив прогресс в ( $FILE.segments
) — затем я мог бы отредактировать второй гигабайт завтра, снова сохранив прогресс в ( $FILE.segments
) и т. д. — при этом оригинал bigfile.dat
остается неизменным.
Когда все изменения будут завершены, можно, вероятно, вызвать своего рода команду (например, editsegments --finalize bigfile.dat
), которая просто навсегда закодирует новый связанный список как содержимое bigfile.dat
(и в соответствии с этим удалит bigfile.dat.segments
и bigfile.dat.iedit
). Или, что еще проще, можно просто сделать:
cp bigfile.dat.iedit /path/to/somewhere/else/bigfile.modified.dat
Конечно, помимо d
команды elete script, можно r
также использовать команду eplace, например:
r 16:18 AAA
... говоря: замените содержимое между байтами 16 и 18 следующими 18-16+1=3 байтами после пробела (то есть AAA
) - связанный список на самом деле может "прицепиться" к содержимому самой команды скрипта (таблица ниже также содержит elete d
):
0 1 2 3 4 5 6 7 8 9 10 11 12,3,4 15 16 17 18 19 20 21 22 23
L 1\n
L
2
\n
L
3
\n
L
4
\n
L
5
\n
L 6\n
0 1 2 3 ------------------------------>| | 19 20 21 22 23
.
.
.
.
.
.
.
\n
r
1
6
:
1
8
AAA
\n
.
.
.
.
Теперь, я думаю, что такие программы, как hexedit
(как уже упоминалосьздесь) действительно изменяют файлы на месте, но мне бы хотелось воспользоваться преимуществом возможности создания сценариев (еще лучше, если бы это можно было регулировать с помощью приложения с графическим интерфейсом, пусть даже и терминального), а также преимуществом отсутствия необходимости в фактическом изменении исходного файла до тех пор, пока не будет подтверждено, что все изменения внесены так, как требуется.
Я не уверен, возможно ли что-то подобное вообще, и даже если это так, полагаю, для этого может потребоваться специальный драйвер (а не просто пользовательская программа)... Но, думаю, в любом случае стоит спросить — есть ли что-то подобное для Linux?
Заранее большое спасибо за любые ответы.
Удачи!
решение1
Структура файлов на диске зависит от используемой файловой системы. Ни одна из реальных файловых систем не использует связанные списки, как вы описываете (это было бы fseek(3)
невыносимо). Ближайшее к этому — MicrosoftТОЛСТЫЙ, по сути перемещая указатели из блоков данных в массив, скрывающий их.
Но большинство файловых систем используют некоторые ссылки на основе указателей на блоки данных в файле, поэтому в принципе можно вырезать блок файла, просто перетасовав горсть указателей (не все содержимое файла) и пометив блок в середине файла как свободный. К сожалению, это не очень полезная операция, блоки файла довольно большие (обычно 4 КБ) и редко будут разумно выравниваться со структурами в файле (будь то строки или другие подразделения).
решение2
То, что вы описываете, очень похоже наповторитьтекстового редакторасписок повторовпротив неизмененного исходного файла, к которому этосписок повторовпринадлежит. Я уверен, что gvim
у него есть такойнастойчивыйсписок отмены/повтора, который вы можете(?) использовать, и я знаю, что emacs
такой список определенно есть, и вы, скорее всего, могли бы заставить его делать все, что захотите (с помощью скрипта elisp
), например.Сохранение истории отмен Emacs между сеансами.
Кстати, отключение всех нежелательных действий может быть хорошей идеей для таких больших файлов, например:автосохранение,подсветка синтаксиса(медленно набольшойфайл emacs) и т. д., а emacs на 32-битной системе имеет размер 256 МБограничение размера файла.
Конечно, это не будет столь лаконично, как то, что вы предложили, но может быть полезно, если не будет большого количества изменений.
решение3
Обычно вы не можете редактировать файл на месте, не перенеся весь файл в память. Я предполагаю, что на самом деле вы просто хотите иметь новый файл, который является копией старого без определенных строк. Это можно легко сделать с помощью утилит unix head
и tail
. Например, чтобы скопировать все, кроме строк 5, 12 и 52 из файла, вы можете сделать
head -n 4 bigfile.dat > tempfile.dat
tail -n +6 bigfile.dat | head -n 6 >> tempfile.dat
tail -n +13 bigfile.dat | head -n 39 >> tempfile.dat
tail -n 53 bigfile.dat >> tempfile.dat
Если вы не знакомы с этими утилитами, я объясню их более подробно.
Утилита head
выводит первые n строк из файла. Если позиционный аргумент не указан, она будет использовать стандартный ввод в качестве файла. Флаг -n
сообщает head, сколько строк выводить. Таким образом, head -n 2
выведет только первые 2 строки из стандартного ввода.
Утилита tail
выводит последние n строк файла. Как и head, она может читать из файла или стандартного ввода. Флаг -n сообщает tail, сколько строк вывести с конца. Вы также можете добавить к числу префикс в виде знака плюс, чтобы указать tail вывести строки с конца файла, начиная с указанного количества строк с начала. Например, tail -n 2
выводит последние две строки из стандартного ввода. Однако tail -n +2
выводит все строки, начиная со строки номер 2 (пропускает строку 1).
Итак, в общем случае, если вы хотите вывести строки в диапазоне [x, y) из файла, вы должны сделать следующее:
`tail -n +x | head -n d`
где d = y - x. Эти команды создадут новый файл. Затем вы можете удалить старый файл, если захотите. Преимущество такого подхода в том, что в любой момент времени нужно хранить в памяти head
только tail
одну строку, поэтому она не будет быстро заполнять вашу оперативную память.
решение4
Похоже на работу для скрипта sed. Я думаю, он был разработан для таких задач. Построчная обработка, повторная обработка одной и той же группы команд и регулярные выражения — все это объединено в одном инструменте. Хотя я знаю, что он справится с работой, я не могу дать вам дальнейших указаний, кроме как направить вас к их прекрасномустраница руководства.