# expressions

computed values (!expr)

An expression computes a value for each host, such as a list, a number, or true or false.

## Syntax

The !expr tag marks an expression in the language of expr (expr-lang.org). Its result keeps its type: a list, a number, a boolean, a map, or a string.

## Data

An expression reads these names without a leading dot:

|  |  |
| --- | --- |
| vars | the variables of the host, with the vars of the recipe step |
| facts | the facts of the host; run winch doc facts |
| host | name, short, address, groups, and vars of the host; address is the name when the host has no address |
| item | the current element in a step with loop |
| match(p) | the hosts that match the host pattern p, with the fields of host |

match(p) reads the inventory, not the --hosts selection. A host from match() has only the vars that need no facts. A default cannot use match().

## Where expressions go

Any module argument can be an expression or a template, and so can then. when takes an expression. loop takes a list or an expression.

## Operators

|  |  |
| --- | --- |
| + - \* / % \*\* | arithmetic |
| == != \< \> \<= \>= | comparison |
| and or not | logic, also \&\& \|\| ! |
| in | membership in a list or a map |
| contains startsWith endsWith | text tests |
| matches | regular expression test |
| c ? a : b | choice |
| a ?? b | a, or b when a is nil |
| \[\] ?. | read an element or a key |
| x\[i:j\] 1..3 | slice and range |
| \| | pipe: vars.name \| lower() |

## Predicates

In a predicate, such as the second argument of filter, \# is the element, and .name reads the key name of a map element: filter(vars.users, .uid \>= 1000).

## Missing values

A missing map key is nil. A module argument whose expression gives null fails the host.

## Quoting

