Bash ile REST API Tüketimi: curl ve jq ile JSON Verisi İşleme

Günlük sistem yönetimi işlerinde REST API’larla konuşmak artık kaçınılmaz bir beceri haline geldi. İster monitoring sisteminden veri çekiyor ol, ister bir bulut servisini yönetiyor, ister şirket içi bir uygulamanın durumunu kontrol ediyor olun, bir noktada Bash scriptinden API çağrısı yapmanız gerekiyor. Bu yazıda curl ve jq ikilisini kullanarak REST API tüketimini, JSON işlemeyi ve bunları gerçek dünya senaryolarında nasıl kullanabileceğinizi ele alacağız.

Temel Araçlar: curl ve jq

Başlamadan önce bu iki aracı tanıyalım. curl zaten çoğu sistemde kurulu geliyor ama jq için kurulum gerekebilir.

# Ubuntu/Debian
sudo apt-get install -y curl jq

# RHEL/CentOS/Rocky
sudo dnf install -y curl jq

# macOS
brew install curl jq

# Versiyonları kontrol et
curl --version
jq --version

curl HTTP isteklerini yapan araç, jq ise JSON verisini parse edip işlemenizi sağlayan hafif ama son derece güçlü bir komut satırı aracı. Bu ikisi birlikte kullanıldığında bash scriptleriniz gerçekten profesyonel bir hal alıyor.

İlk API Çağrısı: Temel curl Kullanımı

En basit GET isteğiyle başlayalım. Test amacıyla jsonplaceholder.typicode.com gibi ücretsiz API’ları kullanabilirsiniz.

# Basit GET isteği
curl https://jsonplaceholder.typicode.com/users/1

# Daha okunaklı çıktı için jq ile pipe
curl -s https://jsonplaceholder.typicode.com/users/1 | jq .

# Sık kullandığım curl parametreleri:
# -s: Silent mod, progress bar gösterme
# -S: Hata olduğunda göster (genellikle -s ile birlikte)
# -L: Redirect'leri takip et
# -o: Çıktıyı dosyaya yaz
# -w: İstek sonrası istatistik göster
# --max-time: Zaman aşımı süresi (saniye)
# -k: SSL sertifika doğrulamasını atla (dikkatli kullanın!)

-s parametresi script yazarken olmazsa olmaz. Progress bar ve gereksiz çıktıları bastırıp sadece API yanıtını almanızı sağlıyor. Ama -sS kombinasyonunu kullanmayı öneririm, bu sayede hata olduğunda yine de göreceksiniz.

jq ile JSON Verisi İşleme

jq öğrenmenin en iyi yolu örneklerle gitmek. Önce temel filtreleri anlayalım.

# Örnek JSON verisi ile çalışalım
JSON='{"name":"Ali","age":30,"city":"Istanbul","roles":["admin","operator"]}'

# Tüm veriyi göster
echo $JSON | jq .

# Belirli bir alanı çek
echo $JSON | jq '.name'           # "Ali" (tırnaklı)
echo $JSON | jq -r '.name'        # Ali (tırnaksız, -r raw output)

# İç içe alanlara erişim
curl -s https://jsonplaceholder.typicode.com/users/1 | jq -r '.address.city'

# Array içindeki elemanlara erişim
echo $JSON | jq -r '.roles[0]'    # admin
echo $JSON | jq -r '.roles[]'     # Tüm elemanları ayrı satırda

# Birden fazla alan seç
curl -s https://jsonplaceholder.typicode.com/users/1 | jq '{isim: .name, email: .email}'

# Array'den belirli alanları filtrele
curl -s https://jsonplaceholder.typicode.com/users | jq -r '.[].email'

-r (raw output) parametresini çok kullanacaksınız. Normalde jq string değerleri tırnak içinde döndürür, ama bash değişkenine atayacaksanız ya da başka komutlara pipe edecekseniz tırnaksız hali lazım.

HTTP Metodları: POST, PUT, DELETE

GET dışında diğer metodları da kullanmak gerekiyor tabii.

