Allt du ville veta om instruktionen if

Liksom många andra språk har PowerShell instruktioner för villkorlig körning av kod i dina skript. En av dessa instruktioner är If-instruktionen . Idag tar vi en djupdykning i ett av de mest grundläggande kommandona i PowerShell.

Anteckning

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

Villkorsstyrd körning

Dina skript behöver ofta fatta beslut och utföra olika logik baserat på dessa beslut. Detta är vad jag menar med villkorsstyrd körning. Du har en instruktion eller ett värde att utvärdera och kör sedan ett annat kodavsnitt baserat på utvärderingen. Det här är precis vad -instruktionen if gör.

Instruktionen if

Här är ett grundläggande exempel på -instruktionen if :

$condition = $true
if ( $condition )
{
    Write-Output "The condition was true"
}

Det första instruktionen if gör är att utvärdera uttrycket inom parentes. Om den utvärderas till $truekör scriptblock den i klammerparenteserna. Om värdet var $falseskulle det hoppa över skriptblocket.

I föregående exempel utvärderade instruktionen if bara variabeln $condition . Det var $true och skulle ha kört Write-Output kommandot i skriptblocket.

På vissa språk kan du placera en enda kodrad efter -instruktionen if och den körs. Så är inte fallet i PowerShell. Du måste ange en fullständig scriptblock med klammerparenteser för att det ska fungera korrekt.

Jämförelseoperatorer

Den vanligaste användningen av -instruktionen if för är att jämföra två objekt med varandra. PowerShell har särskilda operatorer för olika jämförelsescenarier. När du använder en jämförelseoperator jämförs värdet till vänster med värdet till höger.

-eq för likhet

-eq Gör en likhetskontroll mellan två värden för att se till att de är lika med varandra.

$value = Get-MysteryValue
if ( 5 -eq $value )
{
    # do something
}

I det här exemplet tar jag ett känt värde för 5 och jämför det med mitt $value för att se om de matchar.

Ett möjligt användningsfall är att kontrollera statusen för ett värde innan du vidtar en åtgärd för det. Du kan hämta en tjänst och kontrollera att statusen kördes innan du anropade Restart-Service den.

Det är vanligt på andra språk som C# att använda == för likhet (t.ex. 5 == $value) men det fungerar inte med PowerShell. Ett annat vanligt misstag som personer gör är att använda likhetstecknet (t.ex. 5 = $value) som är reserverat för att tilldela värden till variabler. Genom att placera ditt kända värde till vänster gör det misstaget mer besvärligt att göra.

Den här operatorn (och andra) har några varianter.

  • -eq skiftlägesokänslig likhet
  • -ieq skiftlägesokänslig likhet
  • -ceq skiftlägeskänslig likhet

-ne inte lika med

Många operatorer har en relaterad operator som söker efter det motsatta resultatet. -ne verifierar att värdena inte är lika med varandra.

if ( 5 -ne $value )
{
    # do something
}

Använd detta för att se till att åtgärden bara körs om värdet inte 5är . Ett bra användningsfall där skulle vara att kontrollera om en tjänst var i körningstillstånd innan du försöker starta den.

Variationer:

  • -ne skiftlägesokänsligt inte lika med
  • -ine skiftlägesokänsligt inte lika med
  • -cne skiftlägeskänslig inte lika med

Det här är invertera varianter av -eq. Jag grupperar dessa typer tillsammans när jag listar varianter för andra operatorer.

-gt -ge -lt -le för större än eller mindre än

Dessa operatorer används när du kontrollerar om ett värde är större eller mindre än ett annat värde. Ställningen -gt -ge -lt -le för GreaterThan, GreaterThanOrEqual, LessThan och LessThanOrEqual.

if ( $value -gt 5 )
{
    # do something
}

