シェルスクリプトでcurlとjqを使ってREST APIを自動操作する方法|認証ヘッダー・エラー処理・ページング設計の実装パターン

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトでcurlとjqを使ってREST APIを自動操作する方法|認証ヘッダー・エラー処理・ページング設計の実装パターン
「cronで動くバックアップスクリプトからSlackに完了通知を送りたい」「GitHubのAPIでブランチ一覧を定期取得してレポートを自動化したい」
こうした要件でcurlを使ってREST APIを呼び出すシェルスクリプトを書こうとすると、認証エラーの扱い方がわからない、HTTPの4xxエラーをスクリプトが無視して処理を続けてしまう、といった問題に直面しやすい。

この記事では、curlとjqを組み合わせてREST APIを自動操作するシェルスクリプトの設計パターンを解説します。認証情報の安全な管理、HTTPステータスコードを使ったエラー検知、複数ページのレスポンスを取りまとめるページング設計まで、実際の実行結果を交えながら紹介します。

実行環境: RHEL 9.4 / Rocky Linux 9 / Ubuntu 24.04 LTS(bash 5.x、curl 7.76以上、jq 1.6以上)

この記事のポイント

・curl -s -oと-wでレスポンス本文とHTTPステータスコードを別々に受け取れる
・APIトークンはスクリプト内にハードコードせず環境変数かchmod 600ファイルで管理する
・HTTPステータスが4xx/5xxのときはスクリプトをexit 1で止める条件分岐が必須
・ページングはwhile true + 空配列でbreakの汎用パターンが実装しやすく可読性も高い


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜシェルスクリプトからREST APIを操作するのか

REST APIをシェルスクリプトから呼び出す場面は実務で頻繁にある。Python・Ruby・Goのような言語の実行環境を準備しなくても、bash・curl・jqの3つがあれば完結できるのが現場でのシェルの強みだ。

代表的なユースケースを挙げると次のとおり。

・Slack Incoming Webhookでバックアップ失敗時にアラートを自動送信する
・GitHub APIでブランチ一覧やリリースタグを定期取得してレポートを自動生成する
・ZabbixのAPIでホスト設定をスクリプトから一括更新する
・クラウドサービスの認証APIで一時トークンを取得し、後続コマンドに引き渡す

ただし、単に curl http://api.example.com/data と書いただけのスクリプトは、APIが4xxや5xxを返してもcurlの終了コードが0になり、エラーを見逃して処理を継続してしまいがちだ。本番運用に耐えるためには、エラー検知と適切なリカバリーの設計が欠かせない。

curlとjqを組み合わせる基本パターン

1. GETリクエストでデータを取得する

最もシンプルな組み合わせは、curlでAPIを叩いてjqに渡すパイプ接続だ。GitHub公開APIを例に確認する。

#!/bin/bash # GitHub APIからリポジトリの基本情報を取得する例 ORG="torvalds" REPO="linux" curl -s \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${ORG}/${REPO}" \ | jq '.full_name, .description, .stargazers_count'

検証サーバー(Rocky Linux 9.4)での実行結果:

"torvalds/linux" "Linux kernel source tree" 186462

-sオプション(silent)でcurlの進捗表示を抑制し、レスポンスのJSONだけをjqに渡している。jqのフィルタで必要なフィールドだけを抽出するのが基本形だ。

2. POSTリクエストでJSONを送信する

SlackのIncoming WebhookなどJSONをPOSTするケースは次のように書く。

#!/bin/bash # Slack Incoming WebhookにPOSTする例 WEBHOOK_URL="${SLACK_WEBHOOK_URL}" MESSAGE="バックアップ完了: $(date '+%Y-%m-%d %H:%M:%S')" # jqの--argでJSONを安全に生成する(変数内の特殊文字を自動エスケープ) PAYLOAD=$(jq -n --arg text "${MESSAGE}" '{text: $text}') curl -s -X POST \ -H "Content-Type: application/json" \ -d "${PAYLOAD}" \ "${WEBHOOK_URL}"

POSTボディに変数を展開する際は jqの--argオプションでJSONを生成するのが安全だ。シェルで文字列を直接連結してJSON化しようとすると、改行やダブルクォートが混入したときにJSON構文エラーになりやすい。

3. 配列レスポンスをjqで分解する

APIが配列を返す場合、jq '.[]'またはjq -r '.[] | .フィールド名'で各要素を展開できる。

#!/bin/bash # GitHubのブランチ名を一覧取得する例 ORG="torvalds" REPO="linux" curl -s \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${ORG}/${REPO}/branches?per_page=5" \ | jq -r '.[].name'

実行結果(検証サーバーでの出力例):

master linux-rolling-lts

認証情報を安全に扱う設計

1. Bearer Tokenを環境変数で管理する

APIトークンをスクリプトに直接書き込むと、Gitにコミットしたときに流出するリスクがある。環境変数から読み込む設計が基本だ。

