En 2012, Stephen Dolan en avait assez de parser du JSON avec grep, sed et awk comme un animal préhistorique. Il a écrit jq en deux semaines, comme Git et SSH, apparemment deux semaines c'est le temps qu'il faut à un développeur légèrement irrité pour créer un outil que des millions de personnes utiliseront pendant des décennies en n'utilisant que le point.
Parce que le point, tout le monde le connaît. jq . pour formater du JSON, et hop, on se sent malin. C'est l'équivalent de savoir allumer une tronçonneuse et se prendre pour un bûcheron.
jq est un processeur JSON en ligne de commande. Il lit du JSON, il le transforme, il le filtre, il le reformate. C'est tout. Mais "c'est tout" cache une profondeur qui surprend tout le monde la première fois qu'on va au-delà du point. Ce guide parle de ce qui vient après. Si vous êtes déjà à l'aise avec tout ce qui suit, fermez cet onglet et allez faire quelque chose d'utile.
Les bases qu'on croit maîtriser
Le point - formatter du JSON
echo '{"name":"radar","version":"1.2.0","env":"prod"}' | jq .
# {
# "name": "radar",
# "version": "1.2.0",
# "env": "prod"
# }
Le point seul reformate le JSON avec indentation. C'est la commande que 80% des gens connaissent et pensent être l'étendue de jq. C'est le bouton d'allumage de la tronçonneuse.
Accéder à un champ
echo '{"name":"radar","version":"1.2.0"}' | jq '.name'
# "radar"
# Sans les guillemets
echo '{"name":"radar"}' | jq -r '.name'
# radar
-r (raw output) supprime les guillemets autour des strings. Dans un script shell où vous assignez le résultat à une variable, vous voulez presque toujours -r. Si vous ne le mettez pas et que vous vous demandez pourquoi votre variable contient des guillemets, voilà pourquoi.
Accéder à un champ imbriqué
echo '{"meta":{"env":"prod","region":"eu-west-1"}}' | jq '.meta.region'
# "eu-west-1"
Accéder à un tableau
echo '["kubernetes","docker","terraform"]' | jq '.[0]'
# "kubernetes"
echo '["kubernetes","docker","terraform"]' | jq '.[]'
# "kubernetes"
# "docker"
# "terraform"
echo '["kubernetes","docker","terraform"]' | jq 'length'
# 3
Ce qui échoue silencieusement - et c'est le vrai problème
echo '{"name":"radar"}' | jq '.version'
# null
Si le champ n'existe pas, jq retourne null sans erreur et sans remords. Il ne vous dit rien. Il vous laisse propager ce null dans vos scripts jusqu'à ce que quelque chose explose 40 lignes plus loin dans un contexte complètement différent, pendant que vous vous demandez ce que vous avez fait de mal dans une vie antérieure.
jq -e retourne un code d'erreur non-zéro si le résultat est null ou false - utilisez-le dans les scripts :
version=$(echo '{"name":"radar"}' | jq -e -r '.version') || {
echo "Champ version manquant"
exit 1
}
Construire et transformer
Construire un nouvel objet
echo '{"name":"radar","version":"1.2.0","internal_id":"abc123"}' | \
jq '{nom: .name, ver: .version}'
# {
# "nom": "radar",
# "ver": "1.2.0"
# }
Vous choisissez les champs que vous voulez, vous les renommez, vous en ignorez d'autres. Utile pour extraire ce qui compte d'une réponse API qui vous renvoie 47 champs dont vous n'avez besoin que de trois.
Ajouter ou modifier un champ
echo '{"name":"radar","version":"1.2.0"}' | \
jq '. + {"env":"prod","deployed":true}'
Supprimer un champ
echo '{"name":"radar","secret":"abc123","version":"1.2.0"}' | \
jq 'del(.secret)'
del est particulièrement utile pour nettoyer des réponses API avant de les loguer. Parce que loguer des tokens en clair dans Elasticsearch c'est le genre de découverte qu'on fait un vendredi soir.
select - filtrer les éléments
select garde les éléments pour lesquels l'expression est vraie. C'est le WHERE de jq, mais sans le SQL autour pour vous donner l'illusion que vous faites quelque chose de sérieux.
# Filtrer les pods qui ne sont pas Running
kubectl get pods -o json | \
jq '.items[] | select(.status.phase != "Running") | .metadata.name'
# Filtrer les conteneurs avec plus de 2 restarts
kubectl get pods -o json | \
jq '.items[].status.containerStatuses[] |
select(.restartCount > 2) |
{name: .name, restarts: .restartCount}'
Les opérateurs de comparaison
select(.age > 30)
select(.name == "radar")
select(.name != "legacy")
select(.active)
select(.active | not)
select(.tags | length > 0)
select avec test pour les regex
kubectl get pods -o json | \
jq '.items[] | select(.metadata.name | test("radar")) | .metadata.name'
map - transformer chaque élément
map(expr) applique une transformation à chaque élément d'un tableau. C'est le SELECT de jq, et non ce n'est pas la même chose que select, oui c'est confusant, bienvenue dans jq.
kubectl get pods -o json | \
jq '.items | map(.metadata.name)'
# Transformer chaque élément
echo '[1,2,3,4,5]' | jq 'map(. * 2)'
# [2,4,6,8,10]
# Combiner map et select
kubectl get pods -o json | \
jq '.items | map(select(.status.phase == "Running")) | map(.metadata.name)'
group_by et sort_by - agréger et trier
# Grouper les pods par namespace
kubectl get pods -A -o json | \
jq '.items | group_by(.metadata.namespace) |
map({namespace: .[0].metadata.namespace, count: length})'
# Trouver le namespace avec le plus de pods
kubectl get pods -A -o json | \
jq '.items |
group_by(.metadata.namespace) |
map({ns: .[0].metadata.namespace, count: length}) |
sort_by(.count) |
reverse |
.[0]'
unique et unique_by
# Lister les images uniques dans le cluster
kubectl get pods -A -o json | \
jq '[.items[].spec.containers[].image] | unique | .[]' -r
Les cas d'usage DevOps concrets
kubectl - l'usage le plus courant
# Tous les pods qui ne sont pas Running
kubectl get pods -A -o json | jq -r '
.items[] |
select(.status.phase != "Running") |
[.metadata.namespace, .metadata.name, .status.phase] |
@tsv'
# Pods en CrashLoopBackOff - parce que ça arrive toujours à 23h
kubectl get pods -A -o json | jq -r '
.items[] |
select(.status.containerStatuses[]?.state.waiting.reason == "CrashLoopBackOff") |
"\(.metadata.namespace)/\(.metadata.name)"'
# Images utilisées dans le cluster
kubectl get pods -A -o json | jq -r '
.items[].spec.containers[].image' | sort | uniq -c | sort -rn
docker inspect
# Variables d'environnement d'un conteneur
# Utile pour vérifier que votre secret n'est pas "password123"
docker inspect mon-conteneur | \
jq '.[0].Config.Env[]' -r
# Ports exposés
docker inspect mon-conteneur | \
jq '.[0].NetworkSettings.Ports'
Logs JSON
# Filtrer les erreurs
cat app.log | jq 'select(.level == "error")'
# Compter les erreurs par type
# Pour le post-mortem du lundi matin
cat app.log | jq -r 'select(.level == "error") | .error_code' | \
sort | uniq -c | sort -rn
# Erreurs des 5 dernières minutes
cat app.log | jq --arg since "$(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S)" \
'select(.level == "error" and .timestamp > $since)'
Réponses API
# Construire une commande shell depuis une réponse API
# Parce que parfois c'est vraiment la bonne solution
curl -s "https://api.example.com/servers" | \
jq -r '.servers[] | "ssh \(.user)@\(.ip) -p \(.port)"'
Combiner avec curl - le duo parfait
# Extraire un token et l'utiliser immédiatement
TOKEN=$(curl -s -X POST "https://auth.example.com/token" \
-d '{"user":"cyril","pass":"..."}' | jq -r '.access_token')
curl -H "Authorization: Bearer $TOKEN" \
"https://api.example.com/data" | jq '.results[]'
# Pipeline complet : fetch → filter → retry les déploiements en échec
# Trois commandes qui font ce qu'une interface graphique ferait en 15 clics
curl -s "https://api.example.com/deployments" | \
jq -r '.[] | select(.status == "failed") | .id' | \
xargs -I{} curl -s -X POST "https://api.example.com/deployments/{}/retry"
--arg et --argjson - injecter des variables shell proprement
Le piège classique : essayer d'interpoler une variable shell dans un filtre jq comme un cowboy. Ça marche jusqu'au jour où la variable contient un guillemet, un espace, ou pire - du JSON. Ce jour-là vous découvrirez l'injection dans vos propres scripts, ce qui est une expérience humiliante et formatrice.
# NE FAITES PAS ÇA
ENV="prod"
jq "select(.env == \"$ENV\")"
# Faites ça
ENV="prod"
jq --arg env "$ENV" 'select(.env == $env)'
--arg injecte une string. --argjson injecte une valeur JSON :
# Injecter un nombre
jq --argjson threshold 5 'select(.restartCount > $threshold)'
# Injecter la date courante
jq --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'. + {deployed_at: $date}'
--slurp - lire plusieurs objets JSON comme un tableau
# Compter les erreurs dans plusieurs fichiers de logs
cat logs/*.json | jq -s 'map(select(.level == "error")) | length'
@formats - exporter dans d'autres formats
# TSV - pour piper vers awk, sort, ou votre collègue qui préfère Excel
kubectl get pods -o json | \
jq -r '.items[] | [.metadata.name, .status.phase, .spec.nodeName] | @tsv'
# Décoder un secret Kubernetes
# Parce que base64 c'est pas du chiffrement mais tout le monde fait semblant
kubectl get secret mon-secret -o json | \
jq -r '.data.password | @base64d'
Les pièges classiques
Les nulls qui se propagent silencieusement
echo '{"meta":{}}' | jq '.meta.version.tag'
# null
Utilisez // pour définir une valeur par défaut :
jq '.meta.version.tag // "unknown"'
jq '.tags // [] | length'
Les types qui ne correspondent pas
echo '{"count":"5"}' | jq '.count > 3'
# false - "5" string n'est pas > 3 number
# jq ne convertit pas silencieusement, contrairement à JavaScript
# ce qui est une qualité, même si ça surprend
jq '.count | tonumber | . > 3'
# true
? - ignorer les erreurs de type
kubectl get all -o json | jq '.items[].metadata.name?'
Les performances sur les gros fichiers
jq charge tout en mémoire. Sur un fichier de logs de plusieurs gigaoctets, votre serveur va regarder le plafond d'un air pensif avant de rendre l'âme. Pour les gros volumes, --stream traite le JSON en streaming sans tout charger :
jq --stream 'select(.[0][-1] == "level" and .[1] == "error")' huge-logs.json
C'est plus verbeux, moins lisible, et vous allez le détester. Mais votre serveur sera encore là le lendemain matin, ce qui est généralement l'objectif.
Débugger un filtre complexe
Ajoutez | debug n'importe où dans le pipe - jq affiche la valeur courante sur stderr sans interrompre le flux. C'est l'équivalent du console.log du pauvre, mais assumé, documenté, et disponible sans installer Node.js.
jq '.items[] | debug | select(.status.phase == "Running")'
À retenir
| Commande | Pour quoi |
|---|---|
jq . | Formater du JSON |
jq -r '.field' | Extraire sans guillemets |
jq -e '.field' | Erreur si null |
jq 'select(.x == "val")' | Filtrer |
jq '.items | map(.name)' | Transformer chaque élément |
jq 'sort_by(.count) | reverse' | Trier |
jq --arg k "$VAR" 'select(.x == $k)' | Injecter une variable shell |
jq -s '.' | Plusieurs objets → tableau |
jq 'del(.secret)' | Supprimer un champ |
jq '.field // "default"' | Valeur par défaut si null |
FAQ
Quelle différence entre `jq '.[]'` et `jq '.items[]'` ?
.[] itère sur tous les éléments d'un tableau ou toutes les valeurs d'un objet. .items[] itère sur le champ items spécifiquement. Si votre JSON est directement un tableau, .[]. Si c'est un objet avec un champ tableau, .monchamp[]. Si vous confondez les deux, jq vous le dira d'une manière peu aimable.
Comment débugger un filtre jq complexe ?
| debug n'importe où dans le pipe. jq affiche la valeur courante sur stderr. Ajoutez-en plusieurs, retirez-les quand ça marche, exactement comme vos console.log que vous commitez par accident.
jq vs python pour parser du JSON en shell ?
jq pour les opérations simples à moyennes. Python pour les transformations complexes qui nécessitent de la logique. La frontière est floue - quand votre filtre jq dépasse 3 pipes et que vous n'arrivez plus à le lire vous-même, c'est le moment de passer à Python et d'arrêter de vous battre contre l'outil.
Comment traiter du NDJSON ?
cat file.ndjson | jq '.' fonctionne directement - jq traite chaque objet JSON séparé par des newlines. Pas besoin de --slurp sauf si vous voulez les agréger dans un tableau.
`//` vs `?//` ?
// retourne la valeur de droite si la gauche est null ou false. ?// retourne la valeur de droite si la gauche produit une erreur de type. Pour les champs manquants, // suffit. Pour les erreurs de type, ?//. Si vous ne savez pas lequel utiliser, essayez // d'abord et ?// si ça casse encore.
Pour aller plus loin
- jq manual - la référence complète, étonnamment bien écrite
- jqplay - tester des filtres dans le navigateur, indispensable pour les filtres complexes
- jq cookbook - recettes pour les cas courants
man jq- plus court que le manuel en ligne, bon pour une révision rapide avant un incident