База данных — это сердце многих приложений, от полнофункциональных корпоративных сайтов до сравнительно простых инструментов, например, для ведения списков покупок и финансовых трекеров. Популярны реляционные базы данных на основе 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)


  1. mibori
    11.11.2024 20:40

    А ещё есть такая штука -- https://www.nushell.sh/ . Но она не из коробки, конечно.


    1. kt97679
      11.11.2024 20:40

      Nu pipelines use structured data so you can safely select, filter, and sort the same way every time. Stop parsing strings and start solving problems.

      Т.е. мы уходим от парадигмы, что через пайп идет обычный текст? И это значит, что grep использовать нельзя и все существующие скрипты с nushell не совместимы?


      1. mayorovp
        11.11.2024 20:40

        Было бы удивительно, если бы скрипт, написанный под один шелл, был совместим с другим в общем случае (чистый sh - случай частный, и писать под него не так просто как кажется).


    1. webhamster
      11.11.2024 20:40

      Nu pipelines use structured data so you can safely select, filter, and sort the same way every time. Stop parsing strings and start solving problems.

      По сути, первую массовую реализацию этого подхода сделали Microsoft в своем PowerShell. И на самом деле, это правильный и логичный подход, который должен прийти на смену парсинга строк. Пора уже отходить от низкоуровневой шелловской парадигмы "всё - строка" к более структурированной парадигме "всё - объект".


      1. Kingas
        11.11.2024 20:40

        Вот согласен, парсить надо только исходный файл, и дальше работатьс объектами, а не работа со строками на всех этапах.

        И это круто, что PowerShell сделали кроссплатформенным, после того когда выпустили кроссплатформенный .NET. И можно использовать как доп инструмент, и не зависеть от используемого shell.


  1. 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]}



    Либо можно грепать и считывать построчно


  1. alan008
    11.11.2024 20:40

    Не хватает тега "Ненормальное программирование"


    1. Sivchenko_translate Автор
      11.11.2024 20:40

      Вы правы, добавил хаб


    1. Sivchenko_translate Автор
      11.11.2024 20:40

      Вы правы, добавил такой хаб


  1. maxshopen
    11.11.2024 20:40

    Осталось настроить индексы, журналирование и репликацию ;)


    1. Politura
      11.11.2024 20:40

      Транзакции


      1. 2medic
        11.11.2024 20:40

        И обеспечить ссылочную целостность данных.


      1. saboteur_kiev
        11.11.2024 20:40

        Это же все текстовые файлы. Просто весь скрипт положить в гит и сразу появляется даже полноценный коммит, а еще можно настроить удаленный апстрим и будет еще и пуш


    1. sshikov
      11.11.2024 20:40

      Знаете, есть такая штука, как Hive. Там нет индексов, нет транзакций, репликацию обеспечивает Hadoop HDFS. При этом в качестве хранилища данных поддерживается любой разумный формат файлов, какой можно преобразовать в колонки таблицы. Включая и CSV, само собой. Единственное ограничение - что формат этот один на таблицу/партицию. И все это вполне себе работает с терабайтами.

      Конечно, правильнее было бы сказать, что ACID там уже появился, но в тоже время, много-много лет многие пользователи (включая меня) работали с этим как с промышленной СУБД, при некоторых ограничениях, само собой.

      Ну т.е. я к чему - все эти свойства, они в общем случае не обязательны.


  1. boopiz
    11.11.2024 20:40

    формально, набор файлов с данными не является "базой данных", так как по определению для любой элементарной БД нужна "схема" в соответствии с которой эти самые данные будут храниться. без этой самой "схемы" формально данные не являются структурированными в контексте связанного хранения, хоть и могут быть подчинены какой-то структуре внутри себя.

    так что то, что тут описано это просто работа с неструктурированными текстовыми данными для получения какого-либо результата.

    и возникает вопрос - зачем автор хотел натянуть сову на глобус?


    1. Noah1
      11.11.2024 20:40

      Вы знакомы с нереляционными БД?


      1. Dynasaur
        11.11.2024 20:40

        нереляционные БД тоже имеют структуру


        1. mayorovp
          11.11.2024 20:40

          Однако эта самая структура-схема зачастую точно так же нигде не записывается, а лишь подразумевается.


          1. sshikov
            11.11.2024 20:40

            И еще она разная. В пределах одной "таблицы". По сути, это и значит, что ее нет - она есть у каждой записи, но у следующей записи она может быть другая.


  1. wl2776
    11.11.2024 20:40

    А дальше Perl.


  1. eyeDM
    11.11.2024 20:40

    mem/троллейбус-буханка.webp


    1. edo1h
      11.11.2024 20:40

      Нифига себе как мем проапгрейдили, в webp, конечно, он интереснее смотрится


  1. Dynasaur
    11.11.2024 20:40

    чёта я не понял зачем. Чем какой-нибудь SQLite не устраивает? Чего достичь то пытаемся?


    1. AnotherAnkor
      11.11.2024 20:40

      Это просто пост про мотивам "база данных.xlsx".


    1. bear11
      11.11.2024 20:40

      SQLite требует самой библиотеки/программ sqlite. А его может и не быть. Вы можете не иметь возможности устанавливать бинарные программы на компьютере, например, если у вас есть только user shell на машине и /home смонтирован noexec.


      1. Vdm_ro
        11.11.2024 20:40

        У вас нет допуска к запуску/установке программ на комп, но вам обязательно нужна на этом компе самопальная база данных... =))


        1. saboteur_kiev
          11.11.2024 20:40

          ну я с таким встречался.
          не то, чтобы база данных, но если у тебя есть какой-то набор,например 100-200-300 данных, то их уже не очень удобно хранить линейным списком, хочется упорядочить.


  1. astenix
    11.11.2024 20:40

    Базы такого уровня сложности проще вести в бумажном блокноте.


  1. rcl
    11.11.2024 20:40

    Керниган, Пайк


  1. vvzvlad
    11.11.2024 20:40

    Боже, но зачем? Sqlite весит немного, машстабируем, имеет стандартынй синтаксис команд и работает одинаково на любых системах


  1. Dr_Zlo13
    11.11.2024 20:40

    echo "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')


  1. Vilos
    11.11.2024 20:40

    Шутка про cat ...grep и grep уже была?


  1. gorod0k
    11.11.2024 20:40

    Супер, конечно, только это нифига не база данных.

    < Картинка троллейбус_из_хлеба.жпг >