PowerShell – Cicli

Ora che i nostri script sanno prendere decisioni, è il momento di insegnargli a ripetere. Rinominare cento file, leggere ogni riga di un log, controllare una per una tutta le cartelle di un disco: nessuno ha voglia di scrivere lo stesso comando cento volte, ed è qui che entrano in gioco i cicli.

In PowerShell i cicli si dividono in due famiglie. Quelli di tipo Loop (for, while, do) ripetono un blocco di codice finché una condizione resta vera, oppure per un numero prestabilito di volte. I cicli ForEach, invece, ripetono il blocco per ogni elemento di una collezione: file, processi, righe di testo, numeri.

Avete già dato un’occhiata ai miei articoli PowerShell – Introduzione, PowerShell – Gestione del File System e PowerShell – Operatori e condizioni?

Ripassiamo la cartella di prova

Useremo la stessa cartella di prova del capitolo precedente. Se non l’avete più, ricreatela con questo blocco (vedi il capitolo su operatori e condizioni per i dettagli):

Nel blocco c’è già un foreach, l’avete notato? Lo spiegheremo tra poco. Per ora verifichiamo cosa contiene la cartella:

Figura 1: la cartella di prova

I cicli Loop: for, while e do

Contare con for

Il ciclo for serve quando sappiamo quante volte ripetere un blocco. Dentro le parentesi tonde ci sono tre parti separate da punto e virgola: l’inizializzazione (eseguita una volta sola all’inizio), la condizione (controllata prima di ogni giro) e l’incremento (eseguito alla fine di ogni giro).

Sintassi:

$i++ aumenta la variabile di 1, $i -= 3 la diminuisce di 3: possiamo contare come ci pare, anche all’indietro. Se ci serve solo una sequenza di numeri, PowerShell offre l’operatore di intervallo . . :

Figura 2: il ciclo for e l’operatore di intervallo

Ripetere finché serve: while

Il ciclo while ripete il blocco finché la condizione è vera e la controlla prima di ogni giro: se è falsa fin dall’inizio il blocco non viene eseguito nemmeno una volta. Lo usiamo quando non sappiamo in anticipo quanti giri serviranno.

Figura 3: il ciclo while: le potenze di 3 fino a 100

Attenzione: dentro un while qualcosa deve prima o poi rendere falsa la condizione. Se dimentichiamo di modificare $n il ciclo non finisce mai e la console resta bloccata: per interromperlo si preme CTRL+C.

Almeno una volta: do-while e do-until

Nei cicli do la condizione viene controllata dopo il blocco, che quindi viene eseguito sempre almeno una volta. do … while ripete finché la condizione è vera; do … until ripete finché la condizione è falsa, cioè si ferma quando diventa vera.

Figura 4: while contro do-while, e do-until

Il primo while non stampa nulla, perché 10 -lt 5 è falso fin dall’inizio; il do-while invece esegue il blocco una volta prima di accorgersene. È utile, per esempio, quando dobbiamo chiedere un dato all’utente e ripetere la domanda finché la risposta non è valida.

Saltare o uscire: continue e break

Dentro qualunque ciclo possiamo usare due parole chiave: continue salta il resto del giro corrente e passa a quello successivo, mentre break esce dal ciclo immediatamente. Sono gli stessi break visti nello switch.

Combinato con while ($true), che per definizione non finisce mai, break permette di scrivere cicli che si fermano quando succede qualcosa all’interno del blocco:

Figura 5: continue, break e il ciclo “infinito” con uscita

Riepilogo dei cicli Loop

Ciclo Quando si usa Controlla la condizione
for Si sa quante volte ripetere Prima di ogni giro
while Si ripete finché la condizione è vera Prima di ogni giro
do-while Almeno un giro, poi finché vera Dopo ogni giro
do-until Almeno un giro, poi finché falsa Dopo ogni giro

I cicli ForEach

Molto spesso non ci interessa contare, ma fare qualcosa per ogni elemento di un elenco. PowerShell lavora ad oggetti e quasi ogni cmdlet restituisce una collezione, quindi foreach è probabilmente il ciclo che userete di più. Esistono due forme che si chiamano allo stesso modo ma si comportano in modo diverso: l’istruzione foreach e il cmdlet ForEach-Object.

L’istruzione foreach

Si scrive foreach ($elemento in $collezione) { … }: a ogni giro la variabile $elemento contiene l’elemento successivo della collezione. Il nome della variabile lo scegliamo noi, meglio se parlante ($frutto in $frutti).

Sintassi:

Figura 6: foreach su un array e sui file della cartella

L’ultimo esempio unisce due capitoli: dentro il ciclo usiamo un if per agire solo sui file che superano 100 KB. È la combinazione che userete più spesso negli script.

Il cmdlet ForEach-Object (alias: foreach, %)

ForEach-Object fa lo stesso lavoro ma si inserisce in una pipeline: riceve gli oggetti uno alla volta e, per ciascuno, esegue il blocco tra graffe, dove l’oggetto corrente si chiama $_. Ha due alias: foreach e %.

