I know I'm a bit late to this party, but for anyone who comes by here and wants to avoid using Bash, PowerShell and similar, I've managed to do a substring with nothing but Azure Pipelines expressions.
(I'll definitely go straight to hell for writing this code)
Unfortunately, we can not index on string for some reason, so @dsschneidermann's answer does not work. But we can work around this using the split
function.
This definitely is more complex than using scripting, but is, on the other hand, run fully at the compile time.
- Append some known character after each and every character in the original string using
replace
. I.e., append _
, so that abc
becomes a_b_c_
.
- Split the string using the appended character -
split('a_b_c_', '_')
. The split
function returns an indexable array.
- Use indexing on the array to compose the result
This, besides being unreadable, has the disadvantage that you have to specify all characters that might ever be part of the input value. Failing to do so might cause unexpected behavior. But for a git hash, it's enough to specify the whole alphabet, plus numerics.
The result is this:
- name: Build.SourceVersion.PreparedForSplit
value: ${{ replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(variables['Build.SourceVersion'], 'a', 'a_'), 'b', 'b_'), 'c', 'c_'), 'd', 'd_'), 'e', 'e_'), 'f', 'f_'), 'g', 'g_'), 'h', 'h_'), 'i', 'i_'), 'j', 'j_'), 'k', 'k_'), 'l', 'l_'), 'm', 'm_'), 'n', 'n_'), 'o', 'o_'), 'p', 'p_'), 'q', 'q_'), 'r', 'r_'), 's', 's_'), 't', 't_'), 'u', 'u_'), 'v', 'v_'), 'w', 'w_'), 'x', 'x_'), 'y', 'y_'), 'z', 'z_'), '0', '0_'), '1', '1_'), '2', '2_'), '3', '3_'), '4', '4_'), '5', '5_'), '6', '6_'), '7', '7_'), '8', '8_'), '9', '9_') }}
readonly: true
- name: Build.SourceVersion.Short
value: ${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[0] }}${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[1] }}${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[2] }}${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[3] }}${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[4] }}${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[5] }}${{ split(variables['Build.SourceVersion.PreparedForSplit'], '_')[6] }}
readonly: true
You might put these in a variable template and then, everywhere you need to use them, just include this template, what is a one line of code. So you end up with one hard-to-read code file, but other simple ones, and everything is processed at the template's compile time.
I've also created a PowerShell script for generating the intermediate variable:
# Input variable name
$VariableName = 'Build.SourceVersion'
# Known characters - should be every character that could appear in the input
$Chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
# Character to be appended after every character in the input string
$IntermediateChar = '_'
$output = "variables['$VariableName']"
foreach ($Char in [char[]]$Chars) {
$output = "replace($output, '$Char', '$Char$IntermediateChar')"
}
Write-Host $output