MSXFAQ MeetNow aktiv: Komm doch einfach dazu.

Rekursion oder Stack?

Rekursive Funktionen sind schön einfach, wenn man verschachtelte Aufgaben durch laufen muss. Aber sie haben auch ihre Besonderheiten bezüglich dem Scope der Variablen und Fehlerbearbeitung. Ich zeige das am Beispiel der Verarbeitung einer größeren Verzeichnisstruktur eines SMB-Servers. Bei Migrationen habe ich oft die Frage, wie man Berechtigungen auf einem Dateiserver umschreibt und dazu muss ich per Skript nur nur ein Verzeichnis sondern ganze Verzeichnisbäume durchlaufen. Und nicht immer habe ich alle Berechtigungen.

-Recurse Parameter

Der einfachste Weg erscheint eine Suche mit Powershell und "Get-ChildItem", welches eine Option "-Recurse" mitliefert.

Foreach ($Item in (Get-ChildItem -Path "C:\Shares" -Recurse)) {
   if ($item.PSIsContainer) {
      Write-Host "Verz:$($item.fullname)"
   }
   Else {
      Write-Host "File:$($item.fullname)"
   }
}

Der Code ist einfach und übersichtlich, durchaus auch flott aber Ich habe es immer wieder erlebt, dass der Code abgebrochen ist, z.B.: weil Berechtigungen gefehlt oder andere Fehler die Ausführung unterbrochen haben. Das zweite Problem ist, dass die Verarbeitung erst startet, wenn Get-Childitem alle Verzeichnisse und Dateien durchlaufen hat. Das fällt bei kleinen Strukturen kaum auf aber wenn Sie richtig große Quellen durchlaufen, dann dauert es Minuten, ehe eine Verarbeitung startet. Zudem liefert "Get-ChildItem natürlich PowerShell-Objekte zurück, die durchaus Speicherplatz belegen.

Recursive Funktion

Ich habe daher zuerst versucht, die Rekursion selbst zu schreiben. Auch das ist eigentlich einfach. Sie übergeben den Pfad an eine Funktion, die dann sich selbst wieder aufrufe, wenn Sie auf einen Pfad stößt.

function Get-ChildItemrecurse ($Path)
{
   Foreach ($Item in (get-childitem -Path $path)) {
      if ($item.PSIsContainer) {
         Write-Host "Verz:$($item.fullname)"
         Get-ChilditemRecurse -Path $item.fullname
      }
      Else {
         Write-Host "File:$($item.fullname)"
      }
   }
}

Get-ChilditemRecurse -Path "C:\Shares"

Da ich hier die Rekursion selbst durchführe, kann ich natürlich auch Fehler in der Rekursion einfacher abfangen und behandeln, ohne gleich den kompletten Lauf abzubrechen. Allerdings braucht diese Funktion natürlich auch Speicherplatz, wenn ich die entsprechenden Aktionen direkt in der rekursiven Funktion mit unterbringe.

Linear mit Stack

Rekursion ist mit modernen Programmiersprachen kein Problem aber bei der Fehlersuche kann es anstrengend sein, da jeder Aufruf eine neue Umgebung mit Variablen erstellt. Das kann auch Speicher belegen. Wenn ich als Mensch aber einen klassischen Ordner samt Unterordner sequentiell durchlaufe, dann kann das ein Code auch. Ich muss mit einfach nur die Verzeichnisse irgendwo quasi wie eine noch anstehende Aufgabe merken und danach abarbeiten. Auch das ist mit PowerShell und einem "Stack" möglich:

$DirectoryStack = [System.Collections.Generic.Stack[string]]::new() 
$DirectoryStack.Push("C:\Shares")

while ($DirectoryStack.Count -gt 0) {
   $currentDir = $DirectoryStack.Pop()
   Write-Host "ADD:Dir $($currentDir)"
   try {
      foreach ($item in Get-ChildItem -LiteralPath $currentDir -Force) {
         if ($item.PSIsContainer) {
            Write-Host "Verz:$($item.fullname)"
            $DirectoryStack.Push($item.FullName)
         }
         else {
            Write-Host "File:$($item.fullname)"
         }
      }
   }
   catch {
      Write-Host "SKIP:Fehler beim Zugriff auf $($currentDir): $_" -ForegroundColor Red
   }
}

Wenn das Skript bei Der Verarbeitung eines Verzeichnisses ein Unterverzeichnis findet, dann addiert es dieses einfach auf den Stack. Die "While"-Schleife holt sich dann die Verzeichnisse wieder vom Stack um diese zu bearbeiten.

Linear mit Hashable, Array und Co

Wenn ihnen ein Stack zu suspekt ist, dann können Sie natürlich auch eine Hashtable, Ein Array oder eine Collection dafür verwenden. Denken Sie aber dabei an die interne Verarbeitung und Ablage der Variablen durch den jeweiligen Compiler. Einige Variablen wachsen einfach und andere Werten bei jeder "Add"-Aktion erst in eine neue größere Struktur kopiert. Es macht auch einen Unterschied, ob Sie den Pfad einfach als String oder das jeweilige Objekt als PowerShell-Objekt speichern.

Weitere Links