Windows 2003 Shell Scripting

Scripts zur System- und Dateiverwaltung

20.10.2008 von Armin Hanisch
Mit Scripts lassen sich wiederkehrende Aufgaben erleichtern und automatisieren. Im zweiten Teil unserer Serie „Shell Scripting unter Windows 2003“ stellen wir Ihnen Scripts vor, mit denen Sie Dateien und das System verwalten können.

Unter Linux sind Scripts der Alltag, mittlerweile holt auch die Windows-Fraktion auf. Spätestens, seit Microsoft die Power Shell eingeführt hat, sind auch unter Windows komplexe und hilfreiche Scripts möglich. In dieser Artikelreihe zeigen wir Ihnen Scripts zur Lösung für Aufgabenstellungen aus der täglichen Praxis. Die Scripts sind teilweise etwas komplexer oder setzen Kommandos auf manchmal ungewöhnliche Weise ein. Wir nehmen nicht in Anspruch, immer die optimale Lösung gewählt zu haben, und es gibt für manche Aufgaben sicher auch noch alternative Ansätze oder das Erstellen eines Scripts für den WSH. Sinn dieser Scripts ist das Verdeutlichen einer bestimmten Herangehensweise an solche Probleme. So können Sie selbst solche Shell Scripts ohne Probleme bauen, wenn Sie sich über den Ablauf der von Ihnen gewählten Befehle im Klaren sind.

Im zweiten Teil dieser Serie befassen wir uns mit Scripts, die bei der Verwaltung des Systems und von Dateien hilfreich sind. Auch hier können Sie sich wieder an den Beispielsscripts bedienen und diese an Ihre lokale Umgebung anpassen.

Windows 2003 Shell Scripting: Das Buch zu unserer Serie.

Unsere neue Serie zum Shell Scripting unter Windows 2003 basiert auf Kapitel 16 des Standardwerks „Windows 2003 Shell Scripting – Abläufe automatisieren ohne Programmierkenntnisse“ von Armin Hanisch aus dem Haus Pearson Education. Sie können dieses 320 Seiten starke Buch auch in unserem Partner-Buchshop bestellen.

Inhalt der Artikelserie zum Shell Scripting unter Windows 2003

Teil 1: Allgemeine Problemstellungen

Teil 2: System- und Dateiverwaltung

Teil 3: ActiveDirectory

Teil 4: Netzwerk

Systemverwaltung

Mit Hilfe einer for-Schleife und dem Modifikator $~PATH wird die erste im Suchpfad passende Datei gefunden und ausgegeben. Ab Windows Server 2003 können Sie stattdessen auch das Kommando where benutzen.

which

which liefert beim Aufruf den kompletten Pfad der Datei, die ausgeführt werden würde, wenn der Dateiname eingegeben wird. Sinnvoll ist dies vor allem, wenn diese Datei mehr als einmal (in verschiedenen Verzeichnissen) existiert und Sie sich nicht sicher sind, welche Datei gestartet wird.

Parameter

Code

@for %%f in (%1) do @if "%~$PATH:f" == "" (echo Nicht im Suchpfad) else (echo Ausgeführt wird %~$PATH:f)

Dateiverwaltung

Dieses Script nutzt die Möglichkeiten von dir, mit der Option /o-d eine umgekehrt nach Erstellungsdatum sortierte Liste auszugeben. Diese Ausgabe wird mit der Option /b auf die einfache Ausgabe einer reinen Liste von Dateinamen beschränkt und in eine temporäre Datei umgeleitet.

Anschließend benutzen wir set mit Option /p, um eine Zeile von der Standardeingabe einzulesen und in der Variablen filename zu speichern. Die Standardeingabe wird dabei umgeleitet, sodass die Daten aus der vorher erzeugten temporären Datei stammen. Bei der Umleitung aus einer Datei liest set nur die erste Zeile. Diese enthält den Namen der jüngsten Datei, der nun nur noch ausgegeben werden muss. In der letzten Zeile wird die nicht mehr benötigte temporäre Datei gelöscht.

Parameter

Code

@echo off
dir %1;%2 /b /o-d > %temp%\tmp.dat
set /P filename= < %temp%\tmp.dat
echo Neueste Datei ist: %filename%
del %temp%\tmp.dat

GetWord - Wörter finden

