Содержание этой статьи было первоначально написано Барендом Гарвелинком в блоге 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 .