«Многопроходная» скриптовая модификация большого файла на месте (на уровне файловой системы)?

«Многопроходная» скриптовая модификация большого файла на месте (на уровне файловой системы)?

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

... и далее также эти:

Однако я задумался о другом: я полагаю (но не уверен), что любая файловая система (например, 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 L2\n L3\n L4\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 L2\n L3\n L4\n L 5\n L 6\n

0 1 2 3 ------------------------------>| | 19 20 21 22 23

. . ...\n r1  6  :18  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. Я думаю, он был разработан для таких задач. Построчная обработка, повторная обработка одной и той же группы команд и регулярные выражения — все это объединено в одном инструменте. Хотя я знаю, что он справится с работой, я не могу дать вам дальнейших указаний, кроме как направить вас к их прекрасномустраница руководства.

Связанный контент