Bash, curl и jq: запросы к нейросети Claude прямо из скриптов
Готовая библиотека из трёх функций для вызова Claude из shell: сборка JSON через jq, чтение из пайпа, стриминг без лишних утилит, обработка HTTP-ошибок и пакетная обработка файлов.
5 мин · · разработка · автоматизация
Bash, curl и jq — самый короткий путь подключить нейросеть к существующей автоматизации: разбор логов в cron, описание diff перед коммитом, пакетная обработка текстовых файлов. Zukko принимает запросы в OpenAI-формате на https://api.zukko.pro/v1, поэтому достаточно curl. Ниже — от однострочника до небольшой библиотеки функций; все примеры проверены в bash 3.2 (стоит по умолчанию на macOS) и с jq 1.6.
Однострочник
Ключ fetch_... создайте в кабинете Zukko и задайте переменной окружения:
export ZUKKO_API_KEY=fetch_...
curl -sS https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer $ZUKKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-5", "max_tokens": 300,
"messages": [{"role": "user", "content": "Объясни, что делает set -euo pipefail"}]}' \
| jq -r '.choices[0].message.content'-sS убирает индикатор прогресса, но оставляет сообщения об ошибках curl. jq -r печатает строку без кавычек и с настоящими переносами строк — без -r вы получите JSON-строку с \n.
Почему JSON нужно собирать через jq
Подставлять переменную прямо в строку -d "{...$prompt...}" нельзя: первая же кавычка, обратный слеш или перенос строки в тексте сломают JSON, а текст из внешнего источника может изменить структуру запроса. Правильно — передать значения в jq -n через --arg (строка), --argjson (число, JSON) или --rawfile (содержимое файла целиком), и jq сам экранирует всё как надо.
Второй момент — длина. Большой текст в аргументе командной строки упрётся в системный лимит длины аргументов. Поэтому тело запроса передаём curl через stdin: --data-binary @-.
Библиотека функций
Сохраните как zukko.sh и подключайте через source zukko.sh:
#!/usr/bin/env bash
set -o pipefail
: "${ZUKKO_API_KEY:?Задайте переменную ZUKKO_API_KEY}"
ZUKKO_URL="https://api.zukko.pro/v1/chat/completions"
ZUKKO_MODEL="${ZUKKO_MODEL:-claude-sonnet-5}"
zukko_request() {
local tmp code
tmp=$(mktemp)
if ! code=$(curl -sS -o "$tmp" -w '%{http_code}' "$ZUKKO_URL" \
-H "Authorization: Bearer $ZUKKO_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @-); then
rm -f "$tmp"Что делает каждая функция:
zukko_requestчитает JSON запроса из stdin, отправляет его и печатает ответ. Код ответа получаем через-w '%{http_code}', а тело пишем во временный файл: так не нуженhead -n -1, который не работает в BSD-версииheadна macOS. При коде не 200 функция пишет в stderr статус иerror.messageи возвращает 1.ask "вопрос" [max_tokens]— короткий вопрос из аргумента.ask_pipe "задание"— задание из аргумента плюс данные из stdin. Флаг--rawfile data /dev/stdinчитает вход целиком, включая переносы строк.ask_stream "вопрос"— печатает ответ по мере генерации.
set -o pipefail нужен, чтобы ошибка zukko_request в середине конвейера не терялась: без него код возврата всей цепочки равен коду последнего jq, то есть почти всегда 0.
Примеры:
source ./zukko.sh
ask "Придумай 5 названий для CLI-утилиты бэкапа"
git diff --staged | ask_pipe "Напиши сообщение коммита в формате Conventional Commits"
tail -n 300 /var/log/nginx/error.log | ask_pipe "Сгруппируй ошибки и предложи причины"
ZUKKO_MODEL=glm-5.3-flash ask "Переведи на английский: доброе утро"Стриминг без лишних утилит
В режиме "stream": true сервер отвечает строками data: {...} в формате SSE и завершает поток строкой data: [DONE]. Частая ошибка — отдавать этот поток прямо в jq: строки начинаются с data: , и jq падает на разборе. В ask_stream это решено одним вызовом jq:
-Rчитает вход как сырые строки,select(startswith("data: ")) | .[6:]оставляет полезную часть,select(. != "[DONE]")отбрасывает маркер конца,fromjsonразбирает JSON;.choices[0]?.delta.content // empty— защита от последнего чанка: Zukko присылает в нёмusageи пустой массивchoices, и без?и// emptyв выводе появился быnull;-jпечатает фрагменты без переводов строк между ними, а--unbufferedвыводит их сразу.
curl -N отключает буферизацию на стороне curl, а --fail делает так, что при HTTP-ошибке функция вернёт ненулевой код. Тело ошибки в этом режиме не печатается — если стриминг молчит, повторите тот же запрос через ask, чтобы увидеть сообщение сервера.
Коды ошибок
| Код | Причина | Что делать |
|---|---|---|
| 401 | неверный ключ | проверить ZUKKO_API_KEY |
| 402 | не хватает баланса | пополнить баланс в кабинете |
| 403 | модель не разрешена для ключа | изменить allowed_models ключа или модель |
| 413 | слишком большой запрос | сократить вход |
| 429 | слишком частые запросы | уменьшить параллельность, повторить позже |
| 503 | провайдер недоступен | повторить позже или взять другую модель из /v1/models |
Для 429 и 503 в cron-задачах удобно повторять запрос с паузой, например ask "..." || { sleep 10; ask "..."; }. Для 401–403 повтор бессмыслен.
Пакетная обработка файлов
xargs -P запускает несколько запросов параллельно. Функции нужно экспортировать, иначе дочерний bash -c их не увидит:
set -euo pipefail
source ./zukko.sh
export -f zukko_request ask_pipe
export ZUKKO_URL ZUKKO_MODEL ZUKKO_API_KEY
mkdir -p out
find docs -name '*.md' -print0 \
| xargs -0 -n 1 -P 4 bash -c 'set -o pipefail; ask_pipe "Перескажи в 5 пунктах:" < "$1" > "out/${1##*/}"' _Имя файла передаётся в bash -c как позиционный аргумент $1, а не подставляется в строку команды, поэтому пробелы и кавычки в именах ничего не ломают. Каждый ответ пишется в свой файл, так что выводы параллельных запросов не перемешиваются. -P 4 держит не больше четырёх запросов одновременно; если ловите 429, уменьшите число.
Сколько это стоит
Цены в Zukko за 1M токенов (вход/выход): claude-sonnet-5 — $0.30 / $0.30, gpt-5.6-luna — $0.10 / $0.10, glm-5.3-flash — $0.04 / $0.04. Актуальный список моделей:
curl -sS https://api.zukko.pro/v1/models -H "Authorization: Bearer $ZUKKO_API_KEY" | jq -r '.data[].id'или страница «Модели» в кабинете. Пример: пересказ 200 файлов по 3 000 входных и 300 выходных токенов. На claude-sonnet-5: 200 × (3 000 + 300) × $0.30 / 1 000 000 = $0.198. На glm-5.3-flash: 200 × 3 300 × $0.04 / 1 000 000 = $0.0264. Достаточно ли дешёвой модели для вашей задачи, проверьте на своих данных: прогоните 10 файлов через обе модели и сравните результаты.
Частые вопросы
Как передать многострочный промпт?
Через here-document в подстановке команды:
prompt=$(cat <<'EOF'
Ты ревьюер shell-скриптов.
Найди ошибки в скрипте ниже и предложи исправления.
EOF
)
ask_pipe "$prompt" < deploy.shКавычки вокруг 'EOF' отключают подстановку переменных внутри текста. Конструкция read -d '' для этого хуже: она возвращает ненулевой код и роняет скрипт с set -e.
Почему в ответе видны \n и кавычки?
Не указан jq -r. Без него jq печатает значение как JSON-строку.
Как сохранить результат в переменную?
summary=$(ask_pipe "Кратко:" < report.txt). Если запрос упал, функция вернёт 1, а при set -e скрипт остановится — это удобнее, чем молча записать пустую строку.
Можно ли вызвать Claude в Anthropic-формате через curl?
Да: POST https://api.zukko.pro/v1/messages с заголовком anthropic-version: 2023-06-01, ответ лежит в .content[0].text. Формат запроса описан в справочнике Messages API, а OpenAI-формат — в справочнике chat completions.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Перевод субтитров нейросетью: SRT без сбитых таймкодовГлавный риск при переводе SRT нейросетью — сломанная структура файла. Показываем схему, где таймкоды не попадают к модели, и скрипт на Python с проверкой.
- Редактирование текстов нейросетью: корректура, стиль и сокращениеРазные виды редактуры требуют разных промптов. Показываем формулировки для корректуры, стиля и сокращения, как получать правки с объяснением и не терять авторский голос.
- Нейросеть для написания скриптов: Bash, PowerShell и PythonМодель быстро пишет скрипты для рутины, но запускать их вслепую нельзя. Показываем, как ставить задачу, требовать безопасный режим dry-run и проверять результат до запуска.
- Dify в Docker: установка, первичная настройка и подключение моделейСтавим Dify через Docker Compose, создаём администратора и подключаем модели Claude, GPT и DeepSeek по одному ключу. Отдельно — что делать, если контейнеры не стартуют.