Allt du ville veta om variabelersättning i strängar

Det finns många sätt att använda variabler i strängar. Jag anropar den här variabelersättningen, men jag refererar till varje gång du vill formatera en sträng för att inkludera värden från variabler. Detta är något som jag ofta finner mig själv förklara för nya skriptare.

Anteckning

Den ursprungliga versionen av den här artikeln visades på bloggen som skrivits av @KevinMarquette. PowerShell-teamet tackar Kevin för att han delade det här innehållet med oss. Kolla in hans blogg på PowerShellExplained.com.

Sammanlänkning

Den första klassen av metoder kan kallas sammanlänkning. Det är i princip att ta flera strängar och koppla ihop dem. Det finns en lång historia av att använda sammanfogning för att skapa formaterade strängar.

$name = 'Kevin Marquette'
$message = 'Hello, ' + $name

Sammanfogningen fungerar som den ska när det bara finns några få värden att lägga till. Men detta kan bli komplicerat snabbt.

$first = 'Kevin'
$last = 'Marquette'
$message = 'Hello, ' + $first + ' ' + $last + '.'

Det här enkla exemplet blir redan svårare att läsa.

Variabelersättning

PowerShell har ett annat alternativ som är enklare. Du kan ange dina variabler direkt i strängarna.

$message = "Hello, $first $last."

Vilken typ av citattecken du använder runt strängen gör skillnad. En dubbel citerad sträng tillåter ersättning, men en enda citerad sträng gör det inte. Det finns tillfällen då du vill ha det ena eller det andra så att du har ett alternativ.

Kommandoersättning

Det blir lite knepigt när du börjar försöka hämta egenskapernas värden till en sträng. Det är här många nya människor snubblar upp. Först låt mig visa dig vad de tycker ska fungera (och vid nominellt värde ser nästan ut som det borde).

$directory = Get-Item 'c:\windows'
$message = "Time: $directory.CreationTime"

Du skulle förvänta dig att få CreationTime bort från $directory, men i stället får du detta Time: c:\windows.CreationTime som ditt värde. Anledningen är att den här typen av ersättning endast ser basvariabeln. Den tar hänsyn till perioden som en del av strängen så att den slutar matcha värdet djupare.

Det händer bara så att det här objektet ger en sträng som ett standardvärde när det placeras i en sträng. Vissa objekt ger dig typnamnet i stället, till exempel System.Collections.Hashtable. Bara något att hålla utkik efter.

Med PowerShell kan du utföra kommandokörning i strängen med en särskild syntax. På så sätt kan vi hämta egenskaperna för dessa objekt och köra andra kommandon för att hämta ett värde.

$message = "Time: $($directory.CreationTime)"

Detta fungerar bra för vissa situationer, men det kan bli lika galet som sammanlänkning om du bara har några variabler.

Kommandokörning

Du kan köra kommandon i en sträng. Även om jag har det här alternativet, gillar jag det inte. Det blir rörigt snabbt och svårt att felsöka. Antingen kör jag kommandot och sparar till en variabel eller använder en formatsträng.

$message = "Date: $(Get-Date)"

Formatsträng

.NET har ett sätt att formatera strängar som jag tycker är ganska lätt att arbeta med. Låt mig först visa den statiska metoden för den innan jag visar dig PowerShell-genvägen för att göra samma sak.

# .NET string format string
[string]::Format('Hello, {0} {1}.',$first,$last)

# PowerShell format string
'Hello, {0} {1}.' -f $first, $last

Det som händer här är att strängen parsas för token och {0}{1}, och sedan använder den talet för att välja från de värden som anges. Om du vill upprepa ett värde någon plats i strängen kan du återanvända det värdets nummer.

Ju mer komplicerad strängen blir, desto mer värde får du ut av den här metoden.

Formatera värden som matriser

Om formatraden blir för lång kan du placera dina värden i en matris först.

$values = @(
    "Kevin"
    "Marquette"
)
'Hello, {0} {1}.' -f $values

Det här är inte splatting eftersom jag skickar in hela matrisen, men idén är liknande.

Avancerad formatering

Jag kallade avsiktligt dessa för att komma från .NET eftersom det finns många formateringsalternativ som redan är väldokumenterade på den. Det finns inbyggda sätt att formatera olika datatyper.

"{0:yyyyMMdd}" -f (Get-Date)
"Population {0:N0}" -f  8175133
20211110
Population 8,175,133

Jag ska inte gå in på dem men jag ville bara låta dig veta att detta är en mycket kraftfull formatering motor om du behöver det.

Koppla strängar

Ibland vill du faktiskt sammanfoga en lista med värden tillsammans. Det finns en -join operatör som kan göra det åt dig. Du kan även ange ett tecken som ska kopplas mellan strängarna.

$servers = @(
    'server1'
    'server2'
    'server3'
)

$servers  -join ','

Om du vill använda -join vissa strängar utan avgränsare måste du ange en tom sträng ''. Men om det är allt du behöver finns det ett snabbare alternativ.

[string]::Concat('server1','server2','server3')
[string]::Concat($servers)

Det är också värt att påpeka att du också -split kan strängar.

