База данных — это сердце многих приложений, от полнофункциональных корпоративных сайтов до сравнительно простых инструментов, например, для ведения списков покупок и финансовых трекеров. Популярны реляционные базы данных на основе SQL, но в Linux можно собрать более простую и прозрачную альтернативную базу данных.
Базу данных какого рода можно собрать в Linux
В Linux доминируют текстовые файлы. В Linux есть экосистема и множество надёжных инструментов, при помощи которых текстовые файлы удобно сцеплять — и очень многого добиться, оперируя такими файлами.
В частности, можно собрать базу данных, то есть структурированный источник информации, который может пригодиться вам для очень многих вещей. Работая с текстовыми файлами и инструментами командной строки, можно создавать простые прототипы, быстро проверять ваши данные, а также версионировать данные столь же легко, как и любой код.
Какими инструментами можно воспользоваться?
В Linux есть много полезных команд, в том числе для работы с текстом. Многие из этих команд действуют как фильтры, получающие данные через стандартный ввод, выполняющие над этими данными некоторые операции и выдающие стандартный вывод.
grep обеспечивает поиск по вводимой информации и позволяет выбрать те строки, которые соответствуют одному или нескольким шаблонам.
cut извлекает избранные фрагменты каждой строки и записывает их в стандартный вывод.
awk — более мощный язык для сканирования и обработки паттернов.
sort выполняет именно сортировку (как и следовало ожидать), но может сортировать данные и по конкретным столбцам, а также корректно справляется с числовой/алфавитной сортировкой.
При помощи команд head и tail можно извлекать из вывода заданный срез строк.
join поддерживает взаимосвязанные данные, расположенные во множестве файлов.
У вас есть инструментарий Linux — как с его помощью создать и использовать базу данных
В данном примере мы создадим простую базу данных для приложения из разряда «список дел». Весь базовый функционал такого приложения можно реализовать при помощи стандартных инструментов Linux. В дальнейшем возможности этого приложения можно будет расширить при помощи скриптового языка, либо перенеся его на реляционную базу данных.
Создание таблиц в виде двумерных файлов
Один из простейших структурированных текстовых форматов называется DSV или значения, разделённые разделителем. Это более общий случай формата CSV —значений, разделённых запятыми. В структурированных текстовых файлах под Linux в качестве разделителя полей часто используются пробел или двоеточие. Классический пример — файл /etc/passwd
:
В таком формате можно хранить разнообразные данные, в том числе и список дел:
Buy milk:2024-10-21:2:open
Call bank:2024-10-20:1:closed
Для обновления базы данных подойдёт любой текстовый редактор — в этом заключается ещё одно достоинство обычного текста. Можно добавлять элементы непосредственно из командной строки, перенаправляя вывод из echo в файл:
echo "Take out the trash:$(date -I):3:open" > tasks
Вот эквивалентный код на SQL:
INSERT INTO tasks VALUES('Take out the trash', CURDATE(), '3', 'open')
Обратите внимание: для получения актуальной даты в этой команде используется подкоманда. Немного неудобно прописывать её руками, значительно удобнее было бы автоматизировать её при помощи скрипта.
Выборка целой таблицы
Выбор данных — пожалуй, самая типичная задача при работе с базой данных. Самый простой вариант — выбрать из таблицы всю информацию, то есть
SELECT * FROM tasks
Такой командой мы извлекаем все столбцы базы данных на высоту всех строк. Если мы имеем дело с файловой базой данных, то эквивалентная команда также тривиальна:
cat tasks
Выбор столбцов при помощи cut
Чуть более искусная операция — сузить выборку до конкретных столбцов. Вот как это делается в SQL:
SELECT task FROM tasks
При помощи инструмента cut можно реализовать практически тот же самый функционал:
cut -d':' -f1 tasks
При помощи опции d
задаём разделитель — тот знак, который будет ставиться между полями в каждой строке вашего файла. При помощи опции f
выбираем конкретные поля. Следующий корд позволяет вывести список всех задач, содержащихся в вашей базе данных:
Выбор строк при помощи grep или awk
Обычно требуется вытащить из базы данных не все строки, а как-то ограничить результаты. В таких случаях наиболее распространено требование отфильтровать содержимое по значениям, как, например, здесь:
SELECT * FROM tasks WHERE status=open
В данном случае grep отлично нам подойдёт. Воспользовавшись этой командой, можно сравнивать строки с шаблоном, заданным в виде регулярного выражения. Таким образом можно найти, например, все задачи, имеющие статус "open" (открыта):
grep 'open$' tasks
В данном случае мы опираемся на тот факт, что каждая строка оканчивается полем «status»; знак $ — это конец строки. Что касается полей в середине строки, для их обработки может потребоваться более сложное регулярное выражение. Например, вот как можно получить все строки с приоритетом 2:
grep ':2:[^:]*$' tasks
Но grep обеспечивает сопоставление только с текстовыми шаблонами и не справляется с более сложными выражениями, например, с такими:
SELECT status, task FROM tasks WHERE date<2024-10-21
В этом коде SQL при помощи логического сравнения мы получаем задачи, заведённые до определённой даты. Можно попытаться собрать более сложное регулярное выражение, но так мы рискуем выйти за рамки возможностей grep.
В данном случае вам потребуется более сложный инструмент, такой как awk:
awk -F':' '$2<"2024-10-21" {print $1 ":" $2 }' tasks
Awk может справиться одновременно с задачами grep и cut. В данном примере часть
$2<"2024-10-21"
— это предусловие, означающее, что шаблону соответствуют лишь значения с более ранней датой. После этого команда выведет на экран первые два столбца из каждой строки.
Постраничное разбиение результатов при помощи head и tail
В языке SQL предусмотрен оператор LIMIT
, при помощи которого можно выбирать конкретное количество элементов из результатов. Вот как выбрать первые две строки:
head -2 tasks
При помощи tail
можно получить n последних строк. В совокупности с head
мы воспроизводим функционал оператора LIMIT
, причём включая сдвиги. Например, вот как получить строки 2-3:
head -3 tasks | tail -2
Сортировка строк при помощи sort
Во многих SQL-командах важная роль отводится оператору ORDER BY
. К счастью, в Linux есть отличная эквивалентная команда sort
. Подобно cut
и awk
, можно указать разделительный знак и поле по номеру, хотя флагам соответствуют разные буквы. На этот раз t
— это разделитель, а k
— номер поля:
sort -t':' -k2 tasks
Далее будут отображены все поля, отсортированные по дате:
Объединение таблиц при помощи join
Суть реляционных баз данных заключается в описании отношений между различными таблицами, где поле из одной ссылается на поле из другой. Возможно, вы раньше не знали, что в Linux есть команда, эквивалентная оператору JOIN
из языка SQL — неудивительно, что называется она join
.
Давайте расширим данные списка дел так, чтобы в нём можно было учитывать задачи для нескольких пользователей. Для начала добавим в исходный файл задач новый столбец name так, чтобы данные приняли следующий вид:
Затем создадим файл people, в котором будем хранить данные по каждой персоне, чьи задачи мы учитываем:
Теперь можно воспользоваться командой join
с разделителем, который указывается через опцию t
:
join -t':' -1 5 -2 1 tasks people
При помощи опций -1 и -2 указываем номера тех полей из каждого файла, которые мы собираемся объединять. Здесь речь идёт о первом и пятом полях соответственно. Команда join
будет использовать первое поле по умолчанию, так что код можно упростить до:
join -t':' -1 5 tasks people
В результате получим:
Чтобы сделать вывод немного чище, можно конвейеризовать объединённые таблицы. В таком случае получится обрезать и опустить поле name
:
join -t':' -1 5 tasks people | cut -d':' -f2-
Кроме того, можно будет объединить два имени в одно при помощи awk
:
join -t':' -1 5 tasks people | awk -F':' '{print $2":"$3":"$4":"$5":"$6" "$7}'
Всё вместе
В заключение давайте рассмотрим гораздо более сложное выражение на языке SQL. Вот выражение, при помощи которого мы объединяем обе таблицы, чтобы получить имена, а также выбрать конкретные столбцы и выбрать строки с определённым приоритетом. Далее выполняется сортировка по дате и выбирается только первая подходящая строка:
SELECT task,date,priority,status,first_name,last_name
FROM tasks t
LEFT JOIN people p ON t.name=p.name
WHERE priority=2
ORDER BY date
LIMIT 1
Эквивалентный конвейер команд, пожалуй, несколько сложнее понять, но и в этом нет ничего трудного, если вы знакомы со следующими ключевыми инструментами:
join -t':' -1 5 -2 1 tasks people \
| awk -F':' '{print $2":"$3":"$4":"$5":"$6" "$7}' \
| grep ':2:' \
| sort -t ':' -k2 \
| head -1
Комментарии (33)
saboteur_kiev
11.11.2024 20:40А еще элегантнее делать это через IFS и read, после чего работать с массивом.
Это уменьшает расходы на запуск внешних команд и позволяет создавать различные внутренние структуры данных.
Если же делать именно базу данных, то проще воспользоваться консольным клиентом к sql базе, которых есть под все, включая sqlite, который умеет возвращать результат в csvИ делаешь что-то вроде
mybase="Buy milk:2024-10-21:2:open:Bobby:Jack: Call bank:2014-10-20:1:closed:Bobby Jack:" IFS=":" read -a myarray<<< "$mybase" echo ${myarray[5]}
Либо можно грепать и считывать построчно
maxshopen
11.11.2024 20:40Осталось настроить индексы, журналирование и репликацию ;)
Politura
11.11.2024 20:40Транзакции
saboteur_kiev
11.11.2024 20:40Это же все текстовые файлы. Просто весь скрипт положить в гит и сразу появляется даже полноценный коммит, а еще можно настроить удаленный апстрим и будет еще и пуш
sshikov
11.11.2024 20:40Знаете, есть такая штука, как Hive. Там нет индексов, нет транзакций, репликацию обеспечивает Hadoop HDFS. При этом в качестве хранилища данных поддерживается любой разумный формат файлов, какой можно преобразовать в колонки таблицы. Включая и CSV, само собой. Единственное ограничение - что формат этот один на таблицу/партицию. И все это вполне себе работает с терабайтами.
Конечно, правильнее было бы сказать, что ACID там уже появился, но в тоже время, много-много лет многие пользователи (включая меня) работали с этим как с промышленной СУБД, при некоторых ограничениях, само собой.
Ну т.е. я к чему - все эти свойства, они в общем случае не обязательны.
boopiz
11.11.2024 20:40формально, набор файлов с данными не является "базой данных", так как по определению для любой элементарной БД нужна "схема" в соответствии с которой эти самые данные будут храниться. без этой самой "схемы" формально данные не являются структурированными в контексте связанного хранения, хоть и могут быть подчинены какой-то структуре внутри себя.
так что то, что тут описано это просто работа с неструктурированными текстовыми данными для получения какого-либо результата.
и возникает вопрос - зачем автор хотел натянуть сову на глобус?
Noah1
11.11.2024 20:40Вы знакомы с нереляционными БД?
Dynasaur
11.11.2024 20:40нереляционные БД тоже имеют структуру
mayorovp
11.11.2024 20:40Однако эта самая структура-схема зачастую точно так же нигде не записывается, а лишь подразумевается.
sshikov
11.11.2024 20:40И еще она разная. В пределах одной "таблицы". По сути, это и значит, что ее нет - она есть у каждой записи, но у следующей записи она может быть другая.
Dynasaur
11.11.2024 20:40чёта я не понял зачем. Чем какой-нибудь SQLite не устраивает? Чего достичь то пытаемся?
bear11
11.11.2024 20:40SQLite требует самой библиотеки/программ sqlite. А его может и не быть. Вы можете не иметь возможности устанавливать бинарные программы на компьютере, например, если у вас есть только user shell на машине и /home смонтирован noexec.
Vdm_ro
11.11.2024 20:40У вас нет допуска к запуску/установке программ на комп, но вам обязательно нужна на этом компе самопальная база данных... =))
saboteur_kiev
11.11.2024 20:40ну я с таким встречался.
не то, чтобы база данных, но если у тебя есть какой-то набор,например 100-200-300 данных, то их уже не очень удобно хранить линейным списком, хочется упорядочить.
vvzvlad
11.11.2024 20:40Боже, но зачем? Sqlite весит немного, машстабируем, имеет стандартынй синтаксис команд и работает одинаково на любых системах
Dr_Zlo13
11.11.2024 20:40echo "Take out the trash:$(date -I):3:open" > tasks
Вот эквивалентный код на SQL:
INSERT INTO tasks VALUES('Take out the trash', CURDATE(), '3', 'open')
Вообще-то эквивалентным кодом на SQL будет:
DELETE FROM tasks; INSERT INTO tasks VALUES('Take out the trash', CURDATE(), '3', 'open')
gorod0k
11.11.2024 20:40Супер, конечно, только это нифига не база данных.
< Картинка троллейбус_из_хлеба.жпг >
mibori
А ещё есть такая штука -- https://www.nushell.sh/ . Но она не из коробки, конечно.
kt97679
Т.е. мы уходим от парадигмы, что через пайп идет обычный текст? И это значит, что grep использовать нельзя и все существующие скрипты с nushell не совместимы?
mayorovp
Было бы удивительно, если бы скрипт, написанный под один шелл, был совместим с другим в общем случае (чистый sh - случай частный, и писать под него не так просто как кажется).
webhamster
По сути, первую массовую реализацию этого подхода сделали Microsoft в своем PowerShell. И на самом деле, это правильный и логичный подход, который должен прийти на смену парсинга строк. Пора уже отходить от низкоуровневой шелловской парадигмы "всё - строка" к более структурированной парадигме "всё - объект".
Kingas
Вот согласен, парсить надо только исходный файл, и дальше работатьс объектами, а не работа со строками на всех этапах.
И это круто, что PowerShell сделали кроссплатформенным, после того когда выпустили кроссплатформенный .NET. И можно использовать как доп инструмент, и не зависеть от используемого shell.