# POST isteği - JSON body göndermek
curl -s -X POST 
  -H "Content-Type: application/json" 
  -d '{"title":"Test Post","body":"Bu bir test","userId":1}' 
  https://jsonplaceholder.typicode.com/posts | jq .

# Değişkenlerden JSON body oluşturma (jq ile güvenli yöntem)
TITLE="Sunucu Raporu"
BODY="Tüm sistemler normal"
USER_ID=1

JSON_BODY=$(jq -n 
  --arg title "$TITLE" 
  --arg body "$BODY" 
  --argjson userId "$USER_ID" 
  '{title: $title, body: $body, userId: $userId}')

curl -s -X POST 
  -H "Content-Type: application/json" 
  -d "$JSON_BODY" 
  https://jsonplaceholder.typicode.com/posts | jq .

# PUT isteği
curl -s -X PUT 
  -H "Content-Type: application/json" 
  -d '{"id":1,"title":"Güncellenmiş Başlık"}' 
  https://jsonplaceholder.typicode.com/posts/1 | jq .

# DELETE isteği
curl -s -X DELETE https://jsonplaceholder.typicode.com/posts/1

Burada önemli bir nokta: değişkenleri doğrudan string interpolation ile JSON içine koymayın. Özellikle değişken içinde özel karakter, boşluk ya da tırnak işareti varsa JSON bozulur. jq -n ile --arg ve --argjson kullanmak çok daha güvenli.

Kimlik Doğrulama Yöntemleri

Gerçek API’ların büyük çoğunluğu authentication gerektiriyor. Sık kullanılan yöntemlere bakalım.

# Basic Authentication
curl -s -u "kullanici:sifre" https://api.example.com/endpoint

# Bearer Token (JWT gibi)
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/endpoint

# API Key header olarak
curl -s -H "X-API-Key: your-api-key-here" https://api.example.com/endpoint

# API Key query parameter olarak
curl -s "https://api.example.com/endpoint?api_key=your-key"

# Token'ı önce alıp sonra kullanmak (OAuth2 benzeri)
AUTH_RESPONSE=$(curl -s -X POST 
  -H "Content-Type: application/json" 
  -d '{"username":"admin","password":"secret"}' 
  https://api.example.com/auth/login)

TOKEN=$(echo $AUTH_RESPONSE | jq -r '.access_token')

# Sonraki isteklerde kullan
curl -s -H "Authorization: Bearer $TOKEN" 
  https://api.example.com/protected-endpoint | jq .

Token’ları ve şifreleri doğrudan script içine yazmaktan kaçının. Environment variable olarak ya da güvenli bir secret yönetim sisteminden (HashiCorp Vault, AWS Secrets Manager gibi) alın.

HTTP Durum Kodlarını Kontrol Etmek

Script yazarken sadece yanıt içeriğine değil, HTTP status code’una da bakmak gerekiyor. Aksi halde API hata döndürdüğünde scriptiniz sanki her şey yolundaymış gibi devam edebilir.

#!/bin/bash

API_URL="https://jsonplaceholder.typicode.com/posts/1"

# HTTP status code'u ve response body'yi ayrı ayrı al
HTTP_RESPONSE=$(curl -s -w "n%{http_code}" "$API_URL")
HTTP_BODY=$(echo "$HTTP_RESPONSE" | head -n -1)
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tail -n 1)

echo "Status: $HTTP_STATUS"

if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then
    echo "Başarılı yanıt alındı"
    echo "$HTTP_BODY" | jq -r '.title'
elif [ "$HTTP_STATUS" -eq 401 ]; then
    echo "Hata: Kimlik doğrulama başarısız" >&2
    exit 1
elif [ "$HTTP_STATUS" -eq 404 ]; then
    echo "Hata: Kaynak bulunamadı" >&2
    exit 1
elif [ "$HTTP_STATUS" -ge 500 ]; then
    echo "Hata: Sunucu taraflı hata ($HTTP_STATUS)" >&2
    exit 1
