Udostępnij za pośrednictwem


Expressions

Azure DevOps Server 2019

Important

Select a version from Azure DevOps Content Version selector.

Select the version of this article that corresponds to your platform and version. The version selector is above the table of contents. Look up your Azure DevOps platform and version.

Expressions can be used in many places where you need to specify a string, boolean, or number value when authoring a pipeline. When an expression returns an array, normal indexing rules apply and the index starts with 0.

The most common use of expressions is in conditions to determine whether a job or step should run.

# Expressions are used to define conditions for a step, job, or stage
steps:
- task: ...
  condition: <expression>

Another common use of expressions is in defining variables. Expressions can be evaluated at compile time or at run time. Compile time expressions can be used anywhere; runtime expressions can be used in variables and conditions. Runtime expressions are intended as a way to compute the contents of variables and state (example: condition).

# Two examples of expressions used to define variables
# The first one, a, is evaluated when the YAML file is compiled into a plan.
# The second one, b, is evaluated at runtime.
# Note the syntax ${{}} for compile time and $[] for runtime expressions.
variables:
  a: ${{ <expression> }}
  b: $[ <expression> ]

The difference between runtime and compile time expression syntaxes is primarily what context is available. In a compile-time expression (${{ <expression> }}), you have access to parameters and statically defined variables. In a runtime expression ($[ <expression> ]), you have access to more variables but no parameters.

In this example, a runtime expression sets the value of $(isMain). A static variable in a compile expression sets the value of $(compileVar).

variables:
  staticVar: 'my value' # static variable
  compileVar: ${{ variables.staticVar }} # compile time expression
  isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')] # runtime expression

steps:
  - script: |
      echo ${{variables.staticVar}} # outputs my value
      echo $(compileVar) # outputs my value
      echo $(isMain) # outputs True

An expression can be a literal, a reference to a variable, a reference to a dependency, a function, or a valid nested combination of these.

Literals

As part of an expression, you can use boolean, null, number, string, or version literals.

# Examples
variables:
  someBoolean: ${{ true }} # case insensitive, so True or TRUE also works
  someNumber: ${{ -1.2 }}
  someString: ${{ 'a b c' }}
  someVersion: ${{ 1.2.3 }}

Boolean

True and False are boolean literal expressions.

Null

Null is a special literal expression that's returned from a dictionary miss, for example (variables['noSuch']). Null can be the output of an expression but can't be called directly within an expression.

Number

Starts with '-', '.', or '0' through '9'.

String

Must be single-quoted. For example: 'this is a string'.

To express a literal single-quote, escape it with a single quote. For example: 'It''s OK if they''re using contractions.'.

You can use a pipe character (|) for multiline strings.

myKey: |
  one
  two
  three

Version

A version number with up to four segments. Must start with a number and contain two or three period (.) characters. For example: 1.2.3.4.

Variables

As part of an expression, you may access variables using one of two syntaxes:

  • Index syntax: variables['MyVar']
  • Property dereference syntax: variables.MyVar

In order to use property dereference syntax, the property name must:

  • Start with a-Z or _
  • Be followed by a-Z 0-9 or _

Depending on the execution context, different variables are available.

  • If you create pipelines using YAML, then pipeline variables are available.
  • If you create build pipelines using classic editor, then build variables are available.
  • If you create release pipelines using classic editor, then release variables are available.

Variables are always strings. If you want to use typed values, then you should use parameters instead.

Note

There is a limitation for using variables with expressions for both Classical and YAML pipelines when setting up such variables via variables tab UI. Variables that are defined as expressions shouldn't depend on another variable with expression in value since it isn't guaranteed that both expressions will be evaluated properly. For example we have variable a whose value $[ <expression> ] is used as a part for the value of variable b. Since the order of processing variables isn't guaranteed variable b could have an incorrect value of variable a after evaluation.

Described constructions are only allowed while setup variables through variables keyword in YAML pipeline. It is required to place the variables in the order they should be processed to get the correct values after processing.

Functions

The following built-in functions can be used in expressions.

and

  • Evaluates to True if all parameters are True
  • Min parameters: 2. Max parameters: N
  • Casts parameters to Boolean for evaluation
  • Short-circuits after first False
  • Example: and(eq(variables.letters, 'ABC'), eq(variables.numbers, 123))

coalesce

  • Evaluates the parameters in order (left to right), and returns the first value that doesn't equal null or empty-string.
  • No value is returned if the parameter values all are null or empty strings.
  • Min parameters: 2. Max parameters: N
  • Example: coalesce(variables.couldBeNull, variables.couldAlsoBeNull, 'literal so it always works')