Mithilfe einer for-Schleife wird Zeile für Zeile der Datei gelesen. Durch die Angabe der Option tokens=%3 erhalten wir aber immer nur das Wort aus der Zeile, das dem Wort an der gesuchten Position (der Parameter Nummer drei) entspricht. Für jede Zeile wird die Ausführung an die Sprungmarke :procline übergeben. Der Wert für die gesuchte Zeile (der Parameter Nummer zwei, %2) und der tatsächliche Inhalt der Zeile (%%f) werden als Parameter übergeben.

An der Sprungmarke wird zuerst eine Umgebungsvariable (line) um eins erhöht und dann in einer if-Abfrage mit der gesuchten Zeilennummer verglichen. Bei Übereinstimmung wird die Zeile (die nur das gesuchte Wort enthält) ausgegeben.

Sollten die Wörter in Ihren Eingabedateien nicht durch Leerzeichen getrennt werden, können Sie der for-Schleife die Option delims hinzufügen (wie im Kommentar beschrieben) und dort eine Liste mit anderen Trennzeichen vereinbaren.

Parameter

Code

@echo off
:: select token from input file

set line=0
if x%1 == x goto usage
if not exist %1 goto nofile

:: add the delims= option between the quotes, if your
input is not
:: separated by white space, e.g. for /F "tokens=%3
delims=;" ....
for /F "tokens=%3 usebackq" %%f in (%1) do call
:procline %2 %%f
set line=
goto eof

:procline
set /a line="%line%+1"
if %1 == %line% echo %2
goto eof

:usage
echo %0 - extract a word from a text input file
echo Copyright (c) 2003 by Armin Hanisch
echo.
echo Usage: %0 [filename] [line#] [word#]
echo.
echo Words in input file are separated by white space.
echo If you need different delimiters, check the code
for help.
echo.
goto eof

:nofile
echo Could not find input file %1 - terminating.
goto eof

:eof

GetLine - Bestimmtes finden

Um eine Datei zu nummerieren, bietet die Shell eine Möglichkeit über das Kommando find. Wir suchen nach einem String, der nicht in der Datei vorkommt, und geben alle nicht übereinstimmenden Zeilen nummeriert aus. Die Ausgabe erfolgt in der Form [<nr>]<originalzeile>.

Diese Ausgabe muss nun so aufbereitet werden, dass die Nummer der Zeile mit der als Parameter übergebenen Nummer verglichen wird. Dazu wird mithilfe einer for-Schleife die Ausgabe der Suche pro Zeile an der schließenden eckigen Klammer aufgeteilt und das erste „Wort“ (die öffnende eckige Klammer und die Nummer der Zeile) für den Vergleich benutzt. Das zweite „Wort“ enthält aufgrund der Definition tokens=1,* den gesamten Rest, also die originale Textzeile. Bei der Übereinstimmung mit der gesuchten Zeilennummer wird die originale Zeile ausgegeben.

Um die Erzeugung einer Zwischendatei zu vermeiden, verwenden wir an dieser Stelle das sogenannte Backquoting. Damit kann das Ergebnis der Suche mit find direkt in die for-Schleife eingesetzt werden. Für jede Zeile wird dann in der if-Abfrage der Vergleich auf die passende Zeilennummer ausgeführt. Im Erfolgsfall wird die Zeile ausgegeben. Wird als Zeilennummer eine Nummer angegeben, die größer ist als die Anzahl der Zeilen im Dokument oder eine negative Zahl darstellt, erfolgt keine Ausgabe.

Parameter

Code

Hinweis: Die zweite Zeile ist eine einzelne logische Zeile und wird drucktechnisch bedingt umbrochen.

@echo off
if x%1 == x goto usage
if x%2 == x goto usage
for /F "delims=] usebackq tokens=1,*" %%f in (`find /v /n "@@@@" ^< %2`) do if [%1] == %%f] echo %%g
goto :eof
:usage
echo Aufruf: %0 [zeile] [datei]

Insert - Einfügen von Inhalten