Variationer:

  • -gt större än
  • -igt större än, skiftlägesokänslig
  • -cgt större än skiftlägeskänsligt
  • -ge större än eller lika med
  • -ige större än eller lika med skiftlägesokänslig
  • -cge större än eller lika med skiftlägeskänslig
  • -lt mindre än
  • -ilt mindre än, skiftlägesokänslig
  • -clt mindre än, skiftlägeskänslig
  • -le mindre än eller lika med
  • -ile mindre än eller lika med skiftlägesokänslig
  • -cle mindre än eller lika med skiftlägeskänslig

Jag vet inte varför du skulle använda skiftlägeskänsliga och okänsliga alternativ för dessa operatörer.

-like jokertecken matchningar

PowerShell har en egen jokerteckenbaserad mönstermatchningssyntax och du kan använda den med operatorn -like . Dessa jokerteckenmönster är ganska grundläggande.

  • ? matchar ett enskilt tecken
  • * matchar valfritt antal tecken
$value = 'S-ATX-SQL01'
if ( $value -like 'S-*-SQL??')
{
    # do something
}

Det är viktigt att påpeka att mönstret matchar hela strängen. Om du behöver matcha något i mitten av strängen måste du placera i * båda ändarna av strängen.

$value = 'S-ATX-SQL02'
if ( $value -like '*SQL*')
{
    # do something
}

Variationer:

  • -like skiftlägesokänsligt jokertecken
  • -ilike skiftlägesokänsligt jokertecken
  • -clike skiftlägeskänsligt jokertecken
  • -notlike skiftlägesokänsligt jokertecken matchas inte
  • -inotlike skiftlägesokänsligt jokertecken matchas inte
  • -cnotlike skiftlägeskänsligt jokertecken matchas inte

-matcha reguljärt uttryck

Med -match operatorn kan du kontrollera en sträng för en reguljär uttrycksbaserad matchning. Använd detta när jokerteckenmönstren inte är tillräckligt flexibla för dig.

$value = 'S-ATX-SQL01'
if ( $value -match 'S-\w\w\w-SQL\d\d')
{
    # do something
}

Ett regexmönster matchar var som helst i strängen som standard. Så du kan ange en delsträng som du vill ska matchas så här:

$value = 'S-ATX-SQL01'
if ( $value -match 'SQL')
{
    # do something
}

Regex är ett eget komplext språk och värt att titta på. Jag talar mer om -match och många sätt att använda regex i en annan artikel.

Variationer:

  • -match skiftlägesokänslig regex
  • -imatch skiftlägesokänslig regex
  • -cmatch skiftlägeskänslig regex
  • -notmatch skiftlägesokänsligt regex matchas inte
  • -inotmatch skiftlägesokänsligt regex matchas inte
  • -cnotmatch skiftlägeskänslig regex matchas inte

-är av typen

Du kan kontrollera värdets typ med operatorn -is .

if ( $value -is [string] )
{
    # do something
}

Du kan använda detta om du arbetar med klasser eller accepterar olika objekt över pipelinen. Du kan ha antingen en tjänst eller ett tjänstnamn som indata. Kontrollera sedan om du har en tjänst och hämta tjänsten om du bara har namnet.

if ( $Service -isnot [System.ServiceProcess.ServiceController] )
{
    $Service = Get-Service -Name $Service
}

Variationer:

  • -is av typen
  • -isnot inte av typen

Samlingsoperatorer

När du använder de tidigare operatorerna med ett enda värde blir $true resultatet eller $false. Detta hanteras något annorlunda när du arbetar med en samling. Varje objekt i samlingen utvärderas och operatorn returnerar varje värde som utvärderas till $true.

PS> 1,2,3,4 -eq 3
3

Detta fungerar fortfarande korrekt i en if instruktion. Så ett värde returneras av operatorn och sedan är $truehela -instruktionen .

$array = 1..6
if ( $array -gt 3 )
{
    # do something
}

Det finns en liten fälla som gömmer sig i detaljerna här som jag måste påpeka. När du använder operatorn -ne på det här sättet är det enkelt att av misstag titta på logiken baklänges. Om du använder -ne med en samling returneras $true om något objekt i samlingen inte matchar ditt värde.