contains

  • Evaluates True if left parameter String contains right parameter
  • Min parameters: 2. Max parameters: 2
  • Casts parameters to String for evaluation
  • Performs ordinal ignore-case comparison
  • Example: contains('ABCDE', 'BCD') (returns True)

containsValue

  • Evaluates True if the left parameter is an array, and any item equals the right parameter. Also evaluates True if the left parameter is an object, and the value of any property equals the right parameter.
  • Min parameters: 2. Max parameters: 2
  • If the left parameter is an array, convert each item to match the type of the right parameter. If the left parameter is an object, convert the value of each property to match the type of the right parameter. The equality comparison for each specific item evaluates False if the conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Short-circuits after the first match

Note

There is no literal syntax in a YAML pipeline for specifying an array. This function is of limited use in general pipelines. It's intended for use in the pipeline decorator context with system-provided arrays such as the list of steps.

You can use the containsValue expression to find a matching value in an object. Here's an example that demonstrates looking in list of source branches for a match for Build.SourceBranch.

parameters:
- name: branchOptions
  displayName: Source branch options
  type: object
  default:
    - refs/heads/main
    - refs/heads/test

jobs:
  - job: A1 
    steps:
    - ${{ each value in parameters.branchOptions }}:
      - script: echo ${{ value }}

  - job: B1 
    condition: ${{ containsValue(parameters.branchOptions, variables['Build.SourceBranch']) }}
    steps:
      - script: echo "Matching branch found"

convertToJson

  • Take a complex object and outputs it as JSON.
  • Min parameters: 1. Max parameters: 1.
parameters:
  - name: listOfValues
    type: object
    default:
      this_is:
        a_complex: object
        with:
          - one
          - two
 
steps:
- script: |
    echo "${MY_JSON}"
  env:
    MY_JSON: ${{ convertToJson(parameters.listOfValues) }}

Script output:

{
  "this_is": {
    "a_complex": "object",
    "with": [
      "one",
      "two"
    ]
  }
}

counter

  • This function can only be used in an expression that defines a variable. It can't be used as part of a condition for a step, job, or stage.
  • Evaluates a number that is incremented with each run of a pipeline.
  • Parameters: 2. prefix and seed.
  • Prefix is a string expression. A separate value of counter is tracked for each unique value of prefix. The prefix should use UTF-16 characters.
  • Seed is the starting value of the counter

You can create a counter that is automatically incremented by one in each execution of your pipeline. When you define a counter, you provide a prefix and a seed. Here's an example that demonstrates this.

variables:
  major: 1
  # define minor as a counter with the prefix as variable major, and seed as 100.
  minor: $[counter(variables['major'], 100)]

steps:
- bash: echo $(minor)

The value of minor in the above example in the first run of the pipeline is 100. In the second run it is 101, provided the value of major is still 1.

If you edit the YAML file, and update the value of the variable major to be 2, then in the next run of the pipeline, the value of minor will be 100. Subsequent runs increment the counter to 101, 102, 103, ...

Later, if you edit the YAML file, and set the value of major back to 1, then the value of the counter resumes where it left off for that prefix. In this example, it resumes at 102.

Here's another example of setting a variable to act as a counter that starts at 100, gets incremented by 1 for every run, and gets reset to 100 every day.

Note

pipeline.startTime is not available outside of expressions. pipeline.startTime formats system.pipelineStartTime into a date and time object so that it is available to work with expressions. The default time zone for pipeline.startTime is UTC. You can change the time zone for your organization.

jobs:
- job:
  variables:
    a: $[counter(format('{0:yyyyMMdd}', pipeline.startTime), 100)]
  steps:
  - bash: echo $(a)

Here's an example of having a counter that maintains a separate value for PRs and CI runs.

variables:
  patch: $[counter(variables['build.reason'], 0)]

Counters are scoped to a pipeline. In other words, its value is incremented for each run of that pipeline. There are no project-scoped counters.

endsWith

  • Evaluates True if left parameter String ends with right parameter
  • Min parameters: 2. Max parameters: 2
  • Casts parameters to String for evaluation
  • Performs ordinal ignore-case comparison
  • Example: endsWith('ABCDE', 'DE') (returns True)

eq

  • Evaluates True if parameters are equal
  • Min parameters: 2. Max parameters: 2
  • Converts right parameter to match type of left parameter. Returns False if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Example: eq(variables.letters, 'ABC')

format

  • Evaluates the trailing parameters and inserts them into the leading parameter string
  • Min parameters: 1. Max parameters: N
  • Example: format('Hello {0} {1}', 'John', 'Doe')
  • Uses .NET custom date and time format specifiers for date formatting (yyyy, yy, MM, M, dd, d, HH, H, m, mm, ss, s, f, ff, ffff, K)
  • Example: format('{0:yyyyMMdd}', pipeline.startTime). In this case pipeline.startTime is a special date time object variable.
  • Escape by doubling braces. For example: format('literal left brace {{ and literal right brace }}')