An eine Textdatei Inhalt anzufügen, gehört zu den leichteren Übungen (s. Ausgabeumleitung). Viele Administratoren vermissen aber eine Funktion, mit der eine Textdatei in eine andere Textdatei eingefügt werden kann. Das nachfolgende Beispiel zeigt eine Batchdatei, mit der genau diese Funktion erreicht wird: Eine Datei wird vor einer anzugebenden Zeile in eine zweite Datei eingefügt. Damit die Batchdatei möglichst universal einsetzbar bleibt, wurde auf einige, ab Windows 2000 mögliche, Optimierungen verzichtet. Damit ist der problemlose Einsatz ab Windows NT4.0 bei eingeschalteten Befehlserweiterungen möglich.

Das Script liest die Zieldatei zeilenweise mit einer for-Schleife ein. Dabei läuft in einer Umgebungsvariablen count ein Zähler mit, der die Anzahl der bereits übertragenen Zeilen zählt. Diese Zeilen werden in der temporären Datei $tmp$.txt gesammelt.

Ist die Einfügezeile erreicht, wird die einzufügende Datei per more an die temporäre Datei angehängt. Danach werden in einer zweiten for-Schleife alle Zeilen aus der Zieldatei an die temporäre Datei angehängt, deren Zeilennummer größer als die der Einfügezeile ist.

Das Ergebnis ist eine einzelne Datei mit dem Ergebnis der Einfügeoperation. Diese wird dann anschließend noch über die Zieldatei kopiert.

Parameter

Code

@echo off
if !%3 == ! goto err
if not exist %1 goto err
if not exist %2 goto err
if not %3 GTR 0 goto err
goto ok