#!/bin/bash # スクリプト先頭でトークンの存在を確認する if [[ -z "${GITHUB_TOKEN}" ]]; then echo "エラー: 環境変数 GITHUB_TOKEN が設定されていません" >&2 exit 1 fi curl -s \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/user" \ | jq '.login, .name'

スクリプトの先頭でトークンの存在チェックを行い、未設定なら即 exit 1 するのが安全設計の鉄則だ。cronから実行する場合は、呼び出し元のcrontabに GITHUB_TOKEN=xxxx を記述するか、後述のファイル読み込み方式を使う。

シェルの環境変数の設定方法や読み込み方法については、シェル環境変数設定の基本も合わせて参照してほしい。

2. トークンをファイルで管理する(chmod 600 設計)

cronなど環境変数が渡せないケースでは、専用のトークンファイルを使う。

#!/bin/bash # トークンファイルから読み込む設計 TOKEN_FILE="${HOME}/.config/github_token" if [[ ! -r "${TOKEN_FILE}" ]]; then echo "エラー: トークンファイルが見つかりません: ${TOKEN_FILE}" >&2 exit 1 fi API_TOKEN=$(< "${TOKEN_FILE}") curl -s \ -H "Authorization: Bearer ${API_TOKEN}" \ "https://api.github.com/user"

# トークンファイルのパーミッションを設定する(所有者のみ読み取り可能) chmod 600 ~/.config/github_token ls -la ~/.config/github_token # -rw------- 1 ec2-user ec2-user 41 8月 7 09:15 /home/ec2-user/.config/github_token

トークンファイルのパーミッションは必ず600に設定する。644のままだと同じサーバーの他ユーザーに読まれてしまう。

3. Basic認証を使うAPIへの対応

ZabbixやRedmineなど、Basic認証を使うAPIには-uオプションを使う。

#!/bin/bash # Basic認証を使うAPIの例 API_USER="${ZABBIX_USER}" API_PASS="${ZABBIX_PASS}" curl -s \ -u "${API_USER}:${API_PASS}" \ -H "Content-Type: application/json" \ "https://zabbix.example.com/api_jsonrpc.php" \ -d '{"jsonrpc":"2.0","method":"apiinfo.version","params":[],"id":1}' \ | jq '.result'

パスワードも環境変数経由で渡す設計はBearer Tokenと同様だ。-uの引数をスクリプトにハードコードしてはいけない。

HTTPステータスコードでエラーを検知する設計

1. -w "%{http_code}"でステータスコードを分離して取得する

単純なパイプ接続(curl ... | jq ...)だと、APIが4xxや5xxを返してもcurlの終了ステータスは0になる。レスポンスボディとHTTPステータスコードを分離して取得するのが確実なエラー検知設計だ。

#!/bin/bash # レスポンスボディとHTTPステータスコードを分離して取得する API_URL="https://api.github.com/repos/torvalds/linux" TMP_FILE=$(mktemp) # -o でボディをファイルへ、-w でステータスコードのみを変数に取り込む HTTP_STATUS=$(curl -s \ -H "Accept: application/vnd.github+json" \ -o "${TMP_FILE}" \ -w "%{http_code}" \ "${API_URL}") if [[ "${HTTP_STATUS}" -ge 200 && "${HTTP_STATUS}" -lt 300 ]]; then # 成功(2xx): jqでレスポンスを解析する jq '.full_name, .stargazers_count' "${TMP_FILE}" else # エラー(4xx/5xx): ステータスコードとボディを stderr に出力 echo "APIエラー: HTTP ${HTTP_STATUS}" >&2 cat "${TMP_FILE}" >&2 rm -f "${TMP_FILE}" exit 1 fi rm -f "${TMP_FILE}"

認証エラー(401)が発生した場合の実行結果:

APIエラー: HTTP 401 {"message":"Requires authentication","documentation_url":"https://docs.github.com/..."}

-o "${TMP_FILE}"でレスポンスボディをファイルに保存し、-w "%{http_code}"でステータスコードのみを変数に取り込む。2つを同時にパイプで受け取ることはできないため、この「一時ファイル経由」が定番のパターンだ。

2. API呼び出しを関数化して再利用する

複数のAPIを叩くスクリプトでは、エラー検知ロジックを関数化して再利用するのが保守性の高い設計だ。

#!/bin/bash # Bearer Token認証付きのGETリクエスト関数 api_get() { local url="$1" local tmp tmp=$(mktemp) local status status=$(curl -s \ -H "Authorization: Bearer ${API_TOKEN}" \ -H "Accept: application/vnd.github+json" \ -o "${tmp}" \ -w "%{http_code}" \ "${url}") if [[ "${status}" -lt 200 || "${status}" -ge 300 ]]; then echo "API GET 失敗: HTTP ${status} URL=${url}" >&2 cat "${tmp}" >&2 rm -f "${tmp}" return 1 fi cat "${tmp}" rm -f "${tmp}" return 0 } # 使用例:失敗時はそのままスクリプトを終了する RESULT=$(api_get "https://api.github.com/orgs/torvalds/repos?per_page=5") || exit 1 echo "${RESULT}" | jq -r '.[].name'

