Tiefe Ordnerstrukturen untersuchen und verarbeiten mit Batch (2019)

marc-olivier-jodoin-0TB3AiOu2g4-unsplash

Kürzlich fragte mich Blogbesucher Sebastian nach Hilfe: Mittels Batch-Skript sollen innerhalb eines Ordners, in einer bestimmten Unterordnertiefe, bestimmte Ordner umbenannt werden. In dem Artikel des Kommentars hatte ich Hilfestellung gegeben, bestimmte Aktionen auf alle Unterordner eines Ordners auszuführen. Die Einschränkung, diese Aktionen nur auf ein bestimmten Level von Unterordnern auszuführen, gab es nicht. Schauen wir uns das mal an.

Auslesen von tiefen Ordnerstrukturen

Die Grundstruktur bleibt erstmal gleich: Mit for /d in ([order]\*) do () werden Befehle auf alle Unterordner eines Ordners ausgeführt. Mit dem zusätzlichen Parameter /r durchsuchen wir rekursiv tiefe Ordnerstrukturen und ignorieren sogar Dateien für mehr Performance.

batch-deep-recursion-folder-manipulation-tiefe-ordnerstrukturen-bearbeiten-ausgabe
Mit for /d /r tiefe Ordnerstrukturen durchlaufen und ausgeben

Der Befehl wird dann leicht umgebaut: for /d /r "[ordner]" %%i in (*) do ()
Dann noch etwas Ausgabe mit dazu und wir haben fast den tree Befehl nachgebaut 😉

@echo off
setlocal enableDelayedExpansion 

for /d /r "%cd%" %%i in (*) do (
	echo %%i
)
pause
endlocal

Erkennung der Tiefe des aktuellen Ordners

Jetzt brauchen wir eine Erkennung der Tiefe. Das hat mich etwas länger beschäftigt als gedacht.
An die Tiefe der Rekursion von for /d /r scheint man leider nicht zu kommen und mehrere nicht-rekursive for-Schleifen ineinanderschachteln ist zu unflexibel. Stattdessen war folgender späterer Gedanke attraktiv einfach: Die Backslashes im Ordnerpfad zählen und damit die Tiefe erkennen.
Etwas sehr simpel aber das einzige, das ich erfolgreich umsetzen konnte. Wer hier einen besseren Ansatz hat, gerne ein Kommentar hinterlassen.

Das Zählen von Zeichen in einem String ist in Batch leider kein Einzeiler, aber das Rad muss ja nicht neu erfunden werden. Hier hat schonmal jemand eine Funktion dafür geschrieben. Möglich wäre auch die Nutzung von Powershell mit dem [string].split() Befehl, jedoch ist die Nutzung von Powershell in Batch ein Fass, dass wir hier jetzt nicht öffnen.

Nun zählen wir für jeden Unterordner die Backslashes, subtrahieren die Anzahl der Backslashes des Ordners, in dem das Skript ausgeführt wird und erhalten damit die relative Tiefe zum Skriptordner.

@echo off
setlocal enableDelayedExpansion 

set startdir=%cd%

call :GetCharCount startLevel %startdir% \

for /d /r "%cd%" %%i in (*) do (
	echo %%i
	call :GetCharCount level %%i \
	echo absolute Tiefe: !level!
	set /a relLevel=!level!-!startLevel!
	echo relative Tiefe: !relLevel!
)
pause
endlocal

:GetCharCount
  set _S=%~2
  set /a %~1 = 0
  for /L %%i in (0,1,10000) do (
    if "!_S:~%%i,1!"=="" (set "_S=" & exit /b)
    if /i "!_S:~%%i,1!"=="%~3" set /a %~1 += 1
  )
  set "_S="
  exit /b
batch-deep-recursion-folder-manipulation-tiefe-ordnerstrukturen-bearbeiten-tiefe-ausgeben
Absolute und relative Tiefe zum Skriptordner errechnen und ausgeben.

Verarbeitung von Ordnern in Abhängigkeit von Bedingungen

So, nun kann die Logik in Abhängigkeit von der Tiefe und weiteren gewünschten Faktoren mit ifs verbaut werden.
Alle Ordner in der Tiefe X UND mit dem Ordnernamen Y sollen umbenannt werden? Ich habe das beispielhaft (mit viel debug output) mal umgesetzt:

@echo off
setlocal enableDelayedExpansion 

set startdir=%cd%
set renameFrom=BitteUmbenennen
set renameTo=NeuerOrdnername
call :GetCharCount startLevel %startdir% \

for /d /r "%cd%" %%i in (*) do (
	echo ----
	echo %%i
	call :GetCharCount curLevel %%i \
	set /a relLevel=!curLevel!-!startLevel!
	for %%J in (%%i) do set foldername=%%~nxJ
	echo absolute Tiefe: !curLevel!
	echo relative Tiefe: !relLevel!
	echo Ordnername: !foldername!
	if !relLevel!==3 if "!foldername!"=="!renameFrom!" (
		echo **** RENAME ****
		set oldPath=%%i
		set newPath=!oldPath:%renameFrom%=%renameTo%!
		echo !newPath!
		move !oldPath! !newPath!
		echo ****
	)
)
pause
endlocal

:GetCharCount
  set _S=%~2
  set /a %~1 = 0
  for /L %%i in (0,1,10000) do (
    if "!_S:~%%i,1!"=="" (set "_S=" & exit /b)
    if /i "!_S:~%%i,1!"=="%~3" set /a %~1 += 1
  )
  set "_S="
  exit /b
batch-deep-recursion-folder-manipulation-tiefe-ordnerstrukturen-bearbeiten-umbenennen
Tiefe Ordnerstrukturen werden durchsucht und (beispielhaft) abhängig von der Tiefe und dem Ordnernamen umbenannt

Das ist jetzt natürlich beliebig erweiterbar aber das Prinzip sollte klar werden. Mehrere Voraussetzungen können einfach durch aneinanderreihen von ifs geprüft werden, quasi eine Einzeiler-Verschachtelung. Den aktuellen Ordnernamen ziehen wir mittels for-Befehl raus. Den neuen Ordnernamen bauen wir dank Batch String Manipulation. Das wäre in Powershell vermutlich ein Fünfzeiler aber sowas sieht in Batch irgendwie immernoch „schön“ aus 😉

Finale Version mit mehr Funktionen

Ich habe es vielleicht etwas übertrieben aber ich finde es immernoch interessant und unterhaltsam, in Batch zu coden, auch wenn es super umständlich ist und mit jeder Programmiersprache vermutlich einfacher wäre. Die finale Version beinhaltet weitere Möglichkeiten:

  • Steuerung des Tools mittels Parameter statt fest in den Code eingetragene Variablen – somit ließe sich das Ganze auch in eine .exe verwandeln und flexibel einsetzen. Optionale Modi (siehe unten), Ordnertiefe, Ziel und beliebig viele Quellordner könnt ihr beim Aufruf mit übergeben. Wenn das Programm ohne Parameter gestartet wird, wird eine Hilfe zur Nutzung ausgegeben.
  • Die Angabe mehrerer Quellordnernamen, die in einem Zielordner vereint werden sollen – die Dateien aller dieser Ordner werden im Zielordner derselben Ebene gesammelt.
  • Test-Modus zum Ausprobieren der Einstellungen. Im Testmodus geschieht alles so wie im Normaldurchlauf, nur dass die tatsächliche Datenverarbeitung übersprungen wird. Eine Ausgabe, wo sonst eine Dateiaktion passiert wäre, gibt es trotzdem.
  • Verbose- und Silent-Modus für viel oder wenig Ausgaben während der Abarbeitung. Im Silent-Modus gibt es nur 1 Ausgabe je Ordner, der umbenannt wird. Im Verbose Modus habt ihr viel zu Scrollen! 😉

Der Quelltext hier hier einsehbar: Code anzeigen

@ECHO off
SETlocal enableDelayedExpansion 

REM Author: Hannes Schurig
REM Date: 02.09.2019
REM More: https://it-stack.de/05/08/2019/tiefe-ordnerstrukturen-untersuchen-und-verarbeiten-mit-batch-2019

SET startdir=%cd%