PS> 1,2,3 -ne 4
1
2
3

Detta kan se ut som ett smart trick, men vi har operatörer -contains och -in som hanterar detta mer effektivt. Och -notcontains gör vad du förväntar dig.

-innehåller

Operatorn -contains kontrollerar samlingen efter ditt värde. Så snart den hittar en matchning returneras $true.

$array = 1..6
if ( $array -contains 3 )
{
    # do something
}

Det här är det bästa sättet att se om en samling innehåller ditt värde. Om du använder Where-Object (eller -eq) går hela listan varje gång och är betydligt långsammare.

Variationer:

  • -contains skiftlägesokänslig matchning
  • -icontains skiftlägesokänslig matchning
  • -ccontains skiftlägeskänslig matchning
  • -notcontains skiftlägesokänsligt matchas inte
  • -inotcontains skiftlägesokänsligt matchas inte
  • -cnotcontains skiftlägeskänslig matchas inte

-in

Operatorn -in är precis som operatorn -contains förutom att samlingen finns på höger sida.

$array = 1..6
if ( 3 -in $array )
{
    # do something
}

Variationer:

  • -in skiftlägesokänslig matchning
  • -iin skiftlägesokänslig matchning
  • -cin skiftlägeskänslig matchning
  • -notin skiftlägesokänsligt matchas inte
  • -inotin skiftlägesokänsligt matchas inte
  • -cnotin skiftlägeskänslig matchas inte

Logiska operatorer

Logiska operatorer används för att invertera eller kombinera andra uttryck.

-inte

Operatorn -not vänder ett uttryck från $false till $true eller från $true till $false. Här är ett exempel där vi vill utföra en åtgärd när Test-Path är $false.

if ( -not ( Test-Path -Path $path ) )

De flesta operatorer som vi pratade om har en variant där du inte behöver använda operatorn -not . Men det finns fortfarande tillfällen då det är användbart.

! operator

Du kan använda ! som alias för -not.

if ( -not $value ){}
if ( !$value ){}

Du kan se ! hur personer som kommer från ett annat språk som C#används mer. Jag föredrar att skriva ut det eftersom jag har svårt att se när jag snabbt tittar på mina skript.

-och

Du kan kombinera uttryck med operatorn -and . När du gör det måste båda sidor vara $true för att hela uttrycket ska vara $true.

if ( ($age -gt 13) -and ($age -lt 55) )

I det exemplet $age måste vara 13 eller äldre för vänster sida och mindre än 55 för höger sida. Jag har lagt till extra parenteser för att göra det tydligare i det exemplet, men de är valfria så länge uttrycket är enkelt. Här är samma exempel utan dem.

if ( $age -gt 13 -and $age -lt 55 )

Utvärderingen sker från vänster till höger. Om det första objektet utvärderas till $falseavslutas det tidigt och utför inte rätt jämförelse. Det här är praktiskt när du behöver se till att det finns ett värde innan du använder det. Till exempel Test-Path genererar ett fel om du ger det en $null sökväg.

if ( $null -ne $path -and (Test-Path -Path $path) )

-eller

Gör -or att du kan ange två uttryck och returnerar $true om någon av dem är $true.

if ( $age -le 13 -or $age -ge 55 )

Precis som med operatorn -and sker utvärderingen från vänster till höger. Förutom att om den första delen är $true, så är $true hela -instruktionen och den bearbetar inte resten av uttrycket.

Anteckna också hur syntaxen fungerar för dessa operatorer. Du behöver två separata uttryck. Jag har sett användare försöka göra något liknande $value -eq 5 -or 6 utan att inse sitt misstag.

-xor exklusivt eller

Den här är lite ovanlig. -xor tillåter endast ett uttryck att utvärdera till $true. Så om båda objekten är $false eller båda objekten är $trueär $falsehela uttrycket . Ett annat sätt att titta på detta är uttrycket är bara $true när resultatet av uttrycket är annorlunda.

