yq — Selection
select() — Conditional Filtering
select(condition) keeps values where the condition is true and discards the rest.
This is the primary filtering mechanism in yq/jq.
yq '.asciidoc.attributes | to_entries[] | select(.value | test("10\\.50\\.1\\."))' docs/antora.yml | head -20
# Output (each entry is a key/value pair):
# key: ise-ip
# value: "10.50.1.20"
# ---
# key: vault-ip
# value: "10.50.1.60"
# ...
to_entries converts a map {k: v} into an array of {key: k, value: v} objects.
The [] iterator produces each entry individually so select() can filter.
test() — Regex Matching
yq -r '.asciidoc.attributes | to_entries[] | select(.key | test("^port-")) | .key' docs/antora.yml
# Output:
# port-https
# port-ssh
# port-kerberos
# port-ldap
# port-ldaps
# port-gc
# port-dns
# port-dhcp-server
# port-dhcp-client
# port-ntp
# port-smtp
# port-submission
# port-imaps
test("regex") returns true/false without capturing.
Anchors work: ^ for start, $ for end.
yq -r '.asciidoc.attributes | to_entries[] | select(.key | test("^(ise|vault)-")) | "\(.key): \(.value)"' docs/antora.yml
# Output:
# ise-hostname: ise-01.inside.domusdigitalis.dev
# ise-ip: 10.50.1.20
# ise-ers-port: 9060
# ise-openapi-port: 443
# ise-mnt-port: 443
# ise-dc-port: 2484
# ise-pxgrid-port: 8910
# vault-hostname: vault-01.inside.domusdigitalis.dev
# vault-01-hostname: vault-01.inside.domusdigitalis.dev
# vault-ip: 10.50.1.60
# vault-01-ip: 10.50.1.60
# vault-port: 8200
# vault-pki-path: pki_int
# vault-ssh-path: ssh
# vault-kv-path: kv
yq -r '.asciidoc.attributes | to_entries[] | select(.key | test("vyos"; "i")) | "\(.key): \(.value)"' docs/antora.yml
# Output:
# vyos-vip: 10.50.1.1
# vyos-01-hostname: vyos-01.inside.domusdigitalis.dev
# vyos-01-ip: 10.50.1.2
# vyos-02-hostname: vyos-02.inside.domusdigitalis.dev
# vyos-02-ip: 10.50.1.3
The second argument to test() is a flags string.
"i" is case-insensitive; "x" is extended regex.
map(select()) — Array Filtering
yq '.asciidoc.attributes | to_entries | map(select(.key | test("^ws-")))' docs/antora.yml
# Output:
# - key: ws-razer-hostname
# value: modestus-razer.inside.domusdigitalis.dev
# - key: ws-razer-short
# value: modestus-razer
The difference: to_entries[] with select() streams individual matches.
to_entries | map(select()) collects matches back into an array.
Use map() when you need the result as a list for further processing.
yq '.asciidoc.attributes | to_entries | map(select(.key | test("^port-"))) | length' docs/antora.yml
# Output: 13
has() in Selection
cat <<'YAML' > /tmp/services.yml
services:
ise:
ip: 10.50.1.20
port: 443
description: Identity Services Engine
vault:
ip: 10.50.1.60
port: 8200
dns:
ip: 10.50.1.90
port: 53
description: BIND9 resolver
YAML
yq '.services | to_entries[] | select(.value | has("description")) | .key' /tmp/services.yml
# Output:
# ise
# dns
Negation — select(… | not)
yq '.asciidoc.attributes | to_entries | map(select(.key | test("^(port|ise|vault|ws|vyos|ad|kvm|nas|bt|mail|pfsense|wlc|keycloak|k3s)-") | not)) | length' docs/antora.yml
# Output: ~60 (non-infrastructure attributes)
| not inverts the boolean.
This is the yq equivalent of grep -v.
yq -r '.asciidoc.attributes | to_entries[] | select(.key | test("-ip$|-hostname$|-port$|-mac$") | not) | select(.key | test("^(stats|focus|origin|level|type|status|nav|current)")) | "\(.key): \(.value)"' docs/antora.yml | head -10
# Filters to metadata-class attributes only
Combining Conditions
yq -r '.asciidoc.attributes | to_entries[] | select((.key | test("^port-")) and (.value | test("^[0-9]{2}$"))) | "\(.key): \(.value)"' docs/antora.yml
# Output: ports with 2-digit values (e.g., port-dns: 53, port-ssh: 22, port-ntp: 25)
yq -r '.asciidoc.attributes | to_entries[] | select((.key | test("^cert-")) or (.key | test("^key-"))) | "\(.key): \(.value)"' docs/antora.yml
# Output:
# cert-dir: /etc/ssl/certs
# key-dir: /etc/ssl/private
String Contains (alternative to test)
yq -r '.asciidoc.attributes | to_entries[] | select(.value | type == "!!str" and contains("domusdigitalis")) | .key' docs/antora.yml | head -10
# Output: all attributes containing the domain string
contains() does literal matching — no regex.
Faster than test() when you do not need pattern matching.