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.