Quote a YAML value that starts with a quote, \[, {, \&, \*, or !, or that contains ": " or " \#".

## When they run

winch evaluates expressions on the machine that runs winch, during the plan. They cannot read the result of a step. winch checks each one when it loads the project: an unknown name or a syntax error is an error. An error while winch evaluates one for a host fails that host. The other hosts go on. $env and now() are errors in every expression.

## Functions

| Function | Meaning |
| --- | --- |
| [`abs`](#expressions.abs) | abs(n): the absolute value of the number n. |
| [`all`](#expressions.all) | all(list, pred): true when pred is true for each element. |
| [`any`](#expressions.any) | any(list, pred): true when pred is true for at least one element. |
| [`bitand`](#expressions.bitand) | bitand(a, b): the bitwise AND of two integers. |
| [`bitnand`](#expressions.bitnand) | bitnand(a, b): the bitwise AND NOT of two integers. |
| [`bitnot`](#expressions.bitnot) | bitnot(a): the bitwise NOT of an integer. |
| [`bitor`](#expressions.bitor) | bitor(a, b): the bitwise OR of two integers. |
| [`bitshl`](#expressions.bitshl) | bitshl(a, n): a shifted left by n bits. |
| [`bitshr`](#expressions.bitshr) | bitshr(a, n): a shifted right by n bits, with its sign. |
| [`bitushr`](#expressions.bitushr) | bitushr(a, n): a shifted right by n bits, without its sign. |
| [`bitxor`](#expressions.bitxor) | bitxor(a, b): the bitwise XOR of two integers. |
| [`ceil`](#expressions.ceil) | ceil(n): n rounded up to an integer value. |
| [`concat`](#expressions.concat) | concat(a, b, ...): the lists joined into one list. |
| [`count`](#expressions.count) | count(list, pred): the number of elements for which pred is true. |
| [`date`](#expressions.date) | date(s), date(s, layout), or date(s, layout, zone): the time in the text s, such as date("2026-10-30"). |
| [`duration`](#expressions.duration) | duration(s): the duration in the text s, such as duration("1h30m"). |
| [`filter`](#expressions.filter) | filter(list, pred): the elements for which pred is true: filter(match("all"), .name != host.name). |
| [`find`](#expressions.find) | find(list, pred): the first element for which pred is true, or nil. |
| [`findIndex`](#expressions.findIndex) | findIndex(list, pred): the index of the first element for which pred is true, or -1. |
| [`findLast`](#expressions.findLast) | findLast(list, pred): the last element for which pred is true, or nil. |
| [`findLastIndex`](#expressions.findLastIndex) | findLastIndex(list, pred): the index of the last element for which pred is true, or -1. |
| [`first`](#expressions.first) | first(list): the first element, or nil for an empty list. |
| [`flatten`](#expressions.flatten) | flatten(list): the elements of the list and of its nested lists, in one list. |
| [`float`](#expressions.float) | float(v): the number or the text v as a float. |
| [`floor`](#expressions.floor) | floor(n): n rounded down to an integer value. |
| [`fromBase64`](#expressions.fromBase64) | fromBase64(s): the text that the base64 text s encodes. |
| [`fromJSON`](#expressions.fromJSON) | fromJSON(s): the value of the JSON text s. |
| [`fromPairs`](#expressions.fromPairs) | fromPairs(list): a map from a list of \[key, value\] pairs. |
| [`get`](#expressions.get) | get(m, key): the value of key in the map m, or nil when m has no such key. |
| [`groupBy`](#expressions.groupBy) | groupBy(list, pred): a map from each result of pred to the list of elements that give it. |
| [`hasPrefix`](#expressions.hasPrefix) | hasPrefix(s, prefix): true when s starts with prefix. |
| [`hasSuffix`](#expressions.hasSuffix) | hasSuffix(s, suffix): true when s ends with suffix. |
| [`indexOf`](#expressions.indexOf) | indexOf(s, sub): the index of the first sub in s, or -1. |
| [`int`](#expressions.int) | int(v): the number or the text v as an integer: int("8080"). |
| [`join`](#expressions.join) | join(list, sep): the strings of list joined with sep. |
| [`json`](#expressions.json) | json(v): v as JSON on one line. |
| [`keys`](#expressions.keys) | keys(m): the keys of the map m, in sorted order. |
| [`last`](#expressions.last) | last(list): the last element, or nil for an empty list. |
| [`lastIndexOf`](#expressions.lastIndexOf) | lastIndexOf(s, sub): the index of the last sub in s, or -1. |
| [`len`](#expressions.len) | len(x): the length of a string, a list, or a map. |
| [`lower`](#expressions.lower) | lower(s): s in lower case. |
| [`map`](#expressions.map) | map(list, pred): the result of pred for each element: map(vars.users, .name). |
| [`match`](#expressions.match) | match(pattern): the hosts that match the host pattern, each with name, short, address, groups, and vars, as host has. |
| [`max`](#expressions.max) | max(a, b, ...), or max(list): the largest number. |
| [`mean`](#expressions.mean) | mean(list): the average of the numbers. |
| [`median`](#expressions.median) | median(list): the median of the numbers. |
| [`min`](#expressions.min) | min(a, b, ...), or min(list): the smallest number. |
| [`none`](#expressions.none) | none(list, pred): true when pred is false for each element. |
| [`one`](#expressions.one) | one(list, pred): true when pred is true for exactly one element. |
| [`quote`](#expressions.quote) | quote(s): s in single quotes for sh, each ' as '\\''. |
| [`reduce`](#expressions.reduce) | reduce(list, pred, initial): one value from all elements. |
| [`repeat`](#expressions.repeat) | repeat(s, n): s n times. |
| [`replace`](#expressions.replace) | replace(s, old, new): s with each old replaced by new. |
| [`reverse`](#expressions.reverse) | reverse(list): the elements in reverse order. |
| [`round`](#expressions.round) | round(n): n rounded to the nearest integer value. |
| [`sort`](#expressions.sort) | sort(list), or sort(list, "desc"): the elements in ascending or descending order. |
| [`sortBy`](#expressions.sortBy) | sortBy(list, pred), or sortBy(list, pred, "desc"): the elements in the order of the results of pred: sortBy(vars.users, .uid). |
| [`split`](#expressions.split) | split(s, sep), or split(s, sep, n): the parts of s between each sep. |
| [`splitAfter`](#expressions.splitAfter) | splitAfter(s, sep), or splitAfter(s, sep, n): the parts of s after each sep, each with sep at its end. |
| [`sqlquote`](#expressions.sqlquote) | sqlquote(s): s as a string literal for MariaDB, or for PostgreSQL with E before it: in single quotes, each ' as '', each \\ as \\\\. |
| [`string`](#expressions.string) | string(v): v as text: string(8080) is "8080". |
| [`sum`](#expressions.sum) | sum(list), or sum(list, pred): the sum of the numbers, or of the results of pred. |
| [`take`](#expressions.take) | take(list, n): the first n elements. |
| [`timezone`](#expressions.timezone) | timezone(name): the time zone name, such as "Europe/Berlin", for the In method of a date. |
| [`toBase64`](#expressions.toBase64) | toBase64(s): s in base64. |
| [`toPairs`](#expressions.toPairs) | toPairs(m): the \[key, value\] pairs of the map m, in sorted key order. |
| [`trim`](#expressions.trim) | trim(s), or trim(s, chars): s without white space, or without the characters in chars, at both ends. |
| [`trimPrefix`](#expressions.trimPrefix) | trimPrefix(s, prefix): s without prefix at its start. |
| [`trimSuffix`](#expressions.trimSuffix) | trimSuffix(s, suffix): s without suffix at its end. |
| [`type`](#expressions.type) | type(v): the type of v as text: nil, bool, int, float, string, array, map, time.Time for a date, or time.Duration. |
| [`uniq`](#expressions.uniq) | uniq(list): the elements without repeats, in their first order. |
| [`upper`](#expressions.upper) | upper(s): s in upper case. |
| [`values`](#expressions.values) | values(m): the values of the map m, in the order of its sorted keys. |

### `expressions.abs` {#expressions.abs}

abs(n): the absolute value of the number n.

### `expressions.all` {#expressions.all}

all(list, pred): true when pred is true for each element. In pred, \# is the element, and .name reads the key name of a map element: all(vars.ports, \# \> 0).

### `expressions.any` {#expressions.any}

any(list, pred): true when pred is true for at least one element.

### `expressions.bitand` {#expressions.bitand}

bitand(a, b): the bitwise AND of two integers.

### `expressions.bitnand` {#expressions.bitnand}

bitnand(a, b): the bitwise AND NOT of two integers.

### `expressions.bitnot` {#expressions.bitnot}

bitnot(a): the bitwise NOT of an integer.

### `expressions.bitor` {#expressions.bitor}

bitor(a, b): the bitwise OR of two integers.

### `expressions.bitshl` {#expressions.bitshl}

bitshl(a, n): a shifted left by n bits.

### `expressions.bitshr` {#expressions.bitshr}

bitshr(a, n): a shifted right by n bits, with its sign.

### `expressions.bitushr` {#expressions.bitushr}

bitushr(a, n): a shifted right by n bits, without its sign.

### `expressions.bitxor` {#expressions.bitxor}

bitxor(a, b): the bitwise XOR of two integers.

### `expressions.ceil` {#expressions.ceil}

ceil(n): n rounded up to an integer value.

### `expressions.concat` {#expressions.concat}

concat(a, b, ...): the lists joined into one list.

### `expressions.count` {#expressions.count}

count(list, pred): the number of elements for which pred is true. Without pred, the number of true elements.

### `expressions.date` {#expressions.date}

date(s), date(s, layout), or date(s, layout, zone): the time in the text s, such as date("2026-10-30"). The layout is a Go time layout. now() is a load error, so a plan does not depend on the clock.

### `expressions.duration` {#expressions.duration}

duration(s): the duration in the text s, such as duration("1h30m").

### `expressions.filter` {#expressions.filter}

filter(list, pred): the elements for which pred is true: filter(match("all"), .name != host.name).

### `expressions.find` {#expressions.find}

find(list, pred): the first element for which pred is true, or nil.

### `expressions.findIndex` {#expressions.findIndex}

findIndex(list, pred): the index of the first element for which pred is true, or -1.

### `expressions.findLast` {#expressions.findLast}

findLast(list, pred): the last element for which pred is true, or nil.

### `expressions.findLastIndex` {#expressions.findLastIndex}

findLastIndex(list, pred): the index of the last element for which pred is true, or -1.

### `expressions.first` {#expressions.first}

first(list): the first element, or nil for an empty list.

### `expressions.flatten` {#expressions.flatten}

flatten(list): the elements of the list and of its nested lists, in one list.

### `expressions.float` {#expressions.float}

float(v): the number or the text v as a float.

### `expressions.floor` {#expressions.floor}

floor(n): n rounded down to an integer value.

### `expressions.fromBase64` {#expressions.fromBase64}

fromBase64(s): the text that the base64 text s encodes.

### `expressions.fromJSON` {#expressions.fromJSON}

fromJSON(s): the value of the JSON text s.

### `expressions.fromPairs` {#expressions.fromPairs}

fromPairs(list): a map from a list of \[key, value\] pairs.

### `expressions.get` {#expressions.get}

get(m, key): the value of key in the map m, or nil when m has no such key. get(list, i) gives the element at the index i, or nil.

### `expressions.groupBy` {#expressions.groupBy}

groupBy(list, pred): a map from each result of pred to the list of elements that give it.

### `expressions.hasPrefix` {#expressions.hasPrefix}

hasPrefix(s, prefix): true when s starts with prefix. The operator startsWith does the same.

### `expressions.hasSuffix` {#expressions.hasSuffix}

hasSuffix(s, suffix): true when s ends with suffix. The operator endsWith does the same.

### `expressions.indexOf` {#expressions.indexOf}

indexOf(s, sub): the index of the first sub in s, or -1.

### `expressions.int` {#expressions.int}

int(v): the number or the text v as an integer: int("8080").

### `expressions.join` {#expressions.join}

join(list, sep): the strings of list joined with sep.

### `expressions.json` {#expressions.json}

json(v): v as JSON on one line. Map keys are in sorted order, and non-ASCII characters stay UTF-8. The output is also a valid YAML value.

### `expressions.keys` {#expressions.keys}

keys(m): the keys of the map m, in sorted order. It cannot read vars itself: name each variable.

### `expressions.last` {#expressions.last}

last(list): the last element, or nil for an empty list.

### `expressions.lastIndexOf` {#expressions.lastIndexOf}

lastIndexOf(s, sub): the index of the last sub in s, or -1.

### `expressions.len` {#expressions.len}

len(x): the length of a string, a list, or a map.

### `expressions.lower` {#expressions.lower}

lower(s): s in lower case.

### `expressions.map` {#expressions.map}

map(list, pred): the result of pred for each element: map(vars.users, .name).

### `expressions.match` {#expressions.match}

match(pattern): the hosts that match the host pattern, each with name, short, address, groups, and vars, as host has. It reads the inventory, not the --hosts selection. A host from match() has only the vars that need no facts. It works in steps, not in defaults. A constant pattern that matches no host is a load error.

### `expressions.max` {#expressions.max}

max(a, b, ...), or max(list): the largest number.

### `expressions.mean` {#expressions.mean}

mean(list): the average of the numbers.

### `expressions.median` {#expressions.median}

median(list): the median of the numbers.

### `expressions.min` {#expressions.min}

min(a, b, ...), or min(list): the smallest number.

### `expressions.none` {#expressions.none}

none(list, pred): true when pred is false for each element.

### `expressions.one` {#expressions.one}

one(list, pred): true when pred is true for exactly one element.

### `expressions.quote` {#expressions.quote}

quote(s): s in single quotes for sh, each ' as '\\''. A value goes into a command as it is. Quote a value that must stay one word: then: !expr '"postmap " + quote(vars.map)'.

### `expressions.reduce` {#expressions.reduce}

reduce(list, pred, initial): one value from all elements. In pred, \#acc is the value so far, \# the element, and \#index its index: reduce(vars.sizes, \#acc + \#, 0).

### `expressions.repeat` {#expressions.repeat}

repeat(s, n): s n times.

### `expressions.replace` {#expressions.replace}

replace(s, old, new): s with each old replaced by new.

### `expressions.reverse` {#expressions.reverse}

reverse(list): the elements in reverse order.

### `expressions.round` {#expressions.round}

round(n): n rounded to the nearest integer value.

### `expressions.sort` {#expressions.sort}

sort(list), or sort(list, "desc"): the elements in ascending or descending order.

### `expressions.sortBy` {#expressions.sortBy}

sortBy(list, pred), or sortBy(list, pred, "desc"): the elements in the order of the results of pred: sortBy(vars.users, .uid).

### `expressions.split` {#expressions.split}

split(s, sep), or split(s, sep, n): the parts of s between each sep. With n, at most n parts.

### `expressions.splitAfter` {#expressions.splitAfter}

splitAfter(s, sep), or splitAfter(s, sep, n): the parts of s after each sep, each with sep at its end.

### `expressions.sqlquote` {#expressions.sqlquote}

sqlquote(s): s as a string literal for MariaDB, or for PostgreSQL with E before it: in single quotes, each ' as '', each \\ as \\\\. It assumes the default SQL mode, without NO\_BACKSLASH\_ESCAPES, and a UTF-8 client.

### `expressions.string` {#expressions.string}

string(v): v as text: string(8080) is "8080".

### `expressions.sum` {#expressions.sum}

sum(list), or sum(list, pred): the sum of the numbers, or of the results of pred.

### `expressions.take` {#expressions.take}

take(list, n): the first n elements.

### `expressions.timezone` {#expressions.timezone}

timezone(name): the time zone name, such as "Europe/Berlin", for the In method of a date.

### `expressions.toBase64` {#expressions.toBase64}

toBase64(s): s in base64.

### `expressions.toPairs` {#expressions.toPairs}

toPairs(m): the \[key, value\] pairs of the map m, in sorted key order.

### `expressions.trim` {#expressions.trim}

trim(s), or trim(s, chars): s without white space, or without the characters in chars, at both ends.

### `expressions.trimPrefix` {#expressions.trimPrefix}

trimPrefix(s, prefix): s without prefix at its start.

### `expressions.trimSuffix` {#expressions.trimSuffix}

trimSuffix(s, suffix): s without suffix at its end.

### `expressions.type` {#expressions.type}

type(v): the type of v as text: nil, bool, int, float, string, array, map, time.Time for a date, or time.Duration.

### `expressions.uniq` {#expressions.uniq}

uniq(list): the elements without repeats, in their first order.

### `expressions.upper` {#expressions.upper}

upper(s): s in upper case.

### `expressions.values` {#expressions.values}

values(m): the values of the map m, in the order of its sorted keys.

## Example

```
# winch.yaml
name: demo
hosts:
  mail.acme.com: {}
  web1.acme.com: {}
vars:
  mail_server: false
  domain: acme.com
  admins: [ann, bob]
  mailname: !expr 'vars.mail_server ? vars.domain : host.name'
steps:
  - name: Set the mail name
    file:
      path: /etc/mailname
      content: !expr 'vars.mailname + "\n"'
    when: !expr vars.mail_server
  - name: Add the admins
    user:
      name: !expr item
      groups: [sudo]
    loop: !expr sort(vars.admins)
  - name: List the other hosts
    file:
      path: /etc/peers
      content: !expr 'join(map(filter(match("all"), .name != host.name), .address), "\n") + "\n"'
```