Figura 7: ForEach-Object nella pipeline

NOTA: la parola foreach ha due significati. Se si trova all’inizio di un’istruzione è il ciclo foreach ($x in $y); se si trova dopo una pipe è l’alias di ForEach-Object. PowerShell capisce dal contesto, ma per chi legge il codice può confondere: negli script meglio scrivere ForEach-Object per esteso.

Blocchi Begin, Process ed End

Foreach-Object può eseguire anche codice prima del primo elemento (-Begin) e dopo l’ultimo (-End), mentre -Process è il blocco ripetuto per ogni elemento. Ideale per calcolare totali: inizializziamo in -Begin, sommiamo in -Process e mostriamo il risultato in -End.

Figura 8: somma delle dimensioni con -Begin, -Process e -End

Il carattere backtick (apice rovesciato) a fine riga dice a PowerShell che il comando continua sulla riga successiva: ci permette di non scrivere una riga lunghissima. Attenzione però a non lasciare spazi dopo il backtick.

I metodi .ForEach() e .Where()

Da PowerShell 4.0 ogni collezione ha anche i metodi .ForEach() e .Where(), che applicano un blocco di codice a ogni elemento senza usare la pipeline. Sono compatti e, su grandi quantità di dati, più veloci di ForEach-Object.

Figura 9: i metodi .ForEach() e .Where()

foreach o ForEach-Object?

foreach (istruzione) ForEach-Object (cmdlet)
Sintassi foreach ($x in $lista) { } $lista | ForEach-Object { }
Elemento corrente La variabile scelta ($x) $_
Elaborazione Prima carica tutta la collezione Un elemento alla volta
Memoria Occupa più memoria Occupa meno memoria
Velocità In genere più veloce In genere più lento
Si usa Negli script Nelle pipeline “al volo”

Il fatto che foreach carichi prima tutta la collezione ha una conseguenza importante: possiamo modificare gli elementi mentre ci cicliamo sopra, perché l’elenco di partenza è già stato letto. Proviamo a creare tre file e a rinominarli uno per uno con un contatore:

Figura 10: creare e rinominare file con i cicli

Nel nostro esempio abbiamo aggiunto | Out-Null ai comandi New-Item per nascondere l’output di creazione (potete ometterlo per vedere cosa fanno). Il formato {0:D2} produce un numero a due cifre con lo zero davanti: 01, 02, 03. L’ultima riga ripulisce la cartella di test: ricordate che Remove-Item non usa il cestino.

Esempio pratico completo: totale per estensione

Mettiamo insieme foreach, if e una hashtable (vista nel capitolo sulle variabili) per calcolare quanti byte occupano i file di ciascun tipo. Per ogni file controlliamo se la sua estensione è già nella tabella: se sì sommiamo la dimensione, altrimenti la inseriamo. Un secondo foreach stampa il risultato in ordine alfabetico.

Figura 11: totale dei byte per estensione

Provate a modificarlo: cosa cambia se aggiungete -Recurse a Get-ChildItem? E se volete contare anche quanti file ci sono per ogni estensione?

Consigli Utili per Principianti

  1. Controllate sempre che il ciclo possa finire: in un while qualcosa deve rendere falsa la condizione; se resta bloccato premete CTRL+C.
  1. Preferite foreach a for quando dovete scorrere una collezione: è più leggibile e non rischia errori di indice.
  2. Date nomi parlanti alle variabili: foreach ($file in $files) si legge da solo, foreach ($x in $y) no.
  3. Provate prima con -WhatIf o su una cartella di test: un ciclo che cancella o rinomina agisce su tutti gli elementi, errori compresi.
  4. Usate ForEach-Object per esteso negli script: % è comodo in console, ma rende il codice meno leggibile.
  5. Ricordate continue e break: il primo salta un giro, il secondo esce dal ciclo.

Riepilogo Cicli

Operazione Costrutto Esempio
Ripetere N volte for for ($i=1; $i -le 5; $i++) { }
Ripetere finché vero while while ($n -lt 10) { }
Almeno una volta do-while / do-until do { } while ($n -lt 10)
Saltare un giro continue if ($i -eq 3) { continue }
Uscire dal ciclo break if ($trovato) { break }
Sequenza di numeri .. 1..10
Per ogni elemento foreach foreach ($f in $files) { }
Per ogni oggetto in pipeline ForEach-Object (%) $files | ForEach-Object { $_.Name }
Senza pipeline .ForEach() (1..5).ForEach({ $_ * 2 })

Conclusione

Con i cicli i nostri script smettono di fare una cosa alla volta: possono contare, ripetere, scorrere file e oggetti, uscire quando serve. Insieme a operatori e condizioni abbiamo ora i mattoni fondamentali di qualunque programma.

Ricordate: il modo migliore per imparare è fare pratica! Provate a riscrivere gli esempi dei capitoli precedenti usando un ciclo al posto dei comandi ripetuti.