npx skillsauth add jcsaaddupuy/badrobots yqInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
yq is a lightweight and portable YAML/JSON/XML processor. It's like jq but for YAML files. Use it for parsing, querying, and manipulating YAML configuration files in shell scripts.
Important: This guide covers mikefarah/yq v4+ (the Go implementation), not the older Python yq wrapper.
Advantages:
-iCommon Use Cases:
# Check version
yq --version
# Should output: yq (https://github.com/mikefarah/yq/) version v4.x.x
# NOT the Python yq wrapper which shows: yq 3.x.x
yq [options] 'expression' [file...]
Common Options:
-i / --inplace - Edit file in place-o FORMAT / --output-format FORMAT - Output format (yaml/json/xml/props)-P / --prettyPrint - Pretty print (default for YAML)-I INDENT / --indent INDENT - Set indent (default: 2)-r / --raw-output - Output raw strings (no quotes)-n / --null-input - Don't read input-e / --exit-status - Exit with status based on output# From file
yq '.name' config.yaml
# From stdin
echo 'name: Alice' | yq '.name'
# From command output
kubectl get pod mypod -o yaml | yq '.spec.containers[0].image'
# Multiple files
yq '.version' app1.yaml app2.yaml
# Simple field
echo 'name: Alice' | yq '.name'
# Output: Alice
# Nested field
echo 'user: {name: Alice, age: 30}' | yq '.user.name'
# Output: Alice
# Array element
echo 'items: [a, b, c]' | yq '.items[0]'
# Output: a
# All array elements
echo 'items: [a, b, c]' | yq '.items[]'
# Output: a b c (three separate outputs)
# With quotes (default YAML)
echo 'name: Alice' | yq '.name'
# Output: Alice
# Raw output (like jq -r)
echo 'name: Alice' | yq -r '.name'
# Output: Alice
# Useful for shell variables
VERSION=$(yq -r '.version' package.yaml)
# Update field
echo 'name: Alice' | yq '.name = "Bob"'
# Output: name: Bob
# Update nested field
echo 'user: {name: Alice}' | yq '.user.age = 30'
# Output:
# user:
# name: Alice
# age: 30
# In-place file edit
yq -i '.version = "2.0.0"' config.yaml
# Add new field
echo 'name: Alice' | yq '.age = 30'
# Output:
# name: Alice
# age: 30
# Delete field
echo 'name: Alice
age: 30' | yq 'del(.age)'
# Output: name: Alice
# Delete array element
echo 'items: [a, b, c]' | yq 'del(.items[1])'
# Output: items: [a, c]
# Append to array
echo 'items: [a, b]' | yq '.items += ["c"]'
# Output: items: [a, b, c]
# Prepend to array
echo 'items: [b, c]' | yq '.items = ["a"] + .items'
# Output: items: [a, b, c]
# Get array length
echo 'items: [a, b, c]' | yq '.items | length'
# Output: 3
# Filter array
echo 'users:
- name: Alice
age: 30
- name: Bob
age: 20' | yq '.users[] | select(.age > 25)'
# Output:
# name: Alice
# age: 30
# Merge two YAML files
yq eval-all '. as $item ireduce ({}; . * $item)' file1.yaml file2.yaml
# Simpler: merge operator
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
# Merge with priority (file2 overrides file1)
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
# ❌ Python/grep way
grep "version:" config.yaml | cut -d: -f2 | tr -d ' '
# ✅ yq way
yq -r '.version' config.yaml
# Exit status 0 if field exists and is non-null
yq -e '.database.host' config.yaml > /dev/null && echo "exists"
# Get with default value
yq '.database.host // "localhost"' config.yaml
# Update version in package.yaml
yq -i '.version = "2.0.0"' package.yaml
# Update nested value
yq -i '.database.password = env(DB_PASSWORD)' config.yaml
# Update multiple fields
yq -i '.version = "2.0.0" | .updated = now' config.yaml
# Get all container names from docker-compose.yml
yq '.services | keys | .[]' docker-compose.yml
# Get all image names
yq '.services[].image' docker-compose.yml
# With processing
yq '.services | to_entries | .[] | .key + ":" + .value.image' docker-compose.yml
# Select services with specific image
yq '.services | to_entries | .[] | select(.value.image == "nginx")' docker-compose.yml
# Get services with ports exposed
yq '.services | to_entries | .[] | select(.value.ports) | .key' docker-compose.yml
# Filter array by condition
yq '.users[] | select(.active == true)' users.yaml
# YAML to JSON
yq -o json '.' config.yaml
# Pretty JSON
yq -o json -P '.' config.yaml
# JSON to YAML
yq -P '.' config.json
# Compact JSON (no pretty print)
yq -o json -I=0 '.' config.yaml
# Use env variable in expression
yq '.database.host = env(DB_HOST)' config.yaml
# Set from env var
DB_PASSWORD="secret123" yq -i '.database.password = env(DB_PASSWORD)' config.yaml
# Multiple env vars
yq '.host = env(HOST) | .port = env(PORT)' config.yaml
# docker-compose.yml
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "8080:80"
db:
image: postgres:14
environment:
POSTGRES_PASSWORD: secret
# Get service names
yq '.services | keys | .[]' docker-compose.yml
# Output: web db
# Get web service image
yq '.services.web.image' docker-compose.yml
# Output: nginx:latest
# Get all images
yq '.services[].image' docker-compose.yml
# Output: nginx:latest postgres:14
# Get exposed port
yq '.services.web.ports[0]' docker-compose.yml
# Output: "8080:80"
# Get database password
yq '.services.db.environment.POSTGRES_PASSWORD' docker-compose.yml
# Output: secret
# Update image version
yq -i '.services.web.image = "nginx:alpine"' docker-compose.yml
# Add new service
yq -i '.services.redis = {"image": "redis:latest"}' docker-compose.yml
# Add environment variable
yq -i '.services.web.environment.API_KEY = "xyz123"' docker-compose.yml
# Add port mapping
yq -i '.services.web.ports += ["443:443"]' docker-compose.yml
# Remove service
yq -i 'del(.services.redis)' docker-compose.yml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
# Get deployment name
yq '.metadata.name' deployment.yaml
# Output: nginx-deployment
# Get replica count
yq '.spec.replicas' deployment.yaml
# Output: 3
# Get container image
yq '.spec.template.spec.containers[0].image' deployment.yaml
# Output: nginx:1.14.2
# Get all container names
yq '.spec.template.spec.containers[].name' deployment.yaml
# Output: nginx
# Get container port
yq '.spec.template.spec.containers[0].ports[0].containerPort' deployment.yaml
# Output: 80
# Update image version
yq -i '.spec.template.spec.containers[0].image = "nginx:1.21"' deployment.yaml
# Scale replicas
yq -i '.spec.replicas = 5' deployment.yaml
# Add resource limits
yq -i '.spec.template.spec.containers[0].resources.limits.memory = "256Mi"' deployment.yaml
# Add label
yq -i '.metadata.labels.environment = "production"' deployment.yaml
# Add environment variable to container
yq -i '.spec.template.spec.containers[0].env += [{"name": "DEBUG", "value": "true"}]' deployment.yaml
# .gitlab-ci.yml
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker build -t myapp .
test:
stage: test
script:
- pytest
deploy:
stage: deploy
script:
- kubectl apply -f deployment.yaml
only:
- main
# Get all stages
yq '.stages[]' .gitlab-ci.yml
# Output: build test deploy
# Get build script
yq '.build.script[]' .gitlab-ci.yml
# Output: docker build -t myapp .
# Get job names
yq 'keys | .[] | select(. != "stages")' .gitlab-ci.yml
# Output: build test deploy
# Get jobs for specific stage
yq '. | to_entries | .[] | select(.value.stage? == "test") | .key' .gitlab-ci.yml
# Output: test
# Get deploy conditions
yq '.deploy.only[]' .gitlab-ci.yml
# Output: main
# Add new stage
yq -i '.stages += ["security"]' .gitlab-ci.yml
# Update script
yq -i '.build.script[0] = "docker build -t myapp:latest ."' .gitlab-ci.yml
# Add new job
yq -i '.security = {"stage": "security", "script": ["trivy scan"]}' .gitlab-ci.yml
# Add tags to job
yq -i '.build.tags = ["docker", "linux"]' .gitlab-ci.yml
# Add cache configuration
yq -i '.build.cache = {"paths": ["node_modules/"]}' .gitlab-ci.yml
# Conditionals (yq v4+ uses select)
# Select if condition is true
echo 'status: active' | yq 'select(.status == "active") | "running"'
# Output: running
# Alternative/default with //
echo 'value: null' | yq '.value // "default"'
# Output: default
# Boolean check
echo 'enabled: true' | yq '.enabled == true'
# Output: true
# Multiple conditions with and/or
echo 'env: prod
replicas: 5' | yq 'select(.env == "prod" and .replicas > 3) | "scaled"'
# Output: scaled
# Map over array
echo 'items: [1, 2, 3]' | yq '.items | map(. * 2)'
# Output: [2, 4, 6]
# Map with select
echo 'users:
- name: Alice
age: 30
- name: Bob
age: 20' | yq '[.users[] | select(.age > 25) | .name]'
# Output: [Alice]
# Transform object keys
yq '.services | to_entries | map({"service": .key, "image": .value.image})' docker-compose.yml
# String concatenation
echo 'first: Alice
last: Smith' | yq '.first + " " + .last'
# Output: Alice Smith
# String interpolation
echo 'name: Alice' | yq '"Hello, \(.name)!"'
# Output: Hello, Alice!
# Split string
echo 'path: /usr/local/bin' | yq '.path | split("/")'
# Output: ["", "usr", "local", "bin"]
# Join array
echo 'parts: [usr, local, bin]' | yq '.parts | join("/")'
# Output: usr/local/bin
# String contains
echo 'url: https://example.com' | yq '.url | contains("https")'
# Output: true
# Regex test
echo 'email: [email protected]' | yq '.email | test("@")'
# Output: true
# Regex match
echo 'version: v1.2.3' | yq '.version | capture("v(?<major>\\d+)\\.(?<minor>\\d+)")'
# Output: {major: "1", minor: "2"}
# Sort array
echo 'items: [3, 1, 2]' | yq '.items | sort'
# Output: [1, 2, 3]
# Sort by field
echo 'users:
- name: Bob
age: 30
- name: Alice
age: 25' | yq '.users | sort_by(.name)'
# Group by field
echo 'users:
- name: Alice
dept: IT
- name: Bob
dept: HR
- name: Carol
dept: IT' | yq 'group_by(.dept)'
# YAML files can have multiple documents separated by ---
echo '---
name: Alice
---
name: Bob' | yq '.'
# Select specific document (0-indexed)
echo '---
name: Alice
---
name: Bob' | yq 'select(documentIndex == 0)'
# Output: name: Alice
# Process all documents
echo '---
name: Alice
---
name: Bob' | yq '.name'
# Output: Alice Bob
# Extract value
VERSION=$(yq -r '.version' config.yaml)
echo "Version: $VERSION"
# With default
DB_HOST=$(yq -r '.database.host // "localhost"' config.yaml)
# Check if empty
IMAGE=$(yq -r '.services.web.image' docker-compose.yml)
if [ -z "$IMAGE" ]; then
echo "No image specified"
fi
# yq -e exits with 0 if output is not null/false
if yq -e '.production' config.yaml > /dev/null 2>&1; then
echo "Production config found"
fi
# Check if field exists
if yq -e '.database.password' config.yaml > /dev/null 2>&1; then
echo "Database password is set"
else
echo "Warning: No database password configured"
fi
# Check if array is not empty
if yq -e '.services | length > 0' docker-compose.yml > /dev/null 2>&1; then
echo "Services defined"
fi
# Get all service names and process
yq -r '.services | keys | .[]' docker-compose.yml | while read -r service; do
echo "Processing service: $service"
image=$(yq -r ".services.$service.image" docker-compose.yml)
echo " Image: $image"
done
SERVICE_NAME="web"
IMAGE_NAME="nginx:alpine"
PORT="8080"
# Create new YAML
yq -n ".services.$SERVICE_NAME.image = \"$IMAGE_NAME\" |
.services.$SERVICE_NAME.ports = [\"$PORT:80\"]"
# Output:
# services:
# web:
# image: nginx:alpine
# ports:
# - "8080:80"
# Update existing file with variables
yq -i ".services.$SERVICE_NAME.image = \"$IMAGE_NAME\"" docker-compose.yml
# Read into array
mapfile -t IMAGES < <(yq -r '.services[].image' docker-compose.yml)
echo "Found ${#IMAGES[@]} images: ${IMAGES[@]}"
# Read key-value pairs
while IFS=$'\t' read -r service image; do
echo "Service $service uses image $image"
done < <(yq -r '.services | to_entries | .[] | [.key, .value.image] | @tsv' docker-compose.yml)
# Update Docker image in GitLab CI
yq -i '.build.script[0] = "docker build -t myapp:v2 ."' .gitlab-ci.yml
# Add deployment environment
yq -i '.deploy.environment.name = "production"' .gitlab-ci.yml
# Update runner tags
yq -i '.test.tags = ["docker", "linux"]' .gitlab-ci.yml
# Scale deployment
yq -i '.spec.replicas = 5' deployment.yaml
# Update image across multiple deployments
for file in deployments/*.yaml; do
yq -i '.spec.template.spec.containers[0].image = "myapp:v2.0"' "$file"
done
# Add sidecar container
yq -i '.spec.template.spec.containers += [{"name": "sidecar", "image": "envoy:latest"}]' deployment.yaml
# Update all service images
for service in $(yq -r '.services | keys | .[]' docker-compose.yml); do
current_image=$(yq -r ".services.$service.image" docker-compose.yml)
new_image="${current_image%:*}:latest"
yq -i ".services.$service.image = \"$new_image\"" docker-compose.yml
done
# Add health check to service
yq -i '.services.web.healthcheck = {
"test": ["CMD", "curl", "-f", "http://localhost"],
"interval": "30s",
"timeout": "10s",
"retries": 3
}' docker-compose.yml
# Merge environment-specific configs
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' \
config/base.yaml config/production.yaml > config/final.yaml
# Extract secrets for validation
yq -r '.. | select(tag == "!!str") | select(. == "*secret*" or . == "*password*")' config.yaml
# Validate required fields
required_fields=("database.host" "database.port" "api.key")
for field in "${required_fields[@]}"; do
if ! yq -e ".$field" config.yaml > /dev/null 2>&1; then
echo "Error: Required field $field is missing"
exit 1
fi
done
# Convert Kubernetes deployment to simple JSON summary
yq -o json '{
"name": .metadata.name,
"replicas": .spec.replicas,
"image": .spec.template.spec.containers[0].image
}' deployment.yaml
# Create table from YAML
echo "Service | Image | Ports"
echo "--------|-------|------"
yq -r '.services | to_entries | .[] |
"\(.key) | \(.value.image) | \(.value.ports[0] // "none")"' docker-compose.yml
# ❌ Bad - includes YAML formatting
VERSION=$(yq '.version' config.yaml)
echo "$VERSION" # Could have extra formatting
# ✅ Good - raw output
VERSION=$(yq -r '.version' config.yaml)
echo "$VERSION" # Clean string
# ❌ Risky - no backup
yq -i '.version = "2.0"' config.yaml
# ✅ Good - backup first
cp config.yaml config.yaml.bak
yq -i '.version = "2.0"' config.yaml
# Or use shell backup
cp config.yaml{,.bak} && yq -i '.version = "2.0"' config.yaml
# ❌ Bad - will error if field doesn't exist
yq '.database.password' config.yaml
# ✅ Good - use // for default
yq '.database.password // "not-set"' config.yaml
# ✅ Good - use -e for existence check
if yq -e '.database.password' config.yaml > /dev/null 2>&1; then
echo "Password is configured"
fi
# Field with dots or special chars
yq '.["api.key"]' config.yaml
# Field with spaces
yq '.["first name"]' config.yaml
# Or use bracket notation consistently
yq '.services["my-app"].image' docker-compose.yml
# yq preserves comments by default
# This is a key feature over other YAML parsers
# Original file with comments:
# version: "3.8" # Docker Compose version
# services:
# web:
# image: nginx # Web server
# After modification, comments are preserved:
yq -i '.services.web.ports = ["80:80"]' docker-compose.yml
# Check if valid YAML
if yq -e '.' config.yaml > /dev/null 2>&1; then
echo "Valid YAML"
else
echo "Invalid YAML"
exit 1
fi
# Or use eval (more strict)
yq eval '.' config.yaml > /dev/null 2>&1 || { echo "Invalid YAML"; exit 1; }
# Test expression on simple data
echo 'name: Alice' | yq '.name'
# Pretty print to see structure
yq -P '.' config.yaml
# Output as JSON for clarity
yq -o json '.' config.yaml
"Error: bad file"
# Problem: File doesn't exist or is not valid YAML
yq '.field' nonexistent.yaml
# Solution: Check file exists and is valid
test -f config.yaml && yq -e '.' config.yaml > /dev/null
"Error: Pipe is not a valid function"
# Problem: Wrong yq version (Python yq)
# Check version
yq --version
# Should show: yq (https://github.com/mikefarah/yq/)
# NOT: yq 3.x.x (Python)
"Document is empty"
# Problem: Empty YAML file
# Solution: Check file content or create default
if [ ! -s config.yaml ]; then
echo '{}' > config.yaml
fi
| Task | Python/PyYAML | yq |
|------|---------------|-----|
| Read field | python3 -c "import yaml; print(yaml.safe_load(open('f.yaml'))['name'])" | yq -r '.name' f.yaml |
| Update field | Python script | yq -i '.name = "Bob"' f.yaml |
| Preserve comments | ❌ Lost | ✅ Preserved |
| Speed | ~50ms | ~5ms |
| Dependencies | Python + PyYAML | yq binary |
| YAML to JSON | Python script | yq -o json '.' f.yaml |
# Read value
yq -r '.field' file.yaml
# Update value
yq -i '.field = "value"' file.yaml
# Add field
yq -i '.newfield = "value"' file.yaml
# Delete field
yq -i 'del(.field)' file.yaml
# Array operations
yq '.array[]' file.yaml # All elements
yq '.array[0]' file.yaml # First element
yq -i '.array += ["item"]' file.yaml # Append
# Filter
yq '.items[] | select(.active == true)' file.yaml
# Convert
yq -o json '.' file.yaml # YAML to JSON
yq -P '.' file.json # JSON to YAML
# Merge files
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' f1.yaml f2.yaml
# With default
yq '.field // "default"' file.yaml
# Check existence
yq -e '.field' file.yaml > /dev/null
# Count
yq '.array | length' file.yaml
# Sort
yq '.items | sort_by(.name)' file.yaml
# Docker Compose
yq '.services | keys | .[]' docker-compose.yml # Service names
yq '.services.web.image' docker-compose.yml # Service image
yq -i '.services.web.image = "nginx:alpine"' docker-compose.yml # Update image
# Kubernetes
yq '.metadata.name' deployment.yaml # Deployment name
yq '.spec.replicas' deployment.yaml # Replica count
yq -i '.spec.replicas = 5' deployment.yaml # Scale
yq '.spec.template.spec.containers[0].image' deployment.yaml # Container image
# GitLab CI
yq '.stages[]' .gitlab-ci.yml # All stages
yq '.build.script[]' .gitlab-ci.yml # Build scripts
yq -i '.build.tags = ["docker"]' .gitlab-ci.yml # Add tags
# Generic
yq -r '.database.host // "localhost"' config.yaml # With default
yq -i '.version = env(VERSION)' config.yaml # From env var
yq '.users[] | select(.active)' users.yaml # Filter array
Related skills:
development
DuckDB patterns for JSON/JSONL analysis, array unnesting, and common gotchas. Use when querying JSON files, nested data, or encountering "UNNEST not supported here" errors.
development
Mealie recipe manager API: recipes, shopping lists, meal plans. Requires MEALIE_BASE_URL and MEALIE_API_KEY.
business
TimeWarrior time tracking: start/stop intervals, query durations by tag or issue, compute totals for issue tracker time reporting
development
Bookmark manager for saving, searching, and annotating web content. Use when: (1) saving a webpage for later reference, (2) searching previously saved bookmarks, (3) adding highlights/annotations to saved content, (4) user asks to 'bookmark this' or 'save this article'. Requires READECK_BASE_URL and READECK_API_KEY environment variables.