bicep icon indicating copy to clipboard operation
bicep copied to clipboard

Allow interpolation in Multiline strings

Open chriswue opened this issue 4 years ago • 13 comments

As per #416 the current implementation does not support inline string interpolation and the workarounds with format and concat have each their own draw-backs.

I'd be happy with the proposed solution of matching the number of $ to the number of quote marks.

Couldn't find an issue for this feature so here we go.

chriswue avatar Jun 28 '21 19:06 chriswue

Need this when building large cloud config strings and injecting vars & params into them the format workaround starts to fall apart when you have ~20 things you need to plug into the string

benc-uk avatar Jul 08 '21 13:07 benc-uk

How about doing something python's f-strings or C#'s $-string interpolation:

var interpolated = 'interpolated'
var s = $'''
this is ${interpolated}'''

var ns = '''
this is not ${interpolated}'''

chriswue avatar Aug 13 '21 00:08 chriswue

Agreed on this one. Running into this issue with APIM Policies.

JFolberth avatar Jan 13 '22 02:01 JFolberth

Any update on this?

benc-uk avatar Oct 23 '22 16:10 benc-uk

No update, but I didn't notice how many upvotes this had received. I'll move it to the committed backlog and put it up for discussion to make sure we are not conceptually against having this support in the language.

alex-frankel avatar Oct 24 '22 23:10 alex-frankel

Right now, I use a workaround for Azure Data Explorer statements (it's very picky about what per-line values):

var mappingLine1 = '.create table ${TableName} ingestion json mapping "${MappingName}"'
var mappingLine2 = '''
'['
' {"column": "ColumnName", "Properties": {"Path": "$.columnName"}},'
']'
'''
var mappingLine = '${mappingLine1}\r\n${mappingLine2}'

It'd be so much cleaner (and could get rid of the environment-dependent newline characters) to have multi-line interpolation:

var mappingLine = $'''
.create table ${TableName} ingestion json mapping "${MappingName}"
'['
' {"column": "ColumnName", "Properties": {"Path": "$.columnName"}},'
']'
'''

For the record, I'd vote for the C# style of putting the $ in front of the string to indicate the interpolated values even if you stick with the Bicep approach of the $ in front of the curly braces within the string):

var message = "test";
var myString = $"This is a {test}";

WhitWaldo avatar Nov 23 '22 19:11 WhitWaldo

On the community call today, someone asked for a link to the documentation detailing how this is done in C#. It's a new language feature as of C# 11 described here.

WhitWaldo avatar Dec 01 '22 00:12 WhitWaldo

Looking back at @chriswue's comment:

How about doing something python's f-strings or C#'s $-string interpolation:

var interpolated = 'interpolated'
var s = $'''
this is ${interpolated}'''

var ns = '''
this is not ${interpolated}'''

I think this syntax suggestion would win my vote, possibly with the addition of an arbitrary number of $ characters to permit unescaped ${ sequences:

var s = $$'''
this is $${interpolated}
this is not ${interpolated}'''

If we can close on a syntax, I think the implementation should actually be pretty straightforward - we should be able to adapt the implementations for verbatim strings & interpolated strings without too much difficulty.

anthony-c-martin avatar Dec 01 '22 03:12 anthony-c-martin

@anthony-c-martin I think you do want to allow unescaped ${ sequences - otherwise writing shell script snippets could be come painful. So I like the multiple $ signs

chriswue avatar Dec 01 '22 19:12 chriswue

Are there any updates for the prioritisation of this feature? This would be really useful to have when building out vm image templates

joehubbert avatar Aug 18 '23 13:08 joehubbert

For people looking for a workaround, I had good results with the following code structure when dealing with custom script extensions:

commandToExecute: concat('powershell.exe -ExecutionPolicy Unrestricted -Command "',join([
      'Start-Transcript \\"${installLog}\\"'
      'Get-WindowsCapability -Online -Name open*'
      'Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0'
      'Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0'
      'Start-Service sshd'
      'Set-Service -Name sshd -StartupType Automatic'
      '$key = \'${sshkey}\''
      '$key | Add-Content -Path \'C:\\ProgramData\\ssh\\administrators_authorized_keys\' -Encoding UTF8'
      'icacls.exe \'C:\\ProgramData\\ssh\\administrators_authorized_keys\' /inheritance:r /grant \'Administrators:F\' /grant \'SYSTEM:F\''
      'Stop-Transcript'],';'),
      '"')

This code joins a bunch of powershell commands with ;. It is technically not a multi-line string but using join makes it readable since each command can go on a separate line on the template. It also allows for string interpolation as shown by ${installLog} and ${sshKey}.

If your use case is not PowerShell, you could have an array of strings and join by whichever character you require

vandre avatar Mar 12 '25 14:03 vandre

This would be really helpful for dynamic where clauses in a complex KQL query in an alert, for example:

query: '''
let exceptionErrors = exceptions
    | where severityLevel >= 3
    | where customDimensions.MyCustomProp == "${MyDynamicValue}"
    | project 
        TimeStamp = timestamp, 
        Message = strcat(
              coalesce(outerMessage, ""),
              iif(isnotempty(outerMessage) and isnotempty(innermostMessage), " → ", ""),
              coalesce(innermostMessage, "")
          ), 
        ItemCount = itemCount,
        App = cloud_RoleName,
        Type = "Exception";
let traceErrors = traces
    | where severityLevel >= 3
    | where customDimensions.MyCustomProp == "${MyDynamicValue}"
    | project 
        TimeStamp = timestamp, 
        Message = message,
        ItemCount = itemCount,
        App = cloud_RoleName,
        Type = "Trace";
exceptionErrors
| union traceErrors
'''

sherman89 avatar May 08 '25 10:05 sherman89

Any news on this one?

guimatheus92 avatar Jun 18 '25 21:06 guimatheus92

I have a workaround for now for a KQL query, but would rather use string interpolation for multiline. The workaround I am using now:

var scheduledEventsBaseQuery = '''
arg("").containerserviceeventresources
| where type == "microsoft.containerservice/managedclusters/scheduledevents"'''

var scheduledEventsClusterIdQuery = '| where id contains "${resourceId('Microsoft.ContainerService/managedClusters', clusterName)}"'

var upgradeNotificationSelectQuery = '''
| where properties has "eventStatus"
| extend status = substring(properties, indexof(properties, "eventStatus") + strlen("eventStatus") + 3, 50)
| extend status = substring(status, 0, indexof(status, ",") - 1)
| where status != ""
| where properties has "eventDetails"
| extend upgradeType = case(
                            properties has "K8sVersionUpgrade",
                            "K8sVersionUpgrade",
                            properties has "NodeOSUpgrade",
                            "NodeOSUpgrade",
                            ""
                        )
| extend details = parse_json(tostring(properties.eventDetails))
| where properties has "lastUpdateTime"
| extend eventTime = substring(properties, indexof(properties, "lastUpdateTime") + strlen("lastUpdateTime") + 3, 50)
| extend eventTime = substring(eventTime, 0, indexof(eventTime, ",") - 1)
| extend eventTime = todatetime(tostring(eventTime))
| where eventTime >= ago(2h)
| where upgradeType == "K8sVersionUpgrade"
| project    
    eventTime,
    upgradeType,
    status,
    properties,
    name,
    details
| order by eventTime asc
'''

var upgradeNotificationQuery = '${scheduledEventsBaseQuery}\r\n${scheduledEventsClusterIdQuery}\r\n${upgradeNotificationSelectQuery}'

AshwinSarimin avatar Sep 05 '25 13:09 AshwinSarimin