Содержание этой статьи было первоначально написано Барендом Гарвелинком в блоге Xebia .
В настоящее время я работаю в команде проекта, работающей над приложением, которое хранит большую часть своих данных в CouchDB . Одна из замечательных особенностей Couch — его RESTful API. Это все просто HTTP и JSON, легко понять и легко программировать.
Одним из аспектов, где этот интерфейс не так легко доступен, является сценарий оболочки. Существует завиток для обработки всех HTTP-вещей, которые нам могут понадобиться, но преобразование структуры JSON или извлечение информации из нее оказалось менее простым. Мы можем охватить простые случаи с помощью grep и awk, но JSON достаточно сложен, чтобы мы (или, ну, я) не захотели. Если бы документы были в формате XML, мы могли бы использовать xpath и xslt для выполнения нашей тяжелой работы. Насколько мне известно, нет эквивалента xmlstarlet для JSON, который бы надежно справлялся с этой работой.
Мы решили нашу проблему сценариев оболочки, и решение совершенно очевидно, потому что есть отличный DSL для управления структурами JSON. Это называется JavaScript .
Наши сценарии администрирования используют Node.js в качестве стадии конвейера. Существует так много блогов и статей, в которых говорится о Node как о серверной платформе, поэтому легко забыть, что это универсальный интерпретатор JavaScript и REPL.
Простой пример
Этот скрипт читает представление CouchDB, в котором перечислены документы, от которых мы хотим избавиться. В этом примере я использовал стандартное представление _all_docs, которое доступно в каждой базе данных Couch. Затем он обрабатывает этот список в JavaScript, чтобы создать команду удаления для массового API CouchDB . Затем команда выполняется, и объект ответа печатается с помощью jsonpp .
COUCH="http://localhost:5984/dbname" curl "${COUCH}/_all_docs&include_docs=true" \ | ./format_delete_command.js \ | curl --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ -X POST --data-binary @- "${COUCH}/_bulk_docs" \ | tr -d '\n' | jsonpp
Для тех, кто не знаком с curl: параметр -data-binary указывает тело запроса. Знак @ указывает, что ниже следует имя файла. Для публикации из stdin используйте знак — в качестве имени файла. Без знака at значение параметра используется непосредственно как тело запроса.
Когда мы читаем из этого представления CouchDB, ответ выглядит примерно так:
{"total_rows":22,"offset":0,"rows":[ {"id":"123456","key":"123456","value":{"rev":"1-acf7f3..."},"doc":{...}}, {"id":"123457","key":"123457","value":{"rev":"1-b67df1..."},"doc":{...}} ...20 more... ]}
Обратите внимание, что поле doc пропущено, если вы не передадите include_docs = true в качестве параметра запроса.
Чтобы отправить команду массового удаления, мы отправляем это:
{ "docs":[ {"_id":"123456","_rev":"1-acf6f39495a2cd4465be504cd435629e","_deleted":true} {"_id":"123457","_rev":"1-b67df18954264dbb65be341294e572a5","_deleted":true} ...20 more... ]}
awk мог бы справиться с этим. Вот JavaScript, который мы используем вместо:
#! /usr/bin/env node var inputStream = process.stdin , data = ''; process.stdin.resume(); // Read the entire input stream into the data variable. inputStream.on('data', function(chunk) { data += chunk; }); // At end of stream, load the JSON object and process it. inputStream.on('end', function() { var json = JSON.parse(data) , rows = json['rows'] , postdata = { docs: [] }; rows.forEach(function(row) { var doc = row['doc']; postdata.docs.push( {_id: doc._id, _rev: doc._rev, _deleted: true} ); }); console.log(JSON.stringify(postdata)); });
Несколько вещей, на которые стоит обратить внимание:
- Node.js достаточно умен, чтобы игнорировать строку hashbang.
- Мы просто буферизируем весь ввод во временную переменную. Наши документы маленькие, нам не нужен умный потоковый парсер.
- Используйте console.log для записи в стандартную строку, console.warn для записи в стандартную ошибку. Вы можете использовать process.exit (int) для выхода с кодом ошибки.
- Для этого конкретного представления у нас были идентификаторы и обороты в выводе представления, и поэтому мы могли бы отформатировать команду удаления без передачи параметра include_docs. Это потому, что я использовал представление _all_docs для примера. Наши фактические представления имеют различный вывод, и использование _all_docs заставляет скрипт работать со всеми из них. (Пожалуйста, не передавайте свой _all_docs вид через этот скрипт, просто удалите базу данных!)
Другие наши скрипты используют тот же подход.
Резюме
Node.js так часто называют сервером, что легко забыть, что вы можете использовать его в конвейере оболочки. Этот пост демонстрирует, как вы можете сделать это.
Обновлено 2012-06-27:
Вы можете проверить мой следующий пост, сценарии оболочки JSON с jsawk .