:err
echo Insert a text file into another text file before
line #.
echo.
echo Usage: %0 [source] [target] [line#]
echo.
echo Line number must be greater then 0 and both files
must exist.
echo If line number is greater then line count in
target, source file
echo will be appended.
echo Target file will be modified, need write access for
this file.
echo.
goto eof

:ok
if exist $tmp$.txt del $tmp$.txt
set count=0
for /F "tokens=*" %%f in (%2) do call :tran1 "%%f" %3
more < %1 >> $tmp$.txt
set count=0
for /F "tokens=*" %%f in (%2) do call :tran2 "%%f" %3
copy $tmp$.txt %2 /y
if exist $tmp$.txt del $tmp$.txt
goto eof

:tran1
set /a count=%count%+1
if %count% GEQ %2 goto eof
echo %~1 >> $tmp$.txt
goto eof

:tran2
set /a count=%count%+1
if %count% LSS %2 goto eof
echo %~1 >> $tmp$.txt
goto eof

:eof

Delete - Löschen von Zeilen

Dieses Shell Script durchläuft die angegebene Datei in einer for-Schleife und zählt in einer Umgebungsvariablen (line) die Anzahl der vorher durchlaufenen Zeilen mit. Alle Zeilen, bis auf diejenige mit der Nummer, die dem zweiten Parameter beim Aufruf entspricht, werden ausgegeben.

Da im Befehlsteil der Schleife als erster Parameter die Nummer der zu löschenden Zeile übergeben wird und danach der Inhalt der Zeile (in Anführungszeichen, falls Leerzeichen enthalten sind), erfolgt nach der Sprungmarke :procline eine Überprüfung mit if auf den Parameter Nummer eins (%1). Solche Konstruktionen kommen in Batches öfter vor, daher sollten Sie immer wissen, ob die Abarbeitung an dieser Stelle im normalen Ablauf stattfindet oder wie hier über einen internen Aufruf per call :label.

Da der Inhalt der Zeile in Anführungszeichen übergeben wurde, wird bei echo die Konstruktion %~2 gewählt, mit der die umschließenden Anführungszeichen wieder entfernt werden.

Als Designalternative haben wir hier die Ausgabe der Datei nach dem Löschen der angegebenen Zeile auf die Standardausgabe gelenkt. Dies hat den Vorteil, dass der Benutzer des Scripts völlig frei in der Verwendung der Ausgabe ist. Diese kann direkt auf die Konsole gehen oder aber mithilfe der Umleitungssymbole oder per Piping an andere Programme oder Dateien übergeben werden. So bleibt das Script flexibel, und die Anzahl der notwendigen Parameter wird minimiert.

Parameter

Code

@echo off
if !%1 == ! goto err
if !%2 == ! goto err
set line=0
for /F "usebackq" %%f in (%1) do call :procline %2 "%%f"
goto :eof
:err
echo Usage: %0 [file] [line#]
echo.
echo Deletes line number # from the file and writes
echo the result to StdOut
goto :eof
:procline
set /a line="line+1"
if not %line% EQU %1 echo %~2

Reverse - Reihenfolgen umdrehen

Dieses Script dreht die Reihenfolge der Zeilen in einer Textdatei um, das heißt, die letzten Zeilen werden an den Beginn der Datei versetzt und umgekehrt.

Parameter

Code

:: reverse a text file, slow but plain shell

:: test der parameter
if !%1 == ! goto nofile

:: ist die datei auch da?
if not exist %1 goto nofile

:: test auf umgebungsvariable
if not defined temp goto noenv

:: evtl. bestehende datei loeschen
if exist %temp%\tmp.txt del %temp%\tmp.txt

::
for /F "tokens=* usebackq" %%f in (%1) do call :procline
"%%f"
more < %temp%\tmp.txt > %1
del %temp%\tmp.txt
del %temp%\line.txt
goto eof

:procline
echo %~1 > %temp%\line.txt
if exist %temp%\tmp.txt more < %temp%\tmp.txt >> %temp%\
line.txt
more < %temp%\line.txt > %temp%\tmp.txt
goto eof

:noenv
echo Variable TEMP nicht definiert, Abbruch.
goto eof

:nofile
echo Keine Datei angegeben, Abbruch.
goto eof

:notfound
echo Die Datei %1 existiert nicht, Abbruch.
goto eof

:eof

Bulk Rename - Großflächig umbenennen

Zuerst erfolgt eine Sicherheitsabfrage, falls das Shell Script im falschen Verzeichnis ausgeführt wird. Die Umgebungsvariable num enthält den Startwert für die Nummerierung der Dateien.

ACHTUNG: Achten Sie darauf, dass Sie solche Scripts mit Vorsicht einsetzen. Es kann durchaus passieren, dass Sie beispielsweise das komplette Windows-Verzeichnis umnummerieren. Testen Sie daher solche Scripts am besten auf einer virtuellen Maschine.

Mithilfe einer for-Schleife werden alle Dateien in einem Verzeichnis, die auf die Suchmaske passen (hier *.jpg), durchlaufen. Für jeden Dateinamen wird die Sprungmarke :doRen angesprungen. Da im Dateinamen auch Leerzeichen enthalten sein könnten, wird der Name in Anführungszeichen übergeben.

Bei der Sprungmarke erfolgt zuerst eine Ausgabe der alten und neuen Dateinamen, dann wird mit ren und der Variablen num die Umbenennung durchgeführt. Anschließend wird der Zähler in num um eines ((eins??))erhöht.

Parameter

(keine)

Code

@echo off
echo Achtung! Dieses Script aendert alle Namen
echo von JPG-Dateien im aktuellen Verzeichnis
echo in eine numerische Form ab!
echo Weiter mit bel. Taste oder [Strg+C]
echo zum Abbrechen.
pause > nul
set num=1
for %%f in (*.jpg) do call :doRen "%%f"
echo Fertig.
GOTO :EOF

:doRen
echo Aus %1 wird Img-%num%%~x1 ...
ren %1 Img-%num%%~x1
Set /A num=%num% + 1

Textdateien nummerieren

Textdateien lassen sich per find nummerieren. Vielen Benutzern allerdings gefällt die Art der Nummerierung nicht. Sehen wir uns dazu nochmals das find-Kommando und seine Ausgabe an:

find /n /v "@@@@@" < datei.txt

[1]Eins
[2]Zwei
[3]Drei

Problematisch daran ist das fehlende Leerzeichen zwischen der Zeilenzahl und dem Text. Sie können die Ausgabe in eine neue Datei umleiten und dann mittilfe einer for-Schleife wieder auslesen (mit einem "]" als Trennzeichen). Es geht allerdings auch in einer Zeile, wenn Sie die Möglichkeit von Backquoting, Kommandogruppierung und erweiterter Variablenersetzung nutzen:

for /F "usebackq tokens=*" %f in (`find /n /v "@@@@@"^< datei.txt`) do @set line=%f & set line=!line:]= !& set line=!line:[=! & echo !line!

Dies erzeugt die unten stehende Ausgabe:

1 Eins
2 Zwei
3 Drei

Sollten Sie jetzt stirnrunzelnd auf die Befehlszeile gucken, geht es Ihnen wie einigen meiner Manuskript-Testleser, deshalb möchten wir Ihnen Schritt für Schritt erläutern, was eigentlich passiert.

Makros erstellen

In der täglichen Praxis würden wir keine Befehlszeile dieser Länge immer wieder per Hand eingeben. Als Alternative bietet sich ein Makro an. Möchten Sie diese Funktion allerdings von anderen Shell Scripts aus nutzen, funktioniert ein Makro bekanntlich nicht. Daher an dieser Stelle der Code für ein Shell Script nicenum.bat, mit dem Sie das gleiche Ergebnis erhalten.

@echo off
:: nummerieren einer als Parameter übergebenen
:: Textdatei ohne eckige Klammern

find /n /v "@@@@@" < %1 > %temp%\tmp.txt

for /F "tokens=*" %%f in (%temp%\tmp.txt) do call
:procline "%%f"

goto :eof

:procline
set line=%~1
set line=%line:]= %
set line=%line:[=%
echo %line%

Beachten Sie im Code die Verdopplung der Prozentzeichen bei der Variablen für die for-Schleife. Durch die zeilenweise Abarbeitung der Batch-Engine kommen wir an dieser Stelle auch ohne verzögerte Variablenexpansion aus und können die gewohnten Prozentzeichen für die Variablen benutzen. Da wir in diesem Beispiel den Output des find-Kommandos im temp-Verzeichnis ablegen, kann auch auf das Backquoting verzichtet werden, und durch die Modularisierung über den Aufruf per call :label wird das Shell Script auch übersichtlicher.

Entscheiden Sie selbst, welcher der beiden Ansätze für Sie sinnvoller ist. Sollten Sie auf einer IT-Party in einer dieser „Windows hat keine Shell“-Diskussionen verwickelt werden, kritzeln Sie einfach die Befehlszeilenversion auf eine Serviette, und die Diskussion ist beendet ...

uniq (Duplikate entfernen)

Die Funktion dieses Scripts ist relativ einfach, aber dennoch nützlich, da Windows ein uniq-Kommando fehlt. Zuerst wird die Datei per sort mithilfe von Backquoting sortiert. Sie können natürlich auch sort direkt ohne die for-Schleife aufrufen, das Ergebnis in eine temporäre Datei schreiben und diese Datei dann per for abarbeiten.

Für jede Zeile wird in der Schleife die Ausführung an der Sprungmarke :procline fortgesetzt. Dort wird eine Umgebungsvariable (line) mit dem Inhalt der aktuellen Zeile verglichen. Solange diese übereinstimmen, haben wir ein Duplikat, und die Zeile wird nicht ausgegeben. Ist die Zeile verschieden, wird diese neue Zeile in der Variablen line gespeichert und per echo ausgegeben.

Parameter

Code

@echo off
if !%1 == ! goto noparm
if not exist %1 goto notfound

setlocal
set line=@@@@@
for /F "usebackq tokens=*" %%f in (`sort %1`) do call
:procline "%%f"
endlocal
goto :eof

:procline
if !%line% == !%~1 goto :eof
set line=%~1
echo %~1
goto :eof

:noparm
echo Aufruf: %~f0 [datei]
echo Kein Dateiname angegeben, Abbruch.
goto :eof

:notfound
echo Die Datei %1 wurde nicht gefunden, Abbruch.
goto :eof

Ausblick

Dieser Teil der Artikelserie hat sich mit der System- und der Dateiverwaltung beim Shell Scripting unter Windows beschäftigt. Im nächsten Teil der Serie wird es um die Möglichkeiten zur Verwaltung des ActiveDirectory gehen. (mzu/mja)

Windows 2003 Shell Scripting: Das Buch zu unserer Serie.

Unsere neue Serie zum Shell Scripting unter Windows 2003 basiert auf Kapitel 16 des Standardwerks „Windows 2003 Shell Scripting – Abläufe automatisieren ohne Programmierkenntnisse“ von Armin Hanisch aus dem Haus Pearson Education. Sie können dieses 320 Seiten starke Buch auch in unserem Partner-Buchshop bestellen.

Inhalt der Artikelserie zum Shell Scripting unter Windows 2003

Teil 1: Allgemeine Problemstellungen

Teil 2: System- und Dateiverwaltung

Teil 3: ActiveDirectory

Teil 4: Netzwerk