else
    echo "Beklenmedik durum kodu: $HTTP_STATUS" >&2
    exit 1
fi

-w "n%{http_code}" parametresi response body’nin sonuna bir satır sonu ve ardından HTTP status code’u ekliyor. head -n -1 ile son satır hariç her şeyi, tail -n 1 ile sadece son satırı (status code’u) alıyoruz.

Gerçek Dünya Senaryosu 1: GitHub API ile Repo İzleme

Diyelim ki belirli bir GitHub organizasyonunun repolarını izlemek ve son commitleri raporlamak istiyorsunuz.

#!/bin/bash

# Konfigürasyon
GITHUB_TOKEN="${GITHUB_TOKEN:-}"  # Environment variable'dan al
ORG="hashicorp"
MIN_STARS=1000

if [ -z "$GITHUB_TOKEN" ]; then
    echo "UYARI: GITHUB_TOKEN set edilmemiş, rate limit düşük olacak" >&2
fi

# Organizasyonun repolarını çek
echo "=== $ORG Organizasyonu - Popüler Repolar ==="

REPOS=$(curl -s 
  -H "Accept: application/vnd.github.v3+json" 
  ${GITHUB_TOKEN:+-H "Authorization: token $GITHUB_TOKEN"} 
  "https://api.github.com/orgs/$ORG/repos?per_page=100&sort=stars&direction=desc")

# Rate limit kontrolü
RATE_LIMIT=$(curl -s 
  -H "Accept: application/vnd.github.v3+json" 
  ${GITHUB_TOKEN:+-H "Authorization: token $GITHUB_TOKEN"} 
  "https://api.github.com/rate_limit" | jq '.rate.remaining')

echo "Kalan API çağrısı: $RATE_LIMIT"
echo ""

# 1000+ yıldızı olan repoları listele
echo "$REPOS" | jq -r 
  --argjson min_stars "$MIN_STARS" 
  '.[] | select(.stargazers_count >= $min_stars) | 
   "(.name) | Yıldız: (.stargazers_count) | Dil: (.language // "Belirtilmemiş") | (.description // "Açıklama yok")"'

# İstatistik özeti
echo ""
echo "=== Özet ==="
echo "$REPOS" | jq -r 
  'group_by(.language) | 
   .[] | 
   {dil: .[0].language, adet: length} | 
   "(.dil // "Belirtilmemiş"): (.adet) repo"' | sort -t: -k2 -rn | head -5

Gerçek Dünya Senaryosu 2: Monitoring API’dan Veri Çekme

Prometheus ya da benzeri bir monitoring sisteminin API’sını kullanarak alert durumlarını kontrol eden bir script yazalım.

#!/bin/bash

ALERTMANAGER_URL="${ALERTMANAGER_URL:-http://localhost:9093}"
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
CRITICAL_SERVICES=("nginx" "postgresql" "redis")

# AlertManager'dan aktif alertleri çek
ALERTS=$(curl -s --max-time 10 "$ALERTMANAGER_URL/api/v2/alerts")

if [ $? -ne 0 ]; then
    echo "HATA: AlertManager'a ulaşılamıyor: $ALERTMANAGER_URL" >&2
    exit 1
fi

# Firing durumdaki kritik alertleri filtrele
FIRING_COUNT=$(echo "$ALERTS" | jq '[.[] | select(.status.state == "active")] | length')

echo "Aktif alert sayısı: $FIRING_COUNT"

if [ "$FIRING_COUNT" -eq 0 ]; then
    echo "Tüm sistemler normal."
    exit 0
fi

# Her alert için detay göster
echo ""
echo "=== Aktif Alertler ==="
echo "$ALERTS" | jq -r '
  .[] | 
  select(.status.state == "active") |
  "[(.labels.severity // "unknown" | ascii_upcase)] (.labels.alertname) - (.labels.instance // "N/A")"
'

