Recipes
Start from the task. Every example uses the released ysh file and ordinary YAML input.
Read one value
ysh '.server.port' config.yml
Scalar output is unquoted by default; -r states that intent explicitly:
host=$(ysh -r '.server.host' config.yml)
Select matching entries
ysh '.services[] | select(.enabled) | .name' config.yml
ysh '.services | filter(.port >= 8000) | map(.name)' config.yml
Add -e when an empty, null, or false result should fail a shell script:
ysh -e '.services[] | select(.name == "api")' config.yml >/dev/null
Change a file safely
Preview the exact write first:
ysh --diff '.image.tag = "stable"' deploy.yml
Exit status is 0 when no change is needed, 1 when the file would change, and 2 for an invalid query or input. Use --check when a quiet answer is enough. Then write the same update:
ysh -i '.image.tag = "stable"' deploy.yml
-i refuses symlinks and only replaces the original after every document succeeds.
When preserving comments and layout is mandatory, make it executable policy:
ysh --preserve-only --diff '.image.tag = "stable"' deploy.yml
ysh --preserve-only -i '.image.tag = "stable"' deploy.yml
The command fails instead of falling back to regenerated YAML. See documents and file edits for the complete preservation and write behavior.
Apply the same checked edit to several files:
ysh --check '.image.tag = "stable"' services/*.yml
ysh --diff '.image.tag = "stable"' services/*.yml
ysh --explain=json -i '.image.tag = "stable"' services/*.yml 2>changes.jsonl
Every candidate is prepared before the first replacement. The JSON Lines report records paths and decisions without recording changed values.
Require an invariant before changing anything:
query='with(.kind; select(. == "Deployment") or error("expected Deployment")) | .spec.replicas = 3'
ysh --diff "$query" deploy.yml
ysh -i "$query" deploy.yml
error(MESSAGE) aborts the complete command. Boolean and and or short-circuit, so the error runs only when its guard fails.
Set values from the environment
strenv always creates a string. env parses the variable as YAML.
IMAGE_TAG=stable ysh -i '.image.tag = strenv(IMAGE_TAG)' deploy.yml
LIMITS='{cpu: 2, memory: 512Mi}' ysh '.resources = env(LIMITS)' deploy.yml
Disable environment operators when expressions are untrusted:
ysh --security-disable-env-ops '.services | length' config.yml
Decode embedded configuration
ysh '.payload | from_json | .services[] | select(.enabled)' config.yml
ysh '.embedded | from_yaml | .image.tag' config.yml
ysh '.rows | @csv' config.yml
The same expression surface supports JSON, YAML, TOML, INI, XML, properties, CSV, TSV, Base64, URI, and shell text. See the query guide for the exact forms.
Validate and patch configuration
Gate a document with a local schema:
ysh --schema service.schema.json '.' service.yml
Preview and apply the same RFC 6902 patch while preserving supported YAML source details:
ysh --apply-patch change.json --preserve-only --diff service.yml
ysh --apply-patch change.json --preserve-only -i service.yml
Generate the patch first when you have a desired document:
ysh --json --generate-patch desired.yml '.' current.yml > change.json
See validate, patch, and convert for Merge Patch, structured schema errors, and direct TOML/INI/XML conversion.
Load local configuration
ysh -n 'load("defaults.yml") * load("production.yml")'
ysh -n 'load_props("application.properties")'
Use --security-disable-file-ops when the query must not choose local paths. Loaded content shares the normal input-byte ceiling.
Merge defaults with an override
ysh eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' defaults.yml production.yml
The right mapping wins at scalar leaves.
Choose how arrays and keys merge:
ysh -n --json '{ports: [80]} *+ {ports: [443]}' # append arrays
ysh -n --json '{api: {port: 80}} *? {api: {port: 81}, web: {port: 80}}' # existing keys only
ysh -n --json '{api: {port: 80}} *n {api: {port: 81}, web: {port: 80}}' # new keys only
Use *d to merge array entries by index.
Share data across files
Writable eval-all evaluates once over every input, so one file can supply a value used to update the others:
query='select(fileIndex == 0).version as $version | select(fileIndex > 0).release.version = $version'
ysh eval-all --check "$query" release.yml services/*.yml
ysh eval-all -i "$query" release.yml services/*.yml
All changed candidates are prepared before writing. Unchanged files are not replaced.
Work across every document
ysh --all-documents '[documentIndex, .metadata.name]' stream.yml
ysh --document 2 '.spec' stream.yml
Build new YAML
ysh -n -o=yaml '{name: "api", enabled: true, ports: [8080, 8443]}'
Inspect a strange document
ysh --type '.release.created' config.yml
ysh --tag '.widget' config.yml
ysh --line '.services[0].port' config.yml
ysh --events config.yml
ysh --ast config.yml
Use the event stream to see what the parser recognized, then the AST to inspect resolved node identity, tags, anchors, aliases, and merges.
Bound hostile input
ysh \
--max-input-bytes 1048576 \
--max-nodes 20000 \
--max-depth 80 \
--security-disable-env-ops \
'.metadata.name' untrusted.yml
These are rejection ceilings, not tuning hints. Pick limits appropriate to the workload.