Det är sällsynt att någon någonsin skulle använda denna logiska operator och jag kan inte tänka upp ett bra exempel på varför jag någonsin skulle använda det.

Bitvisa operatorer

Bitwiseoperatorer utför beräkningar på bitarna i värdena och skapar ett nytt värde som resultat. Att lära bitwiseoperatorer ligger utanför omfånget för den här artikeln, men här är listan över dem.

  • -band binärt AND
  • -bor binärt ELLER
  • -bxor binärt exklusivt OR
  • -bnot binärt INTE
  • -shl skift vänster
  • -shr skift höger

PowerShell-uttryck

Vi kan använda normal PowerShell i villkorssatsen.

if ( Test-Path -Path $Path )

Test-Path returnerar $true eller $false när den körs. Detta gäller även kommandon som returnerar andra värden.

if ( Get-Process Notepad* )

Det utvärderas till $true om det finns en returnerad process och $false om det finns något. Det är helt giltigt att använda pipelineuttryck eller andra PowerShell-instruktioner som detta:

if ( Get-Process | Where Name -eq Notepad )

Dessa uttryck kan kombineras med operatorerna -and och -or , men du kan behöva använda parenteser för att dela upp dem i underuttryck.

if ( (Get-Process) -and (Get-Service) )

Söker efter $null

Att inte ha något resultat eller ett $null värde utvärderas till $false i -instruktionen if . När du söker specifikt efter $nullär det bästa praxis att placera $null på vänster sida.

if ( $null -eq $value )

Det finns en hel del nyanser när du hanterar $null värden i PowerShell. Om du är intresserad av att dyka djupare, jag har en artikel om allt du ville veta om $null.

Variabeltilldelning inom villkoret

Jag glömde nästan att lägga till den här tills Prasoon Karunan V påminde mig om det.

if ($process=Get-Process notepad -ErrorAction ignore) {$process} else {$false}

Normalt när du tilldelar ett värde till en variabel skickas inte värdet till pipelinen eller konsolen. När du utför en variabeltilldelning i ett underuttryck skickas den vidare till pipelinen.

PS> $first = 1
PS> ($second = 2)
2

Se hur tilldelningen $first inte har några utdata och tilldelningen $second gör? När en tilldelning görs i en if -instruktion körs den precis som tilldelningen $second ovan. Här är ett rent exempel på hur du kan använda det:

if ( $process = Get-Process Notepad* )
{
    $process | Stop-Process
}

Om $process tilldelas ett värde stoppas $true - $process instruktionen.

Se till att du inte blandar ihop det här med -eq eftersom det inte är en likhetskontroll. Det här är en mer obskyr funktion som de flesta inte inser fungerar på det här sättet.

Variabeltilldelning från skriptblocket

Du kan också använda instruktionen if scriptblock för att tilldela ett värde till en variabel.

$discount = if ( $age -ge 55 )
{
    Get-SeniorDiscount
}
elseif ( $age -le 13 )
{
    Get-ChildDiscount
}
else
{
    0.00
}

Varje skriptblock skriver resultatet av kommandona, eller värdet, som utdata. Vi kan tilldela resultatet av -instruktionen if till variabeln $discount . Det exemplet kan lika enkelt ha tilldelat dessa värden till variabeln $discount direkt i varje scriptblock. Jag kan inte säga att jag använder detta med instruktionen if ofta, men jag har ett exempel där jag använde detta nyligen.

Alternativ körningssökväg

Med -instruktionen if kan du ange en åtgärd för inte bara när -instruktionen är $true, utan även för när den är $false. Det är här som instruktionen else kommer in i bilden.

else

-instruktionen else är alltid den sista delen av -instruktionen när den if används.

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
else
{
    Write-Warning "$path doesn't exist or isn't a file."
}

I det här exemplet kontrollerar $path vi att det är en fil. Om vi hittar filen flyttar vi den. Annars skriver vi en varning. Den här typen av förgreningslogik är mycket vanlig.

Kapslad om

Instruktionen if och else tar ett skriptblock, så att vi kan placera alla PowerShell-kommandon i dem, inklusive en annan if instruktion. På så sätt kan du använda mycket mer komplicerad logik.

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
else
{
    if ( Test-Path -Path $Path )
    {
        Write-Warning "A file was required but a directory was found instead."
    }
    else
    {
        Write-Warning "$path could not be found."
    }
}

I det här exemplet testar vi den lyckliga sökvägen först och vidtar sedan åtgärder för den. Om det misslyckas gör vi en ny kontroll och tillhandahåller mer detaljerad information till användaren.

elseif

Vi är inte begränsade till bara en enda villkorsstyrd kontroll. Vi kan kedja if ihop och else instruktioner i stället för att kapsla dem med hjälp av -instruktionen elseif .

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
elseif ( Test-Path -Path $Path )
{
    Write-Warning "A file was required but a directory was found instead."
}
else
{
    Write-Warning "$path could not be found."
}

Körningen sker uppifrån och ned. Den översta if instruktionen utvärderas först. Om det är $falseflyttas den ned till nästa elseif eller else i listan. Den sista else är standardåtgärden att vidta om ingen av de andra returnerar $true.

växla

I det här läget måste jag nämna uttalandet switch . Det ger en alternativ syntax för att göra flera jämförelser med ett värde. switchMed anger du ett uttryck och resultatet jämförs med flera olika värden. Om något av dessa värden matchar körs det matchande kodblocket. Ta en titt på det här exemplet:

$itemType = 'Role'
switch ( $itemType )
{
    'Component'
    {
        'is a component'
    }
    'Role'
    {
        'is a role'
    }
    'Location'
    {
        'is a location'
    }
}

Det finns tre möjliga värden som kan matcha $itemType. I det här fallet matchar det med Role. Jag använde ett enkelt exempel bara för att ge dig viss exponering för operatören switch . Jag pratar mer om allt du någonsin ville veta om switch-instruktionen i en annan artikel.

Matris infogad

Jag har en funktion som heter Invoke-SnowSql som startar en körbar fil med flera kommandoradsargument. Här är ett klipp från den funktionen där jag skapar argumentmatrisen.

$snowSqlParam = @(
    '--accountname', $Endpoint
    '--username', $Credential.UserName
    '--option', 'exit_on_error=true'
    '--option', 'output_format=csv'
    '--option', 'friendly=false'
    '--option', 'timing=false'
    if ($Debug)
    {
        '--option', 'log_level=DEBUG'
    }
    if ($Path)
    {
        '--filename', $Path
    }
    else
    {
        '--query', $singleLineQuery
    }
)

$Debug Variablerna och $Path är parametrar för funktionen som tillhandahålls av slutanvändaren. Jag utvärderar dem infogade i initieringen av min matris. Om $Debug är sant hamnar $snowSqlParam dessa värden på rätt plats. Samma gäller för variabeln $Path .

Förenkla komplexa åtgärder

Det är oundvikligt att du stöter på en situation som har alldeles för många jämförelser för att kontrollera och din If instruktion rullar långt från höger sida av skärmen.

$user = Get-ADUser -Identity $UserName
if ( $null -ne $user -and $user.Department -eq 'Finance' -and $user.Title -match 'Senior' -and $user.HomeDrive -notlike '\\server\*' )
{
    # Do Something
}

De kan vara svåra att läsa och det gör dig mer benägen att göra misstag. Det finns några saker vi kan göra åt det.

Radfortsättning

Det finns vissa operatorer i PowerShell som låter dig omsluta kommandot till nästa rad. De logiska operatorerna -and och -or är bra operatorer att använda om du vill dela upp uttrycket i flera rader.

if ($null -ne $user -and
    $user.Department -eq 'Finance' -and
    $user.Title -match 'Senior' -and
    $user.HomeDrive -notlike '\\server\*'
)
{
    # Do Something
}

Det är fortfarande mycket som händer där, men att placera varje pjäs på sin egen linje gör stor skillnad. Jag använder vanligtvis detta när jag får mer än två jämförelser eller om jag måste bläddra till höger för att läsa någon av logiken.

Förberäkna resultat

Vi kan ta bort instruktionen från -instruktionen if och bara kontrollera resultatet.

$needsSecureHomeDrive = $null -ne $user -and
    $user.Department -eq 'Finance' -and
    $user.Title -match 'Senior' -and
    $user.HomeDrive -notlike '\\server\*'

if ( $needsSecureHomeDrive )
{
    # Do Something
}

Detta känns bara mycket renare än i föregående exempel. Du får också möjlighet att använda ett variabelnamn som förklarar vad det är som du verkligen kontrollerar. Det här är också ett exempel på självdokumenterande kod som sparar onödiga kommentarer.

Flera if-instruktioner

Vi kan dela upp detta i flera instruktioner och kontrollera dem en i taget. I det här fallet använder vi en flagga eller en spårningsvariabel för att kombinera resultaten.


$skipUser = $false

if( $null -eq $user )
{
    $skipUser = $true
}

if( $user.Department -ne 'Finance' )
{
    Write-Verbose "isn't in Finance department"
    $skipUser = $true
}

if( $user.Title -match 'Senior' )
{
    Write-Verbose "Doesn't have Senior title"
    $skipUser = $true
}

if( $user.HomeDrive -like '\\server\*' )
{
    Write-Verbose "Home drive already configured"
    $skipUser = $true
}

if ( -not $skipUser )
{
    # do something
}

Jag behövde invertera logiken för att få flagglogik att fungera korrekt. Varje utvärdering är en enskild if instruktion. Fördelen med detta är att när du felsöker kan du se exakt vad logiken gör. Jag kunde lägga till mycket bättre verbositet på samma gång.

Den uppenbara nackdelen är att det är så mycket mer kod att skriva. Koden är mer komplex att titta på eftersom den tar en enda logikrad och exploderar den till 25 eller fler rader.

Använda funktioner

Vi kan också flytta all valideringslogik till en funktion. Titta på hur rent det här ser ut i korthet.

if ( Test-SecureDriveConfiguration -ADUser $user )
{
    # do something
}

Du måste fortfarande skapa funktionen för att utföra verifieringen, men den gör den här koden mycket enklare att arbeta med. Det gör den här koden enklare att testa. I dina tester kan du simulera anropet till Test-ADDriveConfiguration och du behöver bara två tester för den här funktionen. En där den returnerar $true och en där den returnerar $false. Det är enklare att testa den andra funktionen eftersom den är så liten.

Den här funktionens brödtext kan fortfarande vara den one-liner som vi började med eller den exploderande logik som vi använde i det sista avsnittet. Detta fungerar bra för båda scenarierna och gör att du enkelt kan ändra implementeringen senare.

Felhantering

En viktig användning av -instruktionen if är att söka efter feltillstånd innan du stöter på fel. Ett bra exempel är att kontrollera om det redan finns en mapp innan du försöker skapa den.

if ( -not (Test-Path -Path $folder) )
{
    New-Item -Type Directory -Path $folder
}

Jag vill säga att om du förväntar dig att ett undantag ska hända, så är det egentligen inte ett undantag. Kontrollera dina värden och verifiera dina villkor där du kan.

Om du vill dyka lite mer i faktisk undantagshantering har jag en artikel om allt du någonsin velat veta om undantag.

Sista ord

-instruktionen if är en så enkel instruktion men är en grundläggande del av PowerShell. Du kommer att använda detta flera gånger i nästan alla skript som du skriver. Jag hoppas att du har en bättre förståelse än du hade tidigare.