# Slack bildirimi gönder
if [ -n "$SLACK_WEBHOOK" ]; then
    ALERT_SUMMARY=$(echo "$ALERTS" | jq -r '
      [.[] | select(.status.state == "active") | 
       ":red_circle: (.labels.alertname) ((.labels.severity // "unknown"))"] | 
      join("n")')

    SLACK_PAYLOAD=$(jq -n 
      --arg count "$FIRING_COUNT" 
      --arg alerts "$ALERT_SUMMARY" 
      '{
        "text": ":rotating_light: *($count) Aktif Alert Var*",
        "attachments": [{
          "color": "danger",
          "text": $alerts
        }]
      }')

    curl -s -X POST 
      -H "Content-Type: application/json" 
      -d "$SLACK_PAYLOAD" 
      "$SLACK_WEBHOOK" > /dev/null

    echo "Slack bildirimi gönderildi."
fi

Pagination ile Büyük Veri Setleri

Çoğu API büyük veri setlerini sayfalara bölerek döndürür. Bunu handle etmek gerekiyor.

#!/bin/bash

# GitHub API pagination örneği
fetch_all_issues() {
    local repo="$1"
    local page=1
    local all_issues="[]"
    local per_page=100

    echo "Issues çekiliyor: $repo" >&2

    while true; do
        RESPONSE=$(curl -s 
          -H "Accept: application/vnd.github.v3+json" 
          "https://api.github.com/repos/$repo/issues?state=open&per_page=$per_page&page=$page")

        # Boş array gelirse dur
        PAGE_COUNT=$(echo "$RESPONSE" | jq 'length')

        if [ "$PAGE_COUNT" -eq 0 ]; then
            break
        fi

        # Mevcut sayfayı birleştir
        all_issues=$(echo "$all_issues $RESPONSE" | jq -s '.[0] + .[1]')

        echo "Sayfa $page: $PAGE_COUNT issue alındı" >&2

        # Bir sayfanın limitinden az geldiyse son sayfayı geçtik
        if [ "$PAGE_COUNT" -lt "$per_page" ]; then
            break
        fi

        page=$((page + 1))

        # Rate limiting için kısa bekleme
        sleep 0.5
    done

    echo "$all_issues"
}

# Kullanım
ISSUES=$(fetch_all_issues "torvalds/linux")
TOTAL=$(echo "$ISSUES" | jq 'length')
echo "Toplam açık issue: $TOTAL"

# Label bazında grupla
echo ""
echo "=== Label Dağılımı ==="
echo "$ISSUES" | jq -r '
  [.[] | .labels[] | .name] | 
  group_by(.) | 
  map({label: .[0], count: length}) | 
  sort_by(-.count) | 
  .[:10][] | 
  "(.label): (.count)"
'

Hata Yönetimi ve Retry Mekanizması

Production ortamında kullandığınız scriptlerin network hatalarına, geçici API kesintilerine karşı dayanıklı olması lazım.

#!/bin/bash

# Retry mekanizmalı API çağrısı
api_call_with_retry() {
    local url="$1"
    local method="${2:-GET}"
    local data="${3:-}"
    local max_retries=3
    local retry_delay=2
    local attempt=1

    while [ $attempt -le $max_retries ]; do
        if [ "$method" = "GET" ]; then
            RESPONSE=$(curl -s -S 
              --max-time 30 
              --connect-timeout 10 
              -w "n%{http_code}" 
              "$url" 2>&1)
        else
            RESPONSE=$(curl -s -S 
              --max-time 30 
              --connect-timeout 10 
              -w "n%{http_code}" 
              -X "$method" 
              -H "Content-Type: application/json" 
              -d "$data" 
              "$url" 2>&1)
        fi

        CURL_EXIT=$?
        HTTP_BODY=$(echo "$RESPONSE" | head -n -1)
        HTTP_STATUS=$(echo "$RESPONSE" | tail -n 1)

        # curl başarısız olduysa
        if [ $CURL_EXIT -ne 0 ]; then
            echo "Deneme $attempt/$max_retries başarısız (curl hatası: $CURL_EXIT)" >&2
        # 5xx hataları için retry
        elif [ "$HTTP_STATUS" -ge 500 ] 2>/dev/null; then
            echo "Deneme $attempt/$max_retries başarısız (HTTP $HTTP_STATUS)" >&2
        else
            # Başarılı yanıt
            echo "$HTTP_BODY"
            return 0
        fi

        if [ $attempt -lt $max_retries ]; then
            echo "Bekleniyor: ${retry_delay}s..." >&2
            sleep $retry_delay
            retry_delay=$((retry_delay * 2))  # Exponential backoff
        fi

        attempt=$((attempt + 1))
    done

    echo "HATA: $max_retries denemeden sonra başarısız" >&2
    return 1
}

# Kullanım
DATA=$(api_call_with_retry "https://jsonplaceholder.typicode.com/posts/1")
if [ $? -eq 0 ]; then
    echo "$DATA" | jq -r '.title'
fi

Exponential backoff kavramı burada çok önemli. Her başarısız denemede bekleme süresini katlıyoruz. Bu sayede sunucu üzerindeki yükü artırmıyoruz ve geçici sorunların kendiliğinden çözülmesine fırsat veriyoruz.

jq ile İleri Seviye Filtreler

jq çok daha güçlü şeyler yapabiliyor. Birkaç pratik örnek daha görelim.

# Örnek: Kompleks veri dönüşümleri

# select() ile koşullu filtreleme
curl -s https://jsonplaceholder.typicode.com/users | jq -r 
  '.[] | select(.address.city == "Gwenborough") | .name'

# map() ile dönüşüm
curl -s https://jsonplaceholder.typicode.com/users | jq 
  '[.[] | {isim: .name, sehir: .address.city, domain: .email | split("@") | .[1]}]'

# group_by ve length ile istatistik
curl -s https://jsonplaceholder.typicode.com/posts | jq 
  'group_by(.userId) | map({kullanici: .[0].userId, post_sayisi: length}) | sort_by(.post_sayisi) | reverse'

# env değişkeni kullanmak
export ARADIGIM_SEHIR="Gwenborough"
curl -s https://jsonplaceholder.typicode.com/users | jq -r 
  --arg sehir "$ARADIGIM_SEHIR" 
  '.[] | select(.address.city == $sehir) | .name'

# null değerlerle başa çıkmak
curl -s https://jsonplaceholder.typicode.com/users | jq -r 
  '.[] | .company.name // "Şirket yok"'

# to_entries ile obje anahtarlarını işleme
echo '{"cpu": 85, "memory": 72, "disk": 45}' | jq -r 
  'to_entries[] | select(.value > 70) | "(.key): %(.value) - DİKKAT!"'

Sonuç

curl ve jq ikilisi, sistem yöneticisinin araç kutusunda olması gereken temel becerilerden biri haline geldi. Monitoring sistemlerinden veri çekmek, bulut sağlayıcı API’larını yönetmek, CI/CD pipeline’larında API entegrasyonları yapmak, webhook’ları test etmek ve çok daha fazlası için bu araçlara ihtiyaç duyuyorsunuz.

Bu yazıda öğrendiklerimizi özetlemek gerekirse:

  • curl ile GET, POST, PUT, DELETE istekleri yapabilir, authentication ekleyebilir, timeout ve retry mantığı kurabilirsiniz
  • jq ile JSON’ı parse edip istediğiniz alanları çekebilir, filtreler uygulayabilir, dönüşümler yapabilirsiniz
  • HTTP status code kontrolü yaparak scriptinizin sessizce başarısız olmasını önleyebilirsiniz
  • Retry ve exponential backoff ile production kalitesinde dayanıklı scriptler yazabilirsiniz
  • Pagination mantığını anlayarak büyük veri setlerini eksiksiz çekebilirsiniz

En önemli tavsiyem: token ve şifreleri asla scriptin içine yazmayın, environment variable veya secret management aracı kullanın. Ve jq‘yi öğrenmek için zaman ayırın, başta biraz garip gelen syntax kısa sürede çok doğal gelmeye başlıyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir