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.
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
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 if
s 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
Das ist jetzt natürlich beliebig erweiterbar aber das Prinzip sollte klar werden. Mehrere Voraussetzungen können einfach durch aneinanderreihen von if
s 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.