Join-Path

Detta förbises ofta, men en bra cmdlet för att skapa en filsökväg.

$folder = 'Temp'
Join-Path -Path 'c:\windows' -ChildPath $folder

Det fantastiska med detta är att det fungerar som de omvända snedstrecken korrekt när de sätter ihop värdena. Detta är särskilt viktigt om du tar värden från användare eller konfigurationsfiler.

Detta går också bra med Split-Path och Test-Path. Jag täcker också dessa i mitt inlägg om att läsa och spara till filer.

Strängar är matriser

Jag måste nämna att lägga till strängar här innan jag fortsätter. Kom ihåg att en sträng bara är en matris med tecken. När du lägger till flera strängar tillsammans skapas en ny matris varje gång.

Titta på det här exemplet:

$message = "Numbers: "
foreach($number in 1..10000)
{
    $message += " $number"
}

Det ser väldigt grundläggande ut, men det du inte ser är att varje gång en sträng läggs till $message i den skapas en helt ny sträng. Minnet allokeras, data kopieras och det gamla tas bort. Inte en stor sak när det bara görs ett par gånger, men en loop som denna skulle verkligen exponera problemet.

StringBuilder

StringBuilder är också mycket populärt för att skapa stora strängar från många mindre strängar. Anledningen är att den bara samlar in alla strängar som du lägger till i den och bara sammanfogar dem alla i slutet när du hämtar värdet.

$stringBuilder = New-Object -TypeName "System.Text.StringBuilder"

[void]$stringBuilder.Append("Numbers: ")
foreach($number in 1..10000)
{
    [void]$stringBuilder.Append(" $number")
}
$message = $stringBuilder.ToString()

Återigen är detta något som jag kontaktar .NET för. Jag använder det inte ofta längre men det är bra att veta att det finns där.

Delineation med klammerparenteser

Detta används för suffixsammanfogning i strängen. Ibland har variabeln inte en ren ordgräns.

$test = "Bet"
$tester = "Better"
Write-Host "$test $tester ${test}ter"

Tack /u/real_parbold för den.

Här är ett alternativ till den här metoden:

Write-Host "$test $tester $($test)ter"
Write-Host "{0} {1} {0}ter" -f $test, $tester

Jag använder personligen formatsträng för detta, men det är bra att veta om du ser det i naturen.

Hitta och ersätta token

Även om de flesta av dessa funktioner begränsar ditt behov av att distribuera din egen lösning, finns det tillfällen då du kan ha stora mallfiler där du vill ersätta strängar inuti.

Anta att du har hämtat en mall från en fil som innehåller mycket text.

$letter = Get-Content -Path TemplateLetter.txt -RAW
$letter = $letter -replace '#FULL_NAME#', 'Kevin Marquette'

Du kan ha många token att ersätta. Tricket är att använda en mycket distinkt token som är lätt att hitta och ersätta. Jag brukar använda ett specialtecken i båda ändar för att skilja det åt.

Jag hittade nyligen ett nytt sätt att närma sig detta. Jag bestämde mig för att lämna det här avsnittet här eftersom detta är ett mönster som ofta används.

Ersätta flera token

När jag har en lista över token som jag behöver ersätta, tar jag en mer allmän metod. Jag skulle placera dem i en hashtable och iterera över dem för att göra ersättningen.

$tokenList = @{
    Full_Name = 'Kevin Marquette'
    Location = 'Orange County'
    State = 'CA'
}

$letter = Get-Content -Path TemplateLetter.txt -RAW
foreach( $token in $tokenList.GetEnumerator() )
{
    $pattern = '#{0}#' -f $token.key
    $letter = $letter -replace $pattern, $token.Value
}

Dessa token kan läsas in från JSON eller CSV om det behövs.

ExecutionContext ExpandString

Det finns ett smart sätt att definiera en ersättningssträng med enkla citattecken och expandera variablerna senare. Titta på det här exemplet:

$message = 'Hello, $Name!'
$name = 'Kevin Marquette'
$string = $ExecutionContext.InvokeCommand.ExpandString($message)

Anropet till .InvokeCommand.ExpandString i den aktuella körningskontexten använder variablerna i det aktuella omfånget för ersättning. Det viktigaste här är att $message kan definieras mycket tidigt innan variablerna ens finns.

Om vi utökar det bara lite kan vi utföra den här ersättningen om och om för många olika värden.

$message = 'Hello, $Name!'
$nameList = 'Mark Kraus','Kevin Marquette','Lee Dailey'
foreach($name in $nameList){
    $ExecutionContext.InvokeCommand.ExpandString($message)
}

För att fortsätta med den här idén; Du kan importera en stor e-postmall från en textfil för att göra detta. Jag måste tacka Mark Kraus för detta förslag.

Vad som än fungerar bäst för dig

Jag är ett fan av metoden formatsträng. Jag gör definitivt detta med de mer komplicerade strängarna eller om det finns flera variabler. På något som är mycket kort, kan jag använda någon av dessa.

Något mer?

Jag täckte mycket mark på den här. Min förhoppning är att du går iväg och lär dig något nytt.