curlとjqを使ったシェルスクリプトのAPI連携設計パターンをさらに体系的に学びたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で即使える設計パターンを実機ハンズオンで身につけられます。

ページング対応:複数ページのレスポンスを全件取得する設計

GitHubやGitLabなど多くのAPIは、一度に返せる件数に上限があり、複数ページに分割してレスポンスを返す。全件取得が必要な場面のページング設計パターンを見ていく。

1. pageパラメータを使う汎用型

page=1から順に増やしてAPIを呼び出し、空の配列が返ってきたら終了する汎用パターンだ。

#!/bin/bash # pageパラメータで全件取得するパターン fetch_all_items() { local base_url="$1" local per_page=100 local page=1 local all_results="[]" while true; do local result result=$(api_get "${base_url}?per_page=${per_page}&page=${page}") if [[ $? -ne 0 ]]; then echo "ページ ${page} の取得に失敗しました" >&2 return 1 fi # 空の配列([])が返ってきたらループ終了 local count count=$(echo "${result}" | jq 'length') if [[ "${count}" -eq 0 ]]; then break fi # 取得結果を累積する all_results=$(echo "${all_results} ${result}" | jq -s 'add') page=$((page + 1)) done echo "${all_results}" } # 使用例 ALL_REPOS=$(fetch_all_items "https://api.github.com/orgs/torvalds/repos") || exit 1 TOTAL=$(echo "${ALL_REPOS}" | jq 'length') echo "合計: ${TOTAL} 件取得" echo "${ALL_REPOS}" | jq -r '.[].name'

2. Linkヘッダーを使うGitHub/GitLab型

GitHubのAPIはLinkレスポンスヘッダーに次ページのURLを含める。-Dオプションでヘッダーをファイルに保存し、次ページURLを解析するパターンも実務でよく使われる。

#!/bin/bash # Linkヘッダーから次ページURLを抽出する関数 extract_next_url() { local link_header="$1" # rel="next" のURLを正規表現で取り出す echo "${link_header}" | grep -o '<[^>]*>; rel="next"' | sed 's/.*$//' } # Linkヘッダー方式での全件取得 fetch_all_with_link() { local url="$1" local all_results="[]" while [[ -n "${url}" ]]; do local tmp_body tmp_header tmp_body=$(mktemp) tmp_header=$(mktemp) local status status=$(curl -s \ -H "Authorization: Bearer ${API_TOKEN}" \ -H "Accept: application/vnd.github+json" \ -D "${tmp_header}" \ -o "${tmp_body}" \ -w "%{http_code}" \ "${url}") if [[ "${status}" -ge 400 ]]; then echo "エラー: HTTP ${status}" >&2 rm -f "${tmp_body}" "${tmp_header}" return 1 fi all_results=$(echo "${all_results} $(cat ${tmp_body})" | jq -s 'add') local link_header link_header=$(grep -i '^Link:' "${tmp_header}" | tr -d '\r') url=$(extract_next_url "${link_header}") rm -f "${tmp_body}" "${tmp_header}" done echo "${all_results}" }

Linkヘッダー方式はAPIが次ページのURLを直接返すため、pageパラメータの連番計算が不要になる利点がある。ただし、ヘッダー解析のコードが複雑になるため、小規模なスクリプトではpageパラメータ方式の方が読みやすい場合が多い。

本記事のまとめ

シェルスクリプトでcurlとjqを使ったREST API自動操作の設計パターンをまとめる。
やりたいこと 設計パターン 注意点
GETリクエスト + JSON解析 curl -s URL | jq '.field' -sで進捗表示を抑制する
POSTでJSONを安全に送信 jq -n --arg text "$MSG" '{text:$text}' 変数直接連結は避けてjqで生成する
HTTPステータスコードを取得 curl -s -o body.tmp -w "%{http_code}" URL 一時ファイルでボディとコードを分離する
Bearer Token認証 curl -H "Authorization: Bearer ${TOKEN}" URL トークンは環境変数か chmod 600 ファイルから読む
4xx/5xxエラーで停止 [[ "${HTTP_STATUS}" -ge 400 ]] && exit 1 エラーボディも stderr に出力して記録する
全件ページング取得 while true; do ... [[ count -eq 0 ]] && break; done jq length で空配列をbreakの判定に使う
・curl -s -oと-wを組み合わせてHTTPステータスコードを変数に取り出すのがエラー検知の基本
・APIトークンはスクリプト内へのハードコード厳禁。環境変数かchmod 600ファイルで管理する
・POSTのJSONボディは文字列連結ではなくjqの--argで生成することで特殊文字の問題を防ぐ
・ページングはpage=1から始めて空配列でbreakする汎用パターンが実装しやすく保守性も高い
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。