IF "%1"=="" (
	ECHO ## USAGE ##
	ECHO.
	ECHO thistool.bat [--test] [--verbose] [depth] [renameTo] [renameFrom]
	ECHO test [optional]: Use "--test" to run in test mode. Normal outputs but no rename will actually be triggered.
	ECHO verbose [optional]: Use "--verbose" to get lots of output, without this there will be just one output per rename done.
	ECHO depth: In which folder level [relative to this tool] will rename do its work?
	ECHO renameTo: Folder name to rename to.
	ECHO renameFrom: As many folder names as you want that will get renamed. Foldernames with spaces in double quotes.
	ECHO.
	ECHO ## Exemplary ##
	ECHO.
	ECHO thistool.bar --test 3 newName "old Name 1" oldName2
	ECHO would run this tool in test-mode [no renames will be done] and both folders "old Name 1" [without quotes]
	ECHO and oldName2 in folder level 3 below this tools folder will trigger the rename process.
	ECHO thistool.bat --verbose 4 "new folder" "old Name" anotherFolder
	ECHO This will actually rename anotherFolder and "old Name" to "new folder" [both without quotes] in 4-level-deep subfolders 
	ECHO and output lots of stuff.
	PAUSE
	ENDLOCAL
	EXIT /B
)

:OptionalParams
REM number detection from: https://stackoverflow.com/a/17585404
ECHO(%~1|findstr "^[-][1-9][0-9]*$ ^[1-9][0-9]*$ ^0$">nul && GOTO :RequiredParams
IF "%1"=="--test" SET test=test&&SHIFT
IF "%1"=="--verbose" SET verbose=verbose&&SHIFT
GOTO :OptionalParams

:RequiredParams
SET depth=%1
SHIFT
SET renameTo=%1

SET "renameFrom=%*"
SET "renameFrom=!renameFrom:*%1 =!"
GOTO :LetsGo

:LetsGo
CALL :GetCharCount startLevel %startdir% \
IF "!verbose!"=="verbose" (
	ECHO ##############################
	ECHO verbose: !verbose!
	ECHO test: !test!
	ECHO depth: !depth!
	ECHO renameTo: !renameTo!
	ECHO renameFrom: %renameFrom%
	ECHO startdir: !startdir!
	ECHO level of startdir: !startlevel!
	ECHO ##############################
)

pause

FOR /d /r "%cd%" %%A IN (*) DO (
	CALL :GetCharCount curLevel "%%A" \
	SET /a relLevel=!curLevel!-!startLevel!
	FOR %%B IN ("%%A") DO SET "foldername=%%~nxB"
	IF "!verbose!"=="verbose" (
		ECHO ----
		ECHO %%A
		ECHO absolute Tiefe: !curLevel!
		ECHO relative Tiefe: !relLevel!
		ECHO Ordnername: !foldername!
	)
	IF !relLevel!==!depth! (
		FOR %%C IN (%renameFrom%) DO (
			SET tempRenameFrom=%%C
			SET cleanFolderName=!tempRenameFrom:"=!
			IF "!foldername!"=="!cleanFolderName!" (
				CALL :RenameFolder %%C "%%A"
			)
		)
	)
)

pause
endlocal
EXIT /B

:RenameFolder
	SET renameFromActual=%~1
	SET oldPath=%~2
	SET cleanRenameTo=!renameTo:"=!
	SET newPath=!oldPath:%renameFromActual%=%cleanRenameTo%!
	IF "!verbose!"=="verbose" (
		ECHO **** RENAME ****
		ECHO From: !oldPath!
		ECHO To: !newPath!
	) ELSE (
		ECHO *** Ordner !oldPath! wird umbenannt!
	)
	IF NOT "!test!"=="test" (
		IF NOT EXIST "!newPath!" (
			move "!oldPath!" "!newPath!">nul
		) ELSE (
			COPY /Y "!oldPath!" "!newPath!">nul
			RD /S /Q "!oldPath!">nul
		)
	) ELSE (
		ECHO Test-Modus aktiv, !oldPath! wird nicht umbenannt.
	)
	ECHO.
	EXIT /B

:GetCharCount
	SET _S=%~2
	SET /a %~1 = 0
	FOR /L %%i IN (0,1,10000) DO (
	IF "!_S:~%%i,1!"=="" (SET "_S=" & exit /b)
	IF /i "!_S:~%%i,1!"=="%~3" SET /a %~1 += 1
	)
	SET "_S="
	EXIT /B

Nutzung des Skripts

Das Skript wird über Paramter beim Aufruf gesteuert:

rename.bat [--test] [--verbose] [depth] [renameTo] [renameFrom]

### Was machen die Parameter? ###
--test: Startet den Testmodus des Tools. Es läuft normal durch, gibt normale Ausgaben aber der Umbenennungs-Befehl wird nicht ausgeführt. Gut zum Testen.
--verbose: Mit dem --verbose Parameter werden viele hilfreiche Ausgaben während der Ausführung gemacht. Ohne diesen Parameter gibt das Tool ausschließlich 1 Zeile pro erfolgter Umbenennung aus, nicht mehr.
depth: In welcher Ordnertiefe werden die Ordner geprüft und umbenannt?
renameTo: Neuer Ordnername
renameFrom: Beliebig viele alte Ordnernamen, die umbenannt werden sollen

### Beispielhafte Aufrufe ###
rename.bat --test 3 NeuerName AlterName1 "Alter Name 2"
rename.bat --test --verbose 4 "Neuer Ordner" "Alter Ordner" Testordner
usw.

@Sebastian: Ich hoffe, deine Anfrage ist damit erfolgreich beantwortet und du kannst den Code so für deine Zwecke einsetzen. Sag Bescheid, wenn du noch Hilfe brauchst.

13 Kommentare

  1. Hallo Hannes,
    damit lassen sich die Ordner umbenennen. Das einzige, was nicht funktioniert, ist, dass wenn ich verschiedennamige Quellordner habe und daraus einen Zielordner machen möchte, verschiebt er den zweiten Quellordner eine Ebene tiefer in den ersten Zielordner (ich hoffe du verstehst, was ich meine).
    Dieses Problem müsste nun noch gelöst werden, dann bin ich vollkommen glücklich.

    Vielen Dank noch einmal für deine Mühe.

    Viele Grüße
    Sebastian

  2. Hallo Sebastian, zwei Sachen werde ich mal hinzufügen: Erkennung des Zielordners und das Einsortieren aller Quellinhalte dort, wenn es ihn schon gibt. Zusätzlich dazu erweitere ich mal renameFrom, damit man dort direkt mehrere Ordnernamen eingeben kann und dann geht er diese nacheinander durch. Dann muss man das Skript nicht mehrfach ausführen.

    1. Hallo Hannes,

      vielen Dank dafür. Die zwei Sachen, die du noch hinzufügen möchtest wären in der Tat noch hilfreich. Finde ich das dann einfach dadrunter?

      Viele Grüße
      Sebastian

        1. Hallo Hannes,

          entschuldige bitte, wenn ich nerve, aber bist du hier schon voran gekommen?

          Viele Grüße
          Sebastian

  3. Hallo Sebastian,
    du musst mir nochmal kurz beschreiben, wie das Programm vorgehen soll. Ordner mit bestimmten (unterschiedlichen) Namen sollen umbenannt werden. Allerdings nur Ordner in der dritten Ebene, aber die dritte Ebene von mehrere unterschiedlichen ersten Ebenen.
    Was meintest du in deinem Kommentar mit „daraus einen Zielordner machen möchte“? Möchtest du aus *allen* Ordnern *aller* dritter Ebenen mit bestimmten (unterschiedlichen) Namen einen einzigen Zielordner irgendwo anders machen? Oder sollen jeweils nur *alle* Ordner mit unterschiedlichen Namen *einer* dritten Ebene zu einem Ordner in dieser dritten Ebene zusammengefasst werden, sodass es einen Zielordner pro Ordner dritter Ebene gibt?
    Ich hoffe das war verständlich aber die Programmierung ist halt ganz unterschiedlich, da frage ich lieber vor der Entwicklung nach.

    1. Hallo Hannes, ich versuche es am besten noch einmal mit einem Beispiel zu erklären.
      Ich habe in der dritten Ebene Ordner die verschieden benannt sind (z.B. Ordner 1, Ordner 2, Ordner 3, Ordner 4,…). Ich möchte jetzt, dass nach bestimmten Ordnernamen gesucht wird (z.B. Ordner 1) dieser soll dann z.B. umbenannt werden in Ordner 1_neu. So auch mit den anderen Ordnernamen. Es kann aber auch sein, dass Ordner 3 z.B. später auch Ordner 1_neu heißt und sich in demselben Überordner befindet wie Ordner 1_neu von Ordner 1. In diesem Fall sollen einfach die Dateien in den schon vorhandenen Ordner 1_neu integriert werden. Die neuen Ordner sollen jeweils in der dritten Ordnerebene bleiben.

      Ich weiß, dass das vielleicht etwas verwirrend klingt, aber ich hoffe, dass ich das einigermaßen erklären konnte.

      Bei Fragen immer gerne fragen und nochmals vielen Dank für deine Mühe!

      Viele Grüße

      1. Okay ich glaube das habe ich verstanden. Kurze Skizze:
        root
        (runter auf die dritte Ebene)
        – – Ebene 3_1
        – – – Ebene3_Ordner1
        – – – Ebene3_Ordner2
        – – – Ebene3_Ordner3
        – – Ebene 3_2
        – – – Ebene3_Ordner1
        – – – Ebene3_Ordner2
        – – – Ebene3_Ordner3

        Du startest nun das Tool rename.bat NeuerOrdner Ebene3_Ordner1 Ebene3_Ordner3 (so wird später der Aufruf sein um mehrere Ordner umzubenennen) – Ebene3_Ordner1 und Ebene3_Ordner3 sollen also beide in NeuerOrdner umbenannt werden und das Ergebnis wäre:
        root
        (runter auf die dritte Ebene)
        – – Ebene 3_1
        – – – Ebene3_Ordner2
        – – – NeuerOrdner (enthält die Dateien von Ebene3_Ordner1 und Ebene3_Ordner3 der Ebene 3_1)
        – – Ebene 3_2
        – – – Ebene3_Ordner2
        – – – NeuerOrdner (enthält die Dateien von Ebene3_Ordner1 und Ebene3_Ordner3 der Ebene 3_2)

        Passt das?

    1. Hallo Hannes,

      vielen Dank dafür. Jetzt brauche ich allerdings noch etwas Hilfe, da ich noch relativ neu bin. Welche Parameter muss ich jetzt genau anpassen und wo gebe ich die alten bzw. neuen Ordnernamen ein?

      Viele Grüße
      Sebastian

      1. Nabend Sebastian!
        Ich habe heute Abend nochmal einige Verbesserungen eingebaut, viele Fehler behoben und die Hilfe erweitert. Kopiere dir jetzt nochmal den gesamten Code aus dem Artikel, den du per Klick auf den „Code anzeigen“ Button im Text angezeigt bekommst. Wenn du das Programm ohne Parameter bzw. nur per Doppelklick aufrufst, erscheint nun eine ausführliche Hilfe. Ordnernamen mit Leerzeichen gehen jetzt auch.
        Im Beitrag habe ich eine kleine deutsche Nutzungshilfe eingebaut.
        Passt soweit?

  4. Hallo Hannes,
    ich habe ein anderes Rätsel das ich noch nicht lösen konnte. Dein BATCH Beitrag könnte helfen.
    Ich habe einen Ordner Medien/ Filme und Co, die als einzelne Dateien in einem Ordner vorliegen. Wäre es möglich über einen Befehl zu erreichen, das jede einzelene Datei in einen eigenen Ordner mit Titel der einzelnen Datei verschobdn wird? Damit am Ende jede einzelne Datei in einem eigenen Ordner steckt?

    Danke falls Du Ideen hast, diese kannst Du gerne an meine Email senden. danke
    olga

    1. Hallo Olga, ja, das ist recht einfach, alles dafür steckt schon in dem Skript hier und es sollten nur etwa 5-7 Zeilen nötig sein. Ist es wirklich immer nur 1 Datei pro Film/Medium oder können es auch mal 2 Dateien sein? Was ist mit Filmserien mit mehreren Teilen (sowas wie Herr der Ringe oder so), auch jeweils einen Ordner pro Teil? Einzige Einschränkung ist: wenn ein Film/Datei das Zeichen „&“ im Dateinamen trägt, wirds schwierig. Die müsste man vorher durch „und“ ersetzen. Geht auch powershell statt Batch? Ich schau mal, wann ich dazu komme.

Schreibe einen Kommentar