ge

  • Evaluates True if left parameter is greater than or equal to the right parameter
  • Min parameters: 2. Max parameters: 2
  • Converts right parameter to match type of left parameter. Errors if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Example: ge(5, 5) (returns True)

gt

  • Evaluates True if left parameter is greater than the right parameter
  • Min parameters: 2. Max parameters: 2
  • Converts right parameter to match type of left parameter. Errors if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Example: gt(5, 2) (returns True)

in

  • Evaluates True if left parameter is equal to any right parameter
  • Min parameters: 1. Max parameters: N
  • Converts right parameters to match type of left parameter. Equality comparison evaluates False if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Short-circuits after first match
  • Example: in('B', 'A', 'B', 'C') (returns True)

le

  • Evaluates True if left parameter is less than or equal to the right parameter
  • Min parameters: 2. Max parameters: 2
  • Converts right parameter to match type of left parameter. Errors if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Example: le(2, 2) (returns True)

length

  • Returns the length of a string or an array, either one that comes from the system or that comes from a parameter
  • Min parameters: 1. Max parameters 1
  • Example: length('fabrikam') returns 8

lt

  • Evaluates True if left parameter is less than the right parameter
  • Min parameters: 2. Max parameters: 2
  • Converts right parameter to match type of left parameter. Errors if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Example: lt(2, 5) (returns True)

ne

  • Evaluates True if parameters are not equal
  • Min parameters: 2. Max parameters: 2
  • Converts right parameter to match type of left parameter. Returns True if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Example: ne(1, 2) (returns True)

not

  • Evaluates True if parameter is False
  • Min parameters: 1. Max parameters: 1
  • Converts value to Boolean for evaluation
  • Example: not(eq(1, 2)) (returns True)

notIn

  • Evaluates True if left parameter isn't equal to any right parameter
  • Min parameters: 1. Max parameters: N
  • Converts right parameters to match type of left parameter. Equality comparison evaluates False if conversion fails.
  • Ordinal ignore-case comparison for Strings
  • Short-circuits after first match
  • Example: notIn('D', 'A', 'B', 'C') (returns True)

or

  • Evaluates True if any parameter is True
  • Min parameters: 2. Max parameters: N
  • Casts parameters to Boolean for evaluation
  • Short-circuits after first True
  • Example: or(eq(1, 1), eq(2, 3)) (returns True, short-circuits)

startsWith

  • Evaluates True if left parameter string starts with right parameter
  • Min parameters: 2. Max parameters: 2
  • Casts parameters to String for evaluation
  • Performs ordinal ignore-case comparison
  • Example: startsWith('ABCDE', 'AB') (returns True)

xor

  • Evaluates True if exactly one parameter is True
  • Min parameters: 2. Max parameters: 2
  • Casts parameters to Boolean for evaluation
  • Example: xor(True, False) (returns True)

Job status check functions

You can use the following status check functions as expressions in conditions, but not in variable definitions.

always

  • Always evaluates to True (even when canceled). Note: A critical failure may still prevent a task from running. For example, if getting sources failed.

canceled

  • Evaluates to True if the pipeline was canceled.

failed

  • For a step, equivalent to eq(variables['Agent.JobStatus'], 'Failed').
  • For a job:
    • With no arguments, evaluates to True only if any previous job in the dependency graph failed.
    • With job names as arguments, evaluates to True only if any of those jobs failed.

succeeded

  • For a step, equivalent to in(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')
  • Use with dependsOn when working with jobs and you want to evaluate whether a previous job was successful. Jobs are designed to run in parallel while stages run sequentially.
  • For a job:
    • With no arguments, evaluates to True only if all previous jobs in the dependency graph succeeded or partially succeeded.
    • With job names as arguments, evaluates to True if all of those jobs succeeded or partially succeeded.
    • Evaluates to False if the pipeline is canceled.

succeededOrFailed

  • For a step, equivalent to in(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues', 'Failed')

  • For a job:

    • With no arguments, evaluates to True regardless of whether any jobs in the dependency graph succeeded or failed.
    • With job names as arguments, evaluates to True whether any of those jobs succeeded or failed.
    • You may want to use not(canceled()) instead when there are previous skipped jobs in the dependency graph.

    This is like always(), except it will evaluate False when the pipeline is canceled.

Conditional insertion

You can use if to conditionally assign variable values or set inputs for tasks. You can also conditionally run a step when a condition is met.

The elseif and else clauses are available starting with Azure DevOps 2022 and aren't available for Azure DevOps Server 2020 and earlier versions of Azure DevOps.

Conditionals only work when using template syntax. Learn more about variable syntax.

For templates, you can use conditional insertion when adding a sequence or mapping. Learn more about conditional insertion in templates.

Conditionally assign a variable

variables:
  ${{ if eq(variables['Build.SourceBranchName'], 'main') }}: # only works if you have a main branch
    stageName: prod

pool:
  vmImage: 'ubuntu-latest'

steps:
- script: echo ${{variables.stageName}}

Filtered arrays

When operating on a collection of items, you can use the * syntax to apply a filtered array. A filtered array returns all objects/elements regardless their names.

As an example, consider an array of objects named foo. We want to get an array of the values of the id property in each object in our array.

[
    { "id": 1, "a": "avalue1"},
    { "id": 2, "a": "avalue2"},
    { "id": 3, "a": "avalue3"}
]

We could do the following:

foo.*.id

This tells the system to operate on foo as a filtered array and then select the id property.

This would return:

[ 1, 2, 3 ]

Type casting

Values in an expression may be converted from one type to another as the expression gets evaluated. When an expression is evaluated, the parameters are coalesced to the relevant data type and then turned back into strings.

For example, in this YAML, the values True and False are converted to 1 and 0 when the expression is evaluated. The function lt() returns True when the left parameter is less than the right parameter.

variables:
  firstEval: $[lt(False, True)] # 0 vs. 1, True
  secondEval: $[lt(True, False)] # 1 vs. 0, False

steps:
- script: echo $(firstEval)
- script: echo $(secondEval)

When you use the eq() expression for evaluating equivalence, values are implicitly converted to numbers (false to 0 and true to 1).

variables:
  trueAsNumber: $[eq('true', true)] # 1 vs. 1, True
  falseAsNumber: $[eq('false', true)] # 0 vs. 1, False

steps:
- script: echo $(trueAsNumber)
- script: echo $(falseAsNumber)

In this next example, the values variables.emptyString and the empty string both evaluate as empty strings. The function coalesce() evaluates the parameters in order, and returns the first value that doesn't equal null or empty-string.

variables:
  coalesceLiteral: $[coalesce(variables.emptyString, '', 'literal value')]

steps:
- script: echo $(coalesceLiteral) # outputs literal value

Detailed conversion rules are listed further below.

From / To Boolean Null Number String Version
Boolean - - Yes Yes -
Null Yes - Yes Yes -
Number Yes - - Yes Partial
String Yes Partial Partial - Partial
Version Yes - - Yes -

Boolean

To number:

  • False0
  • True1

To string:

  • False'False'
  • True'True'

Null

  • To Boolean: False
  • To number: 0
  • To string: '' (the empty string)

Number

  • To Boolean: 0False, any other number → True
  • To version: Must be greater than zero and must contain a nonzero decimal. Must be less than Int32.MaxValue (decimal component also).
  • To string: Converts the number to a string with no thousands separator and no decimal separator.

String

  • To Boolean: '' (the empty string) → False, any other string → True
  • To null: '' (the empty string) → Null, any other string not convertible
  • To number: '' (the empty string) → 0, otherwise, runs C#'s Int32.TryParse using InvariantCulture and the following rules: AllowDecimalPoint | AllowLeadingSign | AllowLeadingWhite | AllowThousands | AllowTrailingWhite. If TryParse fails, then it's not convertible.
  • To version: runs C#'s Version.TryParse. Must contain Major and Minor component at minimum. If TryParse fails, then it's not convertible.

Version

  • To Boolean: True
  • To string: Major.Minor or Major.Minor.Build or Major.Minor.Build.Revision.

FAQ

I want to do something that isn't supported by expressions. What options do I have for extending Pipelines functionality?

You can customize your Pipeline with a script that includes an expression. For example, this snippet takes the BUILD_BUILDNUMBER variable and splits it with Bash. This script outputs two new variables, $MAJOR_RUN and $MINOR_RUN, for the major and minor run numbers. The two variables are then used to create two pipeline variables, $major and $minor with task.setvariable. These variables are available to downstream steps. To share variables across pipelines see Variable groups.

steps:
- bash: |
    MAJOR_RUN=$(echo $BUILD_BUILDNUMBER | cut -d '.' -f1)
    echo "This is the major run number: $MAJOR_RUN"
    echo "##vso[task.setvariable variable=major]$MAJOR_RUN"

    MINOR_RUN=$(echo $BUILD_BUILDNUMBER | cut -d '.' -f2)
    echo "This is the minor run number: $MINOR_RUN"
    echo "##vso[task.setvariable variable=minor]$MINOR_RUN"

- bash: echo "My pipeline variable for major run is $(major)"
- bash: echo "My pipeline variable for minor run is $(minor)"