CS Weekly 3: Моя музыка Postgres
Это мой третий еженедельный проект по CS. Прочтите Code Something Weekly: How and Why, чтобы получить вдохновение и идеи, лежащие в основе усилий:
Проект прошлой недели исследовал визуализатор волны с API веб-аудио:
Как диджей, я чувствовал необходимость управлять своей музыкой в локальной базе данных. Конечно, на разных платформах есть такие списки, как избранное и списки пожеланий. Но ими быстро становится трудно управлять в масштабе.
В качестве примера посмотрите мой текущий список пожеланий bandcamp
который на момент написания этой статьи насчитывает колоссальные 1037 предметов.
тссс, у меня скоро день рождения, и я не буду возражать против анонимного "подарка" из этого списка
Проблема в том, что я использую этот список для:
- материал, который я еще не слушал, но должен
- релизы, которые можно загрузить бесплатно, но я обнаружил их на другом устройстве
- товары, которые я еще не купил, но хочу
- предметы, о которых я сомневаюсь
- предметы других жанров, которые я не обязательно буду покупать
Кроме того, практически невозможно упорядочить и разобрать — и это просто Bandcamp. Не говоря уже о музыке, найденной на Deejay.de и других ресурсах.
Введите идею и вдохновение для простого инструмента: My Music Postgres, который доступен на Github (скачать zip здесь).
Управление музыкой может быть весьма обширным (просто посмотрите на все функции iTunes). Поэтому я выделил небольшой набор полезных связанных функций, предназначенных для управления покупками и прослушиванием DJ:
Этот простой инструмент командной строки делает следующее:
- Добавить исполнителей, звукозаписывающие компании и релизы в базу данных
- Список выпусков, которые вам понравились, но которых у вас еще нет
- Запускает релизы, которые вы еще не слушали
- Обновление релизов (прослушано, понравилось, куплено)
В оставшейся части этой статьи мы обсудим сценарии bash и продемонстрируем, как взаимодействовать с базой данных PostgreSQL.
Предполагается базовое знакомство с SQL. Для тех, кто совсем не знаком с SQL, существуют различные учебные пособия для изучения основ:
- https://www.tutorialspoint.com/postgresql/
- http://www.postgresqltutorial.com/
- https://pgexercises.com/ ← это интерактивный ресурс!
Схема таблицы
Дизайн намеренно минимален. У нас есть 3 таблицы:
- Художники
- Этикетки
- Рекорды
Можно было бы добавить МНОГИЕ другие, особенно для тщательного применения. Некоторые примеры включают теги, RecordArtists (для многих ко многим) и жанры. Но это простой музыкальный менеджер, построенный вокруг основной потребности в управлении прослушиваниями и покупками.
Хотя это еще не реализовано (будущая версия), наличие ссылок Artist и Label позволяет нам создать простую опцию для их открытия из командной строки следующим образом:
$ my-music --open artist=ansome >> start chrome https://soundcloud.com/myansome $ my-music --open label=mord >> start chrome https://mord.bandcamp.com/
Инструменты
Если вы раньше не использовали PostgreSQL или не знакомы с некоторыми инструментами, настройка будет довольно простой.
Скачать PostgreSQL можно здесь. Убедитесь, что вы включили клиентов, особенно PgAdmin. Если вы не скачивали PgAdmin, его можно скачать отдельно здесь. PgAdmin — очень мощный графический интерфейс для взаимодействия с базами данных, как показано ниже. Учитывая минималистичный дизайн и наличие ошибок в моей крошечной программе-оболочке, PgAdmin является запасным вариантом для внесения изменений в вашу базу данных. Просто было бы мучительно использовать PgAdmin каждый раз, когда вы хотите сделать что-то простое, например прослушать запись в своем списке прослушать.
Из терминала bash (если вы работаете в Windows, как и я, вы можете использовать git bash среди других опций [1][2]), вы можете легко проверить, правильно ли установлен postgres, запустив:
$ createdb --version
Затем убедитесь, что psql
был установлен и добавлен к вашему пути:
$ psql --version
Написание Bash-скриптов
Хорошо, теперь, когда у нас есть инструменты, давайте обсудим сценарии bash. Я добавил несколько тем, связанных с Bash, которые я считаю полезными. Если вы полностью знакомы с Bash, перейдите к следующему основному разделу PostgreSQL в Bash.
Краткое напоминание
- без пробелов вокруг «=»
myVar=3
- получить значение переменной с помощью «$»
$myVar
- вычислить операции, используя ``
files=`ls`
или $(( ))$(( myVar + 1 ))
- Если формат блока:
if..then..else..fi
- Пока формат блока:
while..do..done
- Формат блока дел:
case a);; b);; esac
- Условия внутри [ ], помните о пробелах:
[ $myVar != 3 ];
Анатомия интерактивного сценария Bash
Bash-скрипты являются процедурными, что означает, что если вы писали приложения в декларативной и/или функциональной парадигме в течение нескольких месяцев, все может показаться немного шатким.
Процедурное программирование сосредоточено вокруг набора процедур (также называемых подпрограммами, функциями), которые по сути являются вычислительными шагами. Сравните это с ключевым словом function
в JavaScript, которые являются объектами первого класса (т.е. имеют замыкание и свойства). Напротив, процедуры изменяют состояние программы (это означает, что функции имеют побочные эффекты в глобальной области видимости).
Для того, чтобы сохранить наш скрипт в сопровождении, мы должны позаботиться о базовом наборе процедур и структуре самой программы.
Я разбил следующую архитектуру:
# Variables user=postgres dbname=MyMusic password=postgres # PROCEDURES addArtist () {} addLabel () {} addRecord () {} listLiked () {} listenToRecord () {} purchasedRecord () {} createDatabase () {} deleteDatabase () {} mainLoop() {} myMusicUsage () {} # MAIN PROGRAM case $1 in ... esac exit 0
Программа читается сверху вниз, что означает, что к тому времени, когда мы доберемся до основной программы, переменные и ссылки на процедуры уже установлены, и теперь мы просто вызываем процедуры в ответ на ввод.
Чтобы продемонстрировать, чем это отличается от скомпилированных языков и языков с подъемом, рассмотрим следующий скрипт:
#!/bin/bash printHello printHello () { echo "hello" }
что дает хороший:
line 3: printHello: command not found
Другими словами, объявление процедуры необходимо проанализировать, прежде чем его можно будет вызвать.
Основной цикл
Основной цикл имеет простую цель: проанализировать параметры командной строки и вызвать соответствующие процедуры.
На аргументы командной строки ссылаются с помощью $1 $2 $3...
и т. д. Для обработки нескольких аргументов можно использовать цикл, в котором каждый аргумент shift
преобразуется в $1
и обрабатывается один за другим. К счастью, наша программа не принимает несколько аргументов, и это сделано для простоты.
Давайте посмотрим на варианты (my-music --help
):
-c, --create - creates database -d, --delete - deletes database. dbname [default=MyMusic] - Name of database user [default=postgres] - Name of db user -h, --help - displays command line options -i, --interactive - interactively add artists...
Чтобы настроить цикл для анализа этих параметров, мы можем использовать простой оператор case
:
case $1 in -i | --interactive) mainLoop ;; -c | --create) createDatabase $2 $3 ;; -d | --delete) deleteDatabase $2 $3 ;; -h | --help) myMusicUsage exit 0 ;; *) myMusicUsage exit 1 ;; esac
Синтаксис немного уродлив, но он не слишком отличается от других языков. Вот перевод JavaScript:
switch (arguments[0]) { case "-i": case "--interactive": mainLoop() case "-c": case "--create": createDatabase(arguments[1], arguments[2]) case "-d": case "--delete": deleteDatabase(arguments[1], arguments[2]) case "-h": case "--help": return myMusicUsage() default: return myMusicUsage() }
Интерактивный цикл
В предыдущих разделах мы видели, что существует процедура с именем mainLoop
(оглядываясь назад, я должен был назвать ее interactiveLoop
). Давайте быстро рассмотрим, как настроить интерактивный цикл, так как это основная функция многих скриптов bash.
Идея состоит в том, чтобы взять то, что мы узнали в предыдущем разделе, и сделать это в цикле while
:
while [ true ]; do echo -n "a to add artist (q|quit to quit) " read action case $action in a ) addArtist ;; q | quit) exit 0 ;; esac done
Команда read
приостанавливает выполнение, ожидая ввода данных пользователем (заканчивается клавишей [enter]
). Как только пользователь введет «q» или «quit», а затем [enter]
, программа будет успешно завершена через exit 0
. Точно так же, чтобы вернуться к предыдущему вызову стека, вы можете использовать return
вместо exit
.
В качестве примера следующая программа:
#!/bin/bash read nothing exit
завершится только после того, как вы нажмете [enter]
или завершите процесс другим способом. nothing
будет содержать значение того, что набрал пользователь, если что.
Процедуры
Есть два одинаковых способа написания процедур в bash, но оба они могут стать «подводным камнем» для программистов, привыкших к другим языкам.
Используя ключевое слово функции:
function function_name { # commands }
Используя пустые круглые скобки:
function_name () { # commands }
Вы вызываете процедуры по имени: function_name
(без скобок).
С аргументами все сложнее, и это очень похоже на то, что происходит с языками ассемблера: вы помещаете их в стек вызовов:
createDatabase () { name=$1 # first arg on stack echo $name user=$2 # second arg on stack echo $user } createDatabase MyMusic postgres # $1 will be MyMusic # $2 will be postgres
Суть в том, чтобы не позволять скобкам обмануть вас. Вы НЕ МОЖЕТЕ сделать следующее:
createDatabase (dbname, username) {...}
PostgreSQL в Bash
Давайте посмотрим, как написать bash-процедуру, которая добавляет Release в базу данных. Это основная, содержательная процедура, включающая в себя все важные детали, используемые в других процедурах. Не забывайте, что исходники всех процедур находятся на Github, если вы хотите проверить, как они были написаны.
PSQL
Клиент, который мы будем использовать из bash, — это psql
, который упакован в виде бинарного файла со стандартной загрузкой Postgres (документацию можно найти здесь).
Основное использование psql
состоит в том, чтобы вызвать его собственную интерактивную программу или открыть специальную оболочку PSQL
. Однако есть варианты, которые позволяют нам передавать всю необходимую нам информацию через команды, избегая дополнительного интерактивного уровня. Особенно полезны следующие:
--echo-all
— печатает что выполнено и ошибки и т.д.
--quiet
— не выводит никаких выполненных инструкций, ошибок или предупреждений
--command
— хлеб с маслом: строка, представляющая оператор SQL
--dbname
— имя базы данных. В нашем случае MyMusic
--file
— считывает SQL, хранящийся в указанном файле. Я использовал это в процедуре createDatabase
.
--tuples-only
вместо того, чтобы печатать таблицу, он просто печатает значения (мы скоро это увидим)
и наконец
--username
— имя пользователя для подключения к базе данных.
Примечание о паролях
Когда мы вызываем psql
, все операции будут запрашивать пароль, что может сильно раздражать в текущей интерактивной программе. Существует способ передать пароль, установив переменную среды, как показано в потоке переполнения стека https://stackoverflow.com/a/6405296/8954866.
Чтобы убедиться, что у меня всегда была установлена эта переменная, я просто добавил к своим вызовам psql
эту переменную:
PGPASSWORD=$password psql ...
Добавление выпуска
Напомним, релиз имеет следующее:
- id
- художник
- метка
- заглавие
- ссылка на сайт
- слушал
- понравилось
- купил
Таким образом, мы должны сделать несколько вещей:
- Проверьте, что художник существует, и если нет, создайте его
- Убедитесь, что метка существует, и если нет, создайте ее.
- Получить название, ссылку, прослушать, понравиться и купить
- Вставьте все в таблицу
Records
Запрос существования и сохранение результата
1 и 2 идентичны, поэтому давайте посмотрим на исполнителя.
Подскажите пользователю исполнителя:
echo -n "Artist: " read artist
Теперь нам нужно проверить, существует ли этот художник.
В SQL мы знаем, что это будет запрос SELECT
:
SELECT id FROM Artists WHERE name = lower('${artist}');
lower
— это операция PostgreSQL, которая преобразует нашу строку в нижний регистр.
'${ }'
просто преобразует переменную artist
в строку.
Однако нам нужно, чтобы из нашего оператора возвращалось id
, так как же нам это сделать? Установите его в переменную, конечно.
Но подождите, вывод этого оператора выглядит следующим образом:
id ---- 3 (1 row)
Как, черт возьми, мы можем получить «3» в переменной bash без какого-то действительно хакерского разбора текста?
Здесь мы используем --tuples-only
вместе с --quiet
.
Я разобью команду на строки, чтобы ее было легче читать, но обратите внимание, что при использовании терминала все должно быть в одной строке:
PGPASSWORD=$password psql --dbname=$dbname --username=$user --quiet --tuples-only command=" SELECT id FROM Artists WHERE name = lower('${artist}'); "
который возвращает простую «3».
Нам нужно сохранить это в переменной:
artistid=`<entire statement here>`
Получив результат, мы можем отреагировать соответствующим образом:
if [[ $artistid == "" ]]; then addArtist # procedure call artistid=`...` # reuse the above statement to get the id fi
Используйте ту же логику для «метки». Кроме того, я также использовал ту же логику с «заголовком», чтобы проверить, существует ли уже запись. Просто измените SELECT
и используйте другие (читай: подходящие) переменные.
Получение всего остального и его вставка
После получения artistid
, labelid
и записи title
получить link
довольно просто:
echo -n "Record link: [enter if none] " read link
Получение логических значений listened
, liked
и purchased
выполняется по одному и тому же шаблону. По сути, реагируйте на значение «y» и считайте все остальное ложным:
echo -n "Did you listen? (y|n) " read listened if [ "$listened" == "y" ]; then listened=true else listened=false fi
Если вы хотите, чтобы буква «y» была нечувствительна к регистру, просто добавьте еще одно условие:
if [ "$listened" == "y" ] || [ "$listened" == "Y" ];
В качестве альтернативы можно использовать регулярное выражение для соответствия «y», «Y», «yes» и «Yes»:
yesReg="^(y|Y)(es)?$" if [[ "$listened" =~ $yesReg ]];
На этом этапе я решил запросить у пользователя подтверждение перед вставкой выпуска:
echo "please confirm:" echo "artist: $artist" echo "label: $label" echo "title: $title" echo "link: $link" echo "listened: $listened" echo "liked: $liked" echo "purchased: $purchased" echo -n "ok? (y|n) " read confirm if [ "$confirm" != "y" ]; then return fi
Наконец, мы вставим все, используя psql
с набором — echo-all
, вместе со всем остальным (PGPASSWORD
, dbname
и username
):
psql --echo-all ... --command="<SQL COMMAND>"
SQL должен быть:
INSERT into Records (title, artist, label, link, listened, liked, purchased) VALUES ( lower('${title}'), $artistid, $labelid, trim('${link}'), $listened, $liked, $purchased );
Это массивная однострочная строка, поэтому я еще раз покажу полную команду со вставленными символами новой строки и пробелами. Но имейте в виду, что все это должно быть в одной строке вашего скрипта:
PGPASSWORD=$password psql --echo-all --dbname=$dbname --username=$username --command=" INSERT into Records (title, artist, label, link, listened, liked, purchased) VALUES( lower('${title}'), $artistid, $labelid, trim('${link}'), $listened $liked $purchased ); "
Заворачивать
В этой статье было рассмотрено, как создать и изменить локальную базу данных PostgreSQL для управления музыкой с помощью интерактивного сценария bash. Были рассмотрены основы написания сценариев Bash, включая: чтение ввода, процедурное программирование, использование условных выражений, операторы case и циклы while. psql
— это основной интерфейс командной строки для взаимодействия с базой данных PostgreSQL из скрипта bash. В частности, мы можем отправлять информацию в качестве аргументов через psql
, используя --command
, --dbname
и --username
.
Спасибо, что ознакомились с моим проектом CS Weekly.
P.S. Если вам интересно читать о диджейских статьях с точки зрения новичков и любителей, я начал серию под названием Brainwaves. Первая (и пока единственная) статья посвящена выбору треков:
P.P.S. Я руководитель индустриального техно. Не стесняйтесь послушать один из моих миксов или подпишитесь на меня:
Удачного кодирования :)