Разработчикам часто приходится иметь дело с файлами, представляющими из себя древовидную структуру: XML, JSON, YAML, всякого рода языки разметки вроде Markdown или Org-mode. Облегчая в общем и целом нашу жизнь, такие файлы имеют склонность к бесконтрольному росту, в какой-то момент из решения превращаясь в проблему.
Стандартное решение этой проблемы — разбиение на меньшие файлы. Это, конечно, работает, но не всегда удобно.
Но существует и альтернатива, о которой — ниже.
org-mode и его разметка.
Пожалуй, стоит сначала изложить мою проблему. Я использую Емакс и — как многие пользователи Емакса — для написания почти всех моих документов, заметок, рабочего дневника и списков задач использую язык разметки org-mode. Выглядит документ в этой разметке примерно следующим образом:
... простой пример файла из репозитория ...
> cat tests/simple.org
document section
* headline 1
headline section 1
** inner headline 1
some inner section 1
some inner section 1-2
** inner headline 2
inner section 2
** inner headline 3
*** inner inner headline 1
* headline 2
section text 2
Один мой документ разросся до нескольких сотен подзаголовков различной глубины вложенности, и по всем этим заголовкам я регулярно прохаживаюсь скриптами в поисках разной информации. Разбирать документ на несколько файлов не хотелось, т.к. синхронизировать между машинами или каким-либо скриптом обрабатывать один файл все же легче. Но и жить так дальше было решительно невозможно.
И тогда мне в голову пришло, что было бы здорово ходить по моему файлу как по директориям, при помощи, скажем, стандартных в Юниксах cd headline1
или cd ..
, ls -l
и cat section
.
Иными словами, мне захотелось уметь представлять дерево заголовков и текстовых секций в виде обыкновенного дерева директорий и файлов. В терминах тех же Юниксов это желание звучит следующим образом: смонтировать некую специализированную файловую систему.
Конечно, писать полноценную файловую систему для Линукса — дело долгое, неблагодарное и уж точно не стоит оно того в такого рода редких случаях.
FUSE
Впрочем, в наши дни уже никто так и не делает, то есть с тех пор как лет десять назад в Линукс был включен модуль FUSE, позволяющий делать файловые системы в виде обыкновенного пользовательского процесса, на который из ядра маршрутизируются все связанные со смонтированной файловой системой /системные вызовы.
С помощью FUSE было написано множество самых разных файловых систем, от игрушечных ФС, монтирующих, например, статьи с Википедии, до вполне серьезных частей современных Линуксов вроде того же Gnome. Таким образом, FUSE стал обязательным элементом популярных дистрибутивов.
Еще приятней работу с FUSE делает тот факт, что в наши дни доступны совсем уж тривиальные в использовании обертки на высокоуровневых языках вроде Python, Ruby, Java и многих других, т.е. собственную файловую систему можно сделать буквально за два-три часа.
fusepy
Конкретно на Питоне оберток вокруг libfuse
(клиентской части FUSE) даже несколько, но больше всего мне понравился проект fusepy: код проекта очень простой и понятный, кроме примеров на Гитхабе и исходного кода мне так ничего и не понадобилось.
Файловая система на базе fusepy
сводится к переопределению методов класса fuse.Operations
, каждый из которых соответствует какому-либо системному вызову.
Для непереопределенных системных вызовов есть либо разумное поведение по умолчанию, либо стандартная ошибка.
Orgfuse
Собственно, конкретный формат файла, который хочется представить в виде дерева директорий и файлов, не так важен. В случае с разметкой org-mode
мне не понравился ни один из доступных парсеров для Питона, и я просто написал собственный. Парсер проходит по указанному файлу, создавая дерево, отражающее структуру документа.
Дерево разбора (parse tree) файла разметки дальше преобразуется в другое дерево, отражающее файлы и директории, которые будет видеть пользователь файловой системы.
Чтобы работать с последним деревом было достаточно реализовать четыре системных вызовов (open
, read
, readdir
, getattr
), каждый из которых занимал буквально несколько строк кода на Питоне:
class FuseOperations(Operations):
def __init__(self, tree):
self.tree = tree
self.fd = 0
def open(self, path, flags):
self.fd += 1
return self.fd
def read(self, path, size, offset, fh):
node = self.tree.find_path(path)
if node is None:
raise FuseOSError(EIO)
return node.content[offset:offset + size]
def readdir(self, path, fh):
node = self.tree.find_path(path)
if node is None:
raise FuseOSError(EROFS)
return ['.', '..'] + [child for child in node.children]
def getattr(self, path, fh=None):
node = self.tree.find_path(path)
if node is None:
raise FuseOSError(ENOENT)
return node.get_attrs()
Итоговый скрипт работает примерно следующим образом:
... монтируем файл как файловую систему ...
> mkdir mount
> python orgfuse.py tests/simple.org mount/
... открываем другой терминал и наслаждаемся ...
> tree mount
mount/
+-- headline 1
¦ +-- inner headline 1
¦ ¦ L-- section
¦ +-- inner headline 2
¦ ¦ L-- section
¦ +-- inner headline 3
¦ ¦ L-- inner inner headline 1
¦ L-- section
+-- headline 2
¦ L-- section
L-- section
6 directories, 5 files
Все это чудо занимает порядка двух сотен строк или 3-4 часа моей ленивой вечерней работы, с моей маленькой задачей справляется замечательно.
Инструкции по установке и код, как водится, можно найти Github.
Если кому интересно преращение прототипа во что-то удобоваримое, с возможностью редактирования файлов и поддержкой большего количества форматов — буду рад пообщаться.
Комментарии (50)
MonkAlex
19.11.2016 20:28-8Читал по диагонали, не понял, какую проблему решали то? Что плохого в древовидном json например?
MonkAlex
20.11.2016 19:36-1Спасибо за минуса, товарищи. Я перечитал статью. Всё равно не понял, ибо человек сначала придумал себе проблемы, потом их героически решил.
KlimovDm
20.11.2016 21:01+1Мне кажется, что вы просто не поняли о чем речь. Поэтому и не можете оценить элегантность решения.
MonkAlex
21.11.2016 10:50Я не понял в чем проблема хранить файлово то, к чему хочется обращаться как к файлам.
VlK
21.11.2016 13:07Может быть, я не достаточно аккуратно изложил проблему.
Есть единый сложный файл, который в принципе кормится одной системе как единый файл, и с этим трудно что-то сделать. С другой стороны, хочется с некоторыми ветками дерева этого файла работать прямо из консоли, простыми скриптами.
Вот и все дела.MonkAlex
21.11.2016 14:13Этот файл вами и создан, как я понимаю. В чём проблема создать N файлов так как вам хочется иерархически?
VlK
21.11.2016 14:45Вы не читаете то, что я уже написал: файл нужен как единое целое в другом месте.
Более того, этот скрипт и делает это самое «создать N файлов иерархически» совершенно автоматически.MonkAlex
21.11.2016 14:57синхронизировать между машинами или каким-либо скриптом обрабатывать один файл все же легче
Но синхронизация как раз таки файловая же везде нынче. Файлы — проще. Синхронизация отдельных файлов — быстрее.
А по скриптам — так вообще не понял, наоборот же, проще каждому скрипту рассказать в какой файл он должен писать, чем объяснить структуру единого файла.
Привязка к одному файлу у вас идеалогическая, имхо, а не техническая.VlK
21.11.2016 16:34А по скриптам — так вообще не понял, наоборот же, проще каждому скрипту рассказать в какой файл он должен писать, чем объяснить структуру единого файла.
Именно про это я и говорю. Писать легче в отдельные файлы. Поэтому большой конфиг превращается в файлы.
Но этот же конфиг мне нужен как один штука, потому что только один штука на входе понимает та система, которая его переваривает. Вот и все дела :-)MonkAlex
21.11.2016 16:49Ааа, просто есть ещё какая то система. Вот про это в шапке что-то не уловил и не понял в итоге зачем. Всё, тогда понятно, спасибо.
Rampages
19.11.2016 21:11+1\<offtop\>
У меня другая проблема – не могу придумать правильную структуру каталогов (классификацию файлов), т.е. есть много файлов, которые можно отнести сразу в несколько папок, городить ссылки из одной папки в другую как-то мягко говоря не правильно.
Такое ощущение, что нужно писать базу данных с ссылками на локальные файлы и задавать им классификацию (категории, метки, темы и т.п.), далее к этому уже интерфейс какой-то… Каталогизатор какой-то получается.
\</offtop\>Evengard
19.11.2016 21:25+2Вам тегировать файлы нужно. Посмотрите в сторону чего нить вроде https://www.tagsistant.net/
roach1967
20.11.2016 01:07Есть-же такая штука — жёсткая ссылка.
Вот только как реализовать.:( С питоном я никак от слова совсем…
Am0ralist
20.11.2016 01:30+2Такая же проблема.
Уже пару лет думаю над тем, чтобы сесть и попробовать написать свой файловый менеджер:
— иерархические теги, возможность задать файлу неограниченное количество тегов
— поиск по тегам с помощью диаграмм Венна (для наглядности)
— отслеживанием изменений фс в заданных папках (добавление и удаление файлов, переименование старых, перемещение в отслеживаемые папки — папок с файлами)
— всплывающее окно с предложением назначить теги новым файлам в отслеживаемых папках
— отслеживание полных дубликатов
— и, видимо, хранить всю эту инфу в бд какой-нибудь.
Просто когда количество схем оригами начинает переваливать за десяток гигабайт начинаются проблемы с поиском какой-нибудь там схемы хамелеона Итагаки Ючи, которая была в каком-то из 150 номеров Tanteidan Magazine. Причем каждый сборник — это пдфка целая, в которой десяток разных схем, так что в имя нельзя все занести…
Sirikid
20.11.2016 15:24Вы не один, лично знаю ещё 3-ех человек которые задавались подобным вопросом. Предлагаемые варианты решения:
1. жесткие ссылки (пробовал, имхо не очень удобно, запутывался в количестве ссылок на файл)
2. дерево катологов (пробовал, нереально создать такое для всех своих файлов)
3. файловый менеджер с БД с информацией о файлах (такой создает Konachan700, но только для картинок afaik)
4. метаинформация
— в файле по типу rarjpeg
— в расширенных атрибутах
— в специализированной фс
Вообще было бы неплохо собраться в кучку и сочинить что-нибудь, цели очень похожие.
cc: Am0ralistSirikid
20.11.2016 16:25Немного перефразирую, хранить метаинформацию хочется в любом случае, можно делать это в
— файле
— пути (папки = теги + жесткие ссылки)
— БД файлового менеджера
— расширенных атрибутах фсAm0ralist
20.11.2016 18:061) Если хранить в доп.файле для каждого из тегируемых файлов, то копировать надо все их, что проблематично, когда ты хочешь поделиться одним файлом из папки.
А для каждого поиска вначале найти все доп.файлы, а потом уже анализировать из содержимое.
Мне кажется, больно много накладных расходов, если только не кешировать это все еще куда и не копировать опять же спец.инструментом, а тогда опять же получается БД + файловый менеджер.
2) в пути и жестких ссылках хранить большое количество тегов (особенно иерархических, особенно, когда тег может быть включен в разные иерархические ветки) + легко это взять и скопировать друзьям?
3) в этом случае копировать можно кусок базы и делать это средствами самого файлового менеджера (то есть копировать файлы через него), при этом еще выбрать какие теги копировать, а какие нет. Причем привязку делать к имени и хешу файла, например, для того, чтоб на другом компьютере такой же файловый менеджер позволил эту информацию легко подцепить, даже если изменились пути, имя без содержимого или содержимое без имени.
4) Было бы идеально, но желательно тогда, чтоб все популярные ФС это поддерживали, для легкого копирования между компьютерами…
Возможно где-то ошибаюсь? Просто в свое время потратил много времени на анализ хотелок и возможностей разных программ, а в итоге найденная программа, в которой работала хотя бы часть желалок — работала только под win32 из-за того, что служба не была скомпилирована под x64, а кросплатформерных приложений вообще не нашел.
Особенно порадовала прога с а-ля облаком тегов, в котором облако не масштабировалось и не прокручивалось, в итоге после 30 тегов программу просто выкинул.Sirikid
21.11.2016 06:411) Не в доп. файле, а в том же самом.
4) Мне кажется это оно: https://en.wikipedia.org/wiki/Extended_file_attributesAm0ralist
21.11.2016 09:251) Если в том же самом, то есть изменяем файл — то не все форматы поддерживают метаданные. Как поведут себя файлы произвольного формата — мне сложно сказать. Плюс проблема дубликатов возникает, когда у тебя хранится уже измененный файл, а ты добавляешь исходный с другим названием, например.
4) В windows для этого используют по сути альтернативные файловые потоки особо извращенным способом. Проблема в том, что эти расширенные атрибуты молча не копируются на тот же Fat32 (привет флешкам и SD-картам), а вот альтернативные потоки хотя бы ошибочку выдадут. Плюс доступ к ним через Жо… веселое API.
При этом сами по себе эти альтернативные потоки позволяют к 1 кб файлику прицепить хоть 100 гб, которые напрямую не увидишь — удобно, что бы прятать (порно) любой файлик от любопытных взглядов. Ну, то есть я знаю одну программу, которая точно позволяла легко смотреть файловые потоки, да.
С учетом, что я это планировал делать для удобного обмена файлами между людьми, которые слабо понимают в IT (самому перелопатить все да потом поддерживать — нереально)…av0000
22.11.2016 22:14С учетом, что я это планировал делать для удобного обмена файлами между людьми, которые слабо понимают в IT (самому перелопатить все да потом поддерживать — нереально)…
Рискну «влезть» — из серии «хорошо забылое старое».
Были на заре такие файловые менеджеры — Volkov Commander и DOS Navigator. Кто у кого тогда «спёр» идею, сейчас уже не угадаешь, но умели они, как и FIDO-шный софт править файлы-компаньоны (как минимум — files.bbs и descript.ion) — в частности при копировании с места на место.
По сути — та же БД со спец. инструментом, но вполне доступным, встроенным в файловый менеджер (ну, да, какое-то расширение для GUI-евых «проводнико» напрашивается) и годным для людей, далеких от IT.
Некоторые мои знакомые архивариусы (из совсем не молодых ещё по тем временам) с огромным удовольствием пользовались такой возможностью и этого им вполне хватало (в купе с ручной правкой/написанием содержимого files.bbs — ну не было тогда готовых отдельных редакторов)Am0ralist
22.11.2016 22:58А почему «рискну»?
Тут любые варианты интересны в плане того, что даже посмотреть кто как делал.
Да и вдруг всплывет что-нибудь уже существующее а-ля tag2find, который под 64 не работает к сожалению, но хотя бы часть возможностей требуемых поддерживал.
Ибо пилить свой велосипед не сильно владея программированием и так весело, а если еще и изначально не верный вариант выбрать, то вообще обидно будет.av0000
23.11.2016 08:45А почему «рискну»?
Тут любые варианты интересны
Это, скорее, хабра-призказка ;)
Для себя искал и порывался написать «тегировалку» фото коллекции — после ухода с винды, где этого добра валом, но как-то «отпустило» или стало лень
А на предмет вариантов — из той же серии: берём графические редакторы или даже более-менее старые фотоаппараты с поддержкой RAW — там к каждому файлу, который «неизменен», прикладывается мелкий (.thm, .xmp и т.п.) со всякими разностями — от режима «проявки» RAW, до истории изменений и тегов.
Из готового — тот же shotwell под линукс — умеет коллекции тегов с сортировкой/выборкой. По отдельной команде записывает теги в файлы, если поддержка, как у jpeg. Далее такой файл переносится на другой комп, а там теги вычитываются и кладутся в локальную базу. Вроде бы оно даже умело интегрироваться с «родными» проводниками для Gnome/KDE a'la дополнительные атрибуты файлов.
Z-r
20.11.2016 16:02есть много файлов, которые можно отнести сразу в несколько папок
Значит пора отказываться от концепции «папок» в пользу концепции «меток».
городить ссылки из одной папки в другую как-то мягко говоря не правильно
Правильно. Все каталоги у нас теперь не «папки», а «метки», и все ссылки на один и тот же документ должны быть равноправны, то есть есть это должны быть жесткие ссылки, а не символьные.
Единственная проблема — ext[234], в отличие от, например, NTFS, не хранит обратных ссылок с инодов на файлы, а поэтому, если стороннего индекса нет, то для удаления документа, если такое вдруг понадобится, придется делать полный перебор:
$ find ~/ -xdev -samefile useless-file.org -delete
Для резервирования такой ФС,
rsync(1)
’у надо будет додать ключ-H
.
В остальном — никаких особенностей.
Am0ralist
20.11.2016 17:48а копировать такое друзьям как?
Ну вот хочу часть своей базы схем поделиться с кем-то, вместе со всеми «тегами» конкретных файлов, кроме личных оценок (оценок сложности схемы и того, нравится ли мне модель, например).
Как это будет происходить в таком случае?Z-r
20.11.2016 18:08хочу часть[ю] своей базы схем поделиться с кем-то
Если часть — это такие-то метки со всем, что под ними лежит, то просто — тем же
rsync -aH
.
вместе со всеми «тегами» конкретных файлов
А вот есть так, то есть если часть — это отдельные документы, размазанные по всему дереву меток, то опять же — только полным перебором. Увы, не хранит ext обратные ссылки.
Мне это ни разу не было не нужно, так что костыля я не написал, но понятно, что он элементарный.
Z-r
20.11.2016 19:46+1Мне это ни разу не было не нужно, так что костыля я не написал, но понятно, что он элементарный.
А вообще, что уж там, давайте напишем:
#!/bin/bash # config TREE_ROOT="$HOME/origami" SCRIPTNAME='amoralist-cp' USAGE=$"Usage: $SCRIPTNAME <source>... <dest>" (($# >= 2)) || { printf >&2 '%s\n' "$USAGE"; exit 0; } dest="${!#}" for ((i = 1; i <= $# - 1; i++)); do if [[ -d ${!i} ]]; then printf >&2 '%s\n' $"${!i} is directory; ignored" else find_argv+=('-samefile' "${!i}" '-or') fi done unset find_argv[-1] find "$TREE_ROOT" -xdev "${find_argv[@]}" -printf '%P\n' | rsync --archive --hard-links --files-from - "$TREE_ROOT" "$dest"
Sirikid
23.11.2016 09:01А есть файловые системы которые хранят обрытные ссылки?
Z-r
24.11.2016 14:18Ну, одну я уже упомянул — NTFS. :-)
Есть ли *стандартные* файловые системы, что хранят обратные ссылки с инодов на файлы, вы хотите спросить? А вот не знаю — интересовался этим весьма поверхностно.Sirikid
24.11.2016 14:35меняем *стандартные* на *можно адекватно использовать с Linux или FreeBSD* и получится то что я хотел спросить :^)
ser-mk
20.11.2016 01:07отличное решение!
до этого я только предполагал такую реализацию через плагин VFS для Midnight Commander. Там мне показалось не так удобно как с fusepy))
Str3lok
20.11.2016 01:07+1Парсинг все равно происходит и все запросы парсера файловой системы пройдут через ядро. Гораздо быстрее работать с единым файлом напрямую. Но как задача для знакомства с fusepy — годится.
VlK
20.11.2016 01:13Я вроде не написал, что данное решение мне срочно надо использовать для обработки миллионов запросов в секунду…
Для скорости я бы писал не на Питоне, и обрабатывал системные вызовы бы не в один поток, и т.д. и т.п. В конце концов можно написать модуль файловой системы для ядра.
И если вам действительно надо условный миллион раз за условную же наносекунду читать конфиг, то, быть может, вы что-то делалете не то..?Str3lok
20.11.2016 10:20-2Конечно, я часто делаю что-то не так. А кто не ошибается? Но работа приучила меня проектировать так, чтобы задача решалась с наименьшим выделением тепла. Это нравится экологам и в итоге не приносит неприятных сюрпризов с ростом обрабатываемой информации.
Вы же понимаете, что на один шаг парсера файловой системы будет 4(!) переключения из юзерспейса в ядро и обратно?
Здесь гораздо интереснее будет инвертировать задачу: раскидать много малых файлов по реальной файловой системе и предоставить один виртуальный для КЭШированного доступа.
В итоге и применять можно оба подхода и системные вызовы экономятся.VlK
20.11.2016 16:12+1Ну да, отчасти вы, конечно, правы. С другой стороны, у меня было несколько часов на все про все, и тут уж не до спасения планеты. :-)
Ваша идея тоже звучит интересно, признаться, но это ваша идея и ваш интерес, не могу ж я их взять и украсть :-)
Rigidus
20.11.2016 01:30Редактирование определенно необходимо. Я бы даже очень был рад полному интерфейсу
bitterman
20.11.2016 14:27+1Похожую тему рассказывали про ОС Plan9. Её постоянным пользователем является (как оказалось) некий ВУЗ Барселоны, занимающийся переводами текстов. Им очень нравится, что в основе ОС многоязыковость и при этом очень легко создавать свои специализированные файловые системы. Вот они дожились до того, чтобы переводить тексты при помощи таких файловых систем — документ делится на разделы, разделы переводят разные люди — для ускорения. И вот здесь фишки Plan9 им оказываются очень впору. Если интересно, можно раскопать, как у них это происходило — у автора статьи очень похожий — и правильный! — взгляд на схожую проблему. Круто решать такие задачи :-)
delvin-fil
20.11.2016 16:03А чем ZIM не подошел(не имею ввиду портирования файлов в него)?
Глянув на его код можно было и немного переиначить, а потом «натравить» на свои.VlK
20.11.2016 16:05ZIM, который формат файла? А при чем здесь вообще конкретный формат файла?
Статья ведь про то, что файлы с иерархией можно легко монтировать как файловую систему, и тогда для работы с содержимым можно пользоваться любыми привычными инструментами.delvin-fil
21.11.2016 08:29ZIM — программа, домашнее wiki. На питоне.
файлы с иерархией можно легко монтировать как файловую систему
Я про это и сказал, что вместо того, чтобы разрабатывать с нуля, можно было посмотреть, как программа делает из одного файла древовидную структуру. А потом, на том же питоне(zim на питоне), написать код под персональные нужды.
Ссылка: https://ru.wikipedia.org/wiki/ZimVlK
21.11.2016 10:53У меня нет проблемы сделать из файла древовидную структуру :-) Такие вещи называются парсерами, и с этим уже давно ни у кого нет проблем.
У меня проблема — представление одной древовидной структуры в виде другой, файловой.delvin-fil
21.11.2016 11:04Эммм… А я разве о другом? Там, в проге есть разделение(типа офисовского «совместного использования файла»(зависит от версии)). Как прога предоставляет ее по сети? Разве не файловой системой?
Тут, мне кажется, спор излишен, ибо я говорил о «посмотреть код программы», а вы так и становились на файловой системе.VlK
21.11.2016 16:44Т.е. вы предлагаете адаптировать программу, визуально отображающую файлы с вики-подобной разметкой в виде дерева..?
Я, вероятно, не совсем вас сразу понял.delvin-fil
22.11.2016 06:59Не совсем. Просто посмотреть код и взять нужное для себя.
VlK
22.11.2016 13:33А что вообще мне там может быть нужно, в этом коде? Т.е., зачем мне разбираться в софтине на тысячи строк кода, когда все решение занимает меньше двух сотен строк?
Опять же, какое отношение софт для просмотра файлов с вики-подобным синтаксисом имеет к скриптам, работающим с файловой системой?
KlimovDm
Интересное решение, очень. Мне совсем недавно нужно было нечто похожее (как сейчас стало понятно), но не додумался… Спасибо, надо поэкспериментировать…