Teil 1: Allgemeine Problemstellungen

Shell Scripting unter Windows

09.10.2008 von Armin Hanisch
Mit Scripts lassen sich wiederkehrende Aufgaben erleichtern und automatisieren. In unserer neuen Artikelserie zeigen wir Ihnen eine praktische Einführung in das Shell Scripting unter Windows und Lösungen für alltägliche Probleme.

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 Scripte möglich. In dieser Artikelreihe zeigen wir Ihnen Scripte 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 ersten Teil der Serie sehen wir uns Lösungen für allgemeine, immer wiederkehrende Problemstellungen an. Hier können Sie sich wie aus einem Baukasten bedienen und einzelne Codestücke in Ihre eigenen Shell Scripts einbauen. Die folgenden Beispiele sollen Ihnen helfen, bestimmte Probleme zu lösen, Sie selbst können die Scripts aber anpassen und Teile daraus anders verwenden. Für den praktischen Einsatz werden Sie sowieso einige Sachen ergänzen oder umbauen.

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. Die komplette Ausgabe dieses 320 Seiten starke Buches können Sie in unserem Buch-Partnershop informit.de bestellen.

Artikelserie

Teil 1: Allgemeine Problemstellungen

Teil 2: System- und Dateiverwaltung

Teil 3: ActiveDirectory

Teil 4: Netzwerk

Befehlsergebnis als Errorlevel festhalten

Ein Grundprinzip von Windows Shell ist das Setzen der Variablen errorlevel nach jeder Befehlsauführung. So sind Sie in der Lage festzustellen, ob ein Kommando fehlerfrei ausgeführt wurde. Problematisch wird es immer dann, wenn das Kommando zwar ohne Fehler abgearbeitet wurde, aber dennoch nicht das gewünschte Ergebnis liefert.

Als Beispiel dient das Kommando tasklist. Mit Hilfe von tasklist soll festgestellt werden, ob auf einem bestimmten Computer ein Prozess ausgeführt wird. Dazu wird mit der Option /s das gewünschte System angegeben und als Suchfilter mit der Option /fi der gesuchte Prozessname definiert.

C:\tmp>tasklist /FI "imagename eq cmd.exe"
Abbildname PID Sitzungsname Sitz.-Nr. Speichernutzung
========== ================ ========= ===============
cmd.exe 2088 Console 0 1.404 K
C:\tmp>

Ganz gleich, ob cmd.exe auf der Maschine „blechbox“ noch ausgeführt wird oder nicht, wir erhalten in jedem Fall einen Errorlevel von 0, da das Kommando selbst ja fehlerfrei ausgeführt wurde. Das folgende Kommando verdeutlicht diesen Sachverhalt.

C:\tmp>tasklist /FI "imagename eq not_here.exe"
INFORMATION: Es werden keine Tasks mit den angegebenen Kriterien ausgeführt.

C:\tmp>echo %errorlevel%
0

C:\tmp>

Ein anderer Wert als 0 für den Errorlevel würde sich nur dann ergeben, wenn ein wirklicher Laufzeitfehler auftritt. Um das zu verdeutlichen, geben wir im nachfolgenden Beispiel absichtlich eine ungültige Option an.

C:\tmp>tasklist /FX "imagename eq cmd.exe"
FEHLER: Argument/Option ungültig - '/FX'.
Geben Sie "TASKLIST /?" ein, um die Syntax anzuzeigen.

C:\tmp>echo %errorlevel%
1

Wie lässt sich nun innerhalb eines Batches feststellen, ob ein bestimmter Prozess noch ausgeführt wird oder nicht? Dazu wird die Ausgabe von tasklist per Piping an den Filter find (alternativ ist auch findstr möglich) weitergereicht. Wir nutzen hier die Tatsache, dass bei einem gefundenen Prozess der Name des Prozesses auch in der Ausgabe auftaucht. Wird der Prozess nicht gefunden, erhalten Sie nur eine Mitteilung, dass kein entsprechender Task ausgeführt wird. Über die Verwendung von find erhalten wir sehr wohl einen passenden Errorlevel. Wird der Prozessname nicht gefunden, liefert find einen Errorlevel von 1, bei Erfolg einen Wert von 0.

C:\tmp>tasklist /FI "imagename eq cmd.exe" /NH | find /i
"cmd.exe" > nul
C:\tmp>echo %errorlevel%
0
C:\tmp>tasklist /FI "imagename eq nixda.exe" /NH | find
/i "nixda.exe" > nul
C:\tmp>echo %errorlevel%
1
C:\tmp>

Dieser Ansatz lässt sich für alle Shellbefehle nutzen, die zwar fehlerfrei ablaufen, aber auch auf ein bestimmtes Ergebnis getestet werden sollen. Die Ausgabe selbst wird für das Zuweisen an die Variable errorlevel nicht benötigt, daher erfolgt eine Umleitung in das Null-Device.

Sehr gut kombinierbar ist dieser Ansatz auch mit der Befehlsverkettung über die Operatoren && bzw. ||. So wird ein nachfolgendes Kommando nur dann ausgeführt, wenn der Prozess noch (oder eben nicht mehr) läuft.

Shell Script bei Fehler beenden

Das obige Beispiel ist auch ein guter Ausgangspunkt für den kontrollierten Ausstieg bei einer nicht erfüllten Anforderung. Angenommen, ein Batch soll beendet werden, wenn ein bestimmter Prozess auf einer Maschine nicht läuft. Dann lässt sich dies wie im nachfolgenden Batch als Prüfcode vor der eigentlichen Funktionalität einbauen.

@echo off
:: ende bei keinem Parameter
if x%1 == x (echo Kein Prozessname angegeben) & goto
:eof
:: test auf prozess
tasklist /FI "imagename eq %1" /NH | find /i "%1" > nul
|| goto :eof
echo Prozess %1 läuft.
:: ab hier restlicher code ...

StdErr für die Fehlererkennung benutzen

Viele Kommandos, mit denen innerhalb der Windows-Shell gearbeitet wird, geben ihre Ausgaben tatsächlich auf zwei verschiedenen Kanälen aus. Der eine ist die Standardausgabe (stdout, Kanalnummer 1), der andere der Standardfehlerkanal (stderr, Kanalnummer 2). Damit können Sie sehr einfach feststellen, ob ein Kommando problemlos ausgeführt wurde oder nicht. Falls die angelegte Datei größer ist als 0 Bytes, ist ein Fehler aufgetreten.

xcopy *.* g:\daten\backup\*.* /Y 2> error.txt

Enthält die Datei error.txt nach der Ausführung keine Daten (die Dateigröße beträgt 0 Byte), wurde das Kommando ohne Fehler beendet.

Größe einer Datei feststellen

Wie im Abschnitt „StdErr für die Fehlererkennung benutzen“ erläutert, lassen sich Dateien, die durch eine Ausgabeumleitung erzeugt wurden, gut für die Fehlererkennung benutzen. Das Problem innerhalb eines Batches liegt nun darin festzustellen, ob eine Datei leer ist (also eine Größe von 0 Byte besitzt) oder nicht. Unter DOS gab es früher die undokumentierte Möglichkeit, über das Kommando copy <datei>+,, diese Datei zu löschen, falls sie 0 Byte groß war. Unter NT und aufwärts funktioniert dieser Ansatz aber nicht mehr, hier geht es dafür einfacher.

Über Modifikatoren lassen sich einzelne Elemente einer Datei ansprechen, der Modifikator "z" beschreibt dabei die Größe einer Datei. Das funktioniert auch bei einer Batchdatei, sofern es sich dabei um existierende Dateinamen handelt. Damit lässt sich dann eine interne Sprungmarke aktivieren, die genau diese Prüfung für eine Datei durchführt und diese dann löscht, falls die Dateigröße 0 Byte beträgt. Das nachfolgende Listing zeigt ein Beispiel für einen solchen Test beim Kopieren von Dateien.

xcopy *.* g:\tmp\daten\zeug\*.* /Y 2> %temp%\x.x
call :chkzero %temp%\x.x
if exist %temp%\x.x echo Fehler!
goto :eof

:: -------- helper routines --------

:chkzero
if %~z1 == 0 del %1
goto :eof

Die eigentliche Fehlerprüfung erfolgt in der dritten Zeile der Datei. Falls die Datei x.x im temporären Verzeichnis noch existiert, war sie länger als 0 Byte und enthält damit die Fehlermeldung. Dies ist auch der Grund, warum der Code die Datei nur dann löscht, wenn sie 0 Byte lang ist. So wird die leere Datei im Erfolgsfall gelöscht, und es gehen keine Informationen verloren. Sie können nun jederzeit nach einem positiven Test auf die Existenz der Datei die darin enthaltene Fehlermeldung auslesen und verarbeiten.

Workgroup oder Domain

Eine oft gestellte Frage ist die nach der Art der Netzwerkanmeldung. Eine Methode ist die Verwendung der Umgebungsvariablen logonserver. Falls es sich bei dem Logon-Server um einen Domain Controller handelt, gibt es normalerweise eine sysvol-Freigabe, in der dann unter anderem die netlogon-Freigabe mit den Scripts liegt. Standardmäßig besitzt die dynamische Gruppe JEDER Leserechte auf diese Freigabe.

if exist %logonserver%\sysvol\%userdnsdomain% echo Logonserver ist ein Domain Controller

Dies kann dazu benutzt werden, mit der obigen Abfrage über zwei Umgebungsvariablen zu testen, ob dieses Verzeichnis auf dem Anmeldeserver existiert. Falls dies nicht der Fall ist, handelt es sich um eine lokale oder Workgroup-Anmeldung. Ob Sie diese Methode oder den Ansatz mit nslookup bevorzugen, bleibt Ihnen überlassen. Letzterer bietet den Vorteil, auch als Test für den Beitritt zu einer Domäne zu funktionieren. In einem späteren Beitrag dieser Serie zeigen wir alternativ, wie sich dieses Problem mittels ActiveDirectory und nslookup lösen lässt

Liste von Computernamen erstellen

Falls Sie ein Kommando auf mehr als einer Maschine ausführen oder mehr als einen Server abfragen wollen, benötigen Sie eine Liste der entsprechenden Rechner. Diese können Sie manuell erstellen und pflegen, oder Sie holen sich die Daten aus dem System. Es gibt sicher mehr als die folgenden drei Möglichkeiten, aber damit haben Sie einige Ideen, woher Sie eine dynamisch erzeugte Liste von Hostnamen bekommen.

DNS

In den Support Tools enthalten ist das Kommando dnscmd.exe, mit dem Sie die Verwaltung des DNS-Servers über die Kommandozeile erledigen können. Mit Hilfe dieses Kommandos lassen sich auch die Einträge einer DNS-Zone auflisten, sogar gefiltert nach Typen. Um alle Adresseinträge (Type A) einer Zone aufzulisten, können Sie die nachfolgend aufgeführte Befehlszeile verwenden (ersetzen Sie dabei shellbook.local durch den Namen Ihrer Domain).

C:\tmp>dnscmd /enumrecords shellbook.local @ /Type A

Returned records:
@ [Aging:3552106] 600 A 192.168.229.11
silicony [Aging:3552106] 3600 A 192.168.229.11
bytebag [Aging:3552106] 3600 A 192.168.229.12
rennsemmel [Aging:3552106] 3600 A 192.168.229.14
Command completed successfully.

Sie müssen nur noch die nicht benötigten Zeile ausfiltern, beispielsweise über more und find, und erhalten dann eine Liste aller Hosts in dieser DNS-Zone.

dnscmd /enumrecords shellbook.local shellbook.local.
/Type A | more +2 | find /v /i "command completed"

AD (dsquery)

Ein anderer Weg ist das Auslesen der entsprechenden Rechnernamen aus dem Active Directory. Sie geben den Container als Parameter für eine Suche mit Hilfe von dsquery an und lassen das Attribut cn alle Unterobjekte (-scope onelevel) ausgeben. Die Option -q bewirkt das Unterdrücken der Kopfzeilen und anderer unwichtiger Ausgaben.

C:\tmp>dsquery * cn=Computers,dc=shellbook,dc=local -
attr cn -q -scope onelevel

BYTEBAG
SILICONY
RENNSEMMEL

C:\tmp>

Aktive Verbindungen

Wollen Sie für ein Shell-Kommando alle Computernamen verwenden, die gerade eine Verbindung mit Ihrem Server offen haben, können Sie dazu netstat verwenden (Sind Sie dagegen an den IP-Adressen anstelle der Hostnamen interessiert, verwenden Sie netstat mit der Option –n). Vorher müssen allerdings noch die Kopfzeilen ausgefiltert werden. Dies kann entweder durch eine Suche per find nach einem Doppelpunkt geschehen (die Ergebnishosts werden durch einen Doppelpunkt, gefolgt vom Port, ausgegeben), oder Sie verwenden more und schneiden mit der Option +4 die ersten vier Zeilen ab.

(for /F "usebackq tokens=3" %f in (`netstat ^| more +4`) do @for /F "delims=:" %g in ("%f") do @echo %g )| sort | uniq

Die Ausgabe von netstat wird zuerst um die Kopfzeilen gekürzt (hier mit more +4), danach dient diese Ausgabe per Backquoting als Input für die for-Schleife, mit der die einzelnen Zeilen zerlegt werden. Das dritte „Wort“ enthält den Remote-Hostnamen und den Port, getrennt durch einen Doppelpunkt. Diese Daten werden von der inneren Schleife am Doppelpunkt (delims=:) aufgeteilt, und der erste Teil wird ausgegeben. Die runden Klammern um die komplette Schleife dienen der Befehlsgruppierung, so dass die Ausgabe nur einmal und komplett durch die Filter läuft. Sollte ein Rechner auf mehr als einem Port eine Verbindung offen haben, taucht er in der Ergebnisliste mehrmals auf. Daher muss sie zuerst mit sort sortiert werden, und dann werden mit Hilfe von uniq die Duplikate entfernt. Unten finden Sie eine Beispielausgabe des Shell Scripts.

silicony.shellbook.local
rennsemmel.shellbook.local
bytebag.shellbook.local

Unter Windows 2003 SP1 funktioniert die Befehlszeile oben nicht und wird mit der Meldung "| ist an dieser Stelle syntaktisch nicht verarbeitbar" abgebrochen. Offensichtlich wurde dem Parser von den vielen Schleifen schwindlig. Die Alternative unten funktioniert auch unter Windows 2003 und filtert nach der Abarbeitung die Zeile mit den Spaltentiteln heraus.

(for /F "usebackq tokens=3" %f in (`netstat`) do @for Eine Zeile /F "delims=:" %g in ("%f") do @echo %g ) | find /V /i "adresse" | sort | uniq

silicony.shellbook.local
rennsemmel.shellbook.local
bytebag.shellbook.local

Bitte beachten Sie: Der Filter uniq ist nicht im Standardumfang von Windows XP oder 2003 enthalten. Sie können nun entweder die Ausgabe der Schleife in eine Datei umleiten und den „uniq“-Batch weiter unten im Kapitel „Dateiverwaltung“ benutzen, oder Sie installieren die GNU Unix Tools oder die Services for Unix von Microsoft. Eines der beiden Pakete sollten Sie aber auf Ihrem Server haben, Sie erleichtern sich die Arbeit nochmals deutlich.

Letzten Login auslesen

Für viele Aufgaben ist es nötig, Informationen zu einzelnen Benutzern auszulesen. Je nach Aufgabenstellung eignen sich verschiedene Befehle der Shell dafür. Falls Sie in einem Active Directory-basierten Netzwerk arbeiten, ist in den meisten Fällen die Kombination aus dsquery und dsget die beste Lösung. Dennoch sollten Sie an dieser Stelle das Kommando net nicht vergessen, es gibt Abfragen, die mit den ds*-Kommandos nicht möglich sind, sehr wohl aber mit net. Dazu ein Beispiel aus der Praxis: Für einen Benutzer soll das Datum der letzten Anmeldung ermittelt werden. Mit Hilfe von dsquery kein Problem, das Problem ist die Ausgabe des Kommandos, wie der folgende Konsolenmitschnitt zeigt.

C:\tmp>dsquery * "cn=franz Testhuber,cn=users,dc=shellbook,dc=local" –attr LastLogon –q 127875195238906250

Die letzte Anmeldung war also am 127875195238906250 – sehr interessant, aber wenig hilfreich. Hierbei handelt es sich um den internen Wert des AD-Zeitstempels (Dies sind die 100 Nanosekunden-Intervalle seit dem 01. Januar 1601 Mitternacht. Dabei handelt es sich um Angaben in UTC. Falls Sie sich auch gerade fragen, wer sich so eine Zeitbasis ausdenkt, es gibt eine logische Erklärung: Dies sind ANSI Decimal Dates aus der COBOL-Ära. Einen guten Artikel zu diesem Thema finden Sie hier. Den können wir per Shell nicht sinnvoll in einen lesbaren Wert konvertieren. Dennoch muss nicht auf den Windows Script Host ausgewichen werden, die Lösung hier ist net user.

C:\tmp>for /F "usebackq tokens=3,4" %f in (`net user franz ^| find /i "anmeldung"`) do @echo Datum: %f Zeit: %g
Datum: 22.03.2006 Zeit: 17:45

Wir filtern die Ausgabe von net user <benutzerlogin> nach der Zeile mit dem Datum der letzten Anmeldung (für die englische Version muss der Suchstring angepasst werden) und bekommen das Datum in einem lesbaren Format angezeigt.

Tricks zur Umsetzung

Die meisten Dinge, die einfach aussehen, sind es in Wirklichkeit nicht. Dazu gehört auch das Auslesen des tatsächlichen letzten Logins eines Benutzers. Es gibt zwar im Active Directory das Attribut LastLogon, es wird aber nicht repliziert und ist nur auf dem Domain Controller verfügbar, auf dem sich der Benutzer gerade anmeldet. Was auf dem System des Autors mit einem armen Domain Controller problemlos funktioniert, führt auf produktiven Systemen unweigerlich irgendwann zu der Information „UserXY war noch nie angemeldet“, weil Ihr Shell Script, VBS-Script oder Programm den falschen DC erwischt hat.

Es gibt zwei Lösungen für dieses Problem. Die eine funktioniert auch unter Windows 2000 und besteht einfach darin, der Reihe nach alle Domain Controller abzuklappern und das Attribut Last-Logon des gesuchten Benutzers auszulesen und daraus das aktuellste Datum zu verwenden.

Der andere Weg ist die Verwendung von Windows 2003. Hier gibt es im Schema für das Active Directory ein neues Attribut mit dem Namen LastLogonTimestamp, das ebenfalls die letzte Anmeldung enthält. Dieses Attribut wird aber über alle Domain Controller repliziert. Sollte sich ein Nutzer allerdings mehrmals in kurzen Zeitabständen hintereinander anmelden, würde dies natürlich einen erheblichen Replikationsverkehr bedeuten.

Also wird das Attribut vom DC, der die Anmeldung durchführt, nur dann repliziert, wenn die letzte Anmeldung mindestens 14 Tage zurückliegt. Für die Beantwortung der Administratorenfrage: „Wer hat sich die letzten vier Wochen nicht mehr angemeldet?“ reicht diese Genauigkeit aber aus. Daher müssen Sie auch nicht unbedingt aus dem UTC-Wert des Zeitstempels die lokale Zeit berechnen, wenn der Wert um zwei Wochen danebenliegen kann.

Immerhin, es gibt standardmäßig auch keine Methode, diese Information über das GUI zu erhalten (etwa mit dsa.msc). Hier müssen Sie erst eine Zusatzbibliothek (acctinfo.dll) von Microsoft installieren. Diese können Sie von der Microsoft Website downloaden.

Ausgabezeilen verändern

Diese Anforderung tritt häufig auf, ist aber meist ein “kosmetisches” Problem bei Ausgaben von Shell Scripts. Ein Ansatz ist es, die Ausgabe über Backquoting in einer zweiten for-Schleife zu übernehmen. Diese kann dann einzelne Teile der Ausgabe mit eigenem Text oder anderen Umgebungsvariablen mischen. Das nachfolgende Codestück zeigt einen solchen Ansatz. Hier wird die Ausgabe von sc per find gefiltert. Das Resultat wird als Kommandoersetzung an die innere Schleife übergeben und das vierte Wort für die Ausgabe verwendet.

C.\tmp>for %f in (silicony bytebag 192.168.229.11) do @for /F "usebackq tokens=4" %m in (`sc \\%f query dns ^|find "STATE"`) do @echo Server %f : %m
Server silicony : RUNNING
Server 192.168.229.11 : RUNNING
C:\tmp>

Hierbei sind auch weitergehende Unterscheidungen möglich. Wenn Sie im obigen Beispiel Ihre eigenen Server in die Liste der ersten Schleife einsetzen und das Kommando ausprobieren, werden Sie feststellen, dass nur die Zeilen ausgegeben werden, bei denen die Abfrage erfolgreich war. Daher müsste die Ausgabe eigentlich auch testen, ob sc einen Fehler meldet. Das Filtern per find muss entfernt werden, und als Kommando für die innere Schleife wird eine Abfrage eingebaut, die auf das Wort „Fehler“ prüft (das Beispiel gilt für die deutsche Version von Windows, für die englische bauen Sie die Zeile bitte entsprechend um, oder Sie prüfen auf die Fehlernummer).

C:\tmp >for %f in (silicony bytebag 192.168.229.11) do @for /F "usebackq tokens=1,2,3,4" %m in (`sc \\%f query dns`) do @if %m == STATE (echo %f : %p) ELSE if %o ==FEHLER (echo %f : Fehler bei Abfrage)
silicony : RUNNING
bytebag : Fehler bei Abfrage
192.168.229.11 : RUNNING
C:\tmp >

Ein Kommando wie das obige wird in der Regel für eine wiederkehrende Abfrage (beispielsweise morgens beim Checkup der Server) benutzt. Also sollten wir die Lesbarkeit verbessern und den Code auch gleich als Bachdatei anlegen (obwohl Sie aus der Zeile oben auch problemlos ein Makro bauen könnten). Dabei werden wir auch gleich die Lesbarkeit der Ausgabe dadurch verbessern, dass die Rechnernamen sauber linksbündig ausgerichtet in einer 16 Zeichen breiten Spalte stehen.

Hier der erste Teil des Shell Scripts. Im Prinzip ist es nichts anderes als das interaktive Kommando, nur dass in der Schleife die Sprungmarke check per call angesprungen wird. Dieser wird der jeweils aktuelle Eintrag aus der Schleifenliste als Parameter übergeben.

@echo off
setlocal
for /F "usebackq tokens=2" %%f in (silicony bytebag 192.168.229.11)
do call :check %%f
endlocal
goto :eof

Zweiter Teil des Scripts

Hier kommt der zweite Teil des Scripts. Jetzt folgt der eigentlich Check für den Zustand des Dienstes. Wir wollen aber erreichen, dass inaktive Server ebenfalls mit einer entsprechenden Meldung angezeigt werden. Daher wird zuerst ein Ping auf der Maschine ausgeführt, um zu prüfen, ob der Server erreichbar und online ist. Falls find eine Zeile mit dem String „TTL“ (steht als Time To Live in einer erfolgreichen Ping-Antwort) gefunden hat, erhalten wir einen Errorlevel von 0, bei negativem Suchergebnis einen Wert von 1 (und damit ist der Rechner nicht per Ping erreichbar). In diesem Fall setzen wir die Variable line auf die auszugebende Statusmeldung (hier UNREACHABLE).

:check
ping -n 1 %1 | find "TTL" > nul
if errorlevel 1 set line=UNREACHABLE
if errorlevel 0 for /F "tokens=4" %%a in ('sc \\%1 query
messenger ^| find "STATE"') do set line=%%a
:: padding des servernamens
set server=%1
set count=0
set c=%server%
:loop
set c=%c:~0,-1%
if not x%c% == x set /A count=%count%+1 & goto loop
:padloop
:: Achtung!! Die nächste Zeile ist nur fuer den Druck umbrochen!
:: das "& goto padloop" gehoert in die gleiche Zeile
if %count% LEQ 16 (set server=%server%.) & (set /A count=%count%+1)
& goto padloop
echo %server% %line%

Bei einem erfolgreichen Ping wird mit sc der Zustand des Dienstes (hier der messenger, Sie sollten dies für Ihre Zwecke anpassen) abgefragt und die gefilterte Ausgabe per Backquoting in die Variable line gespeichert. Ab dem Kommentar :: padding des servernamens beginnt der Code für die „schöne“ Ausgabe des Servernamens. Wir zählen die Länge des aktuellen Servernamens und fügen dann so oft das Zeichen „.“ an den Servernamen an, wie dieser kleiner oder gleich 16 Zeichen ist.

Nun werden nur noch per Echo der ausgerichtete Servername und der Status des Dienstes ausgegeben. Hier eine Beispielausgabe:

C:\tmp>getServiceState.bat
silicony.......... STOPPED
bytebag........... UNREACHABLE
192.168.229.11.... STOPPED

Auch die Stringverarbeitung per Shell ist möglich, wenn der Code auch nicht sehr elegant aussieht. Grundsätzlich Das Ausrichten könnte man in eigenes Shell Script auslagern, dem die Daten als Parameter übergeben werden und das ein Ergebnis per Umgebungsvariable zurückliefert (etwa strPad server left 16).

Scriptname feststellen und verwenden

Die an eine Batchdatei übergebenen Parameter werden über die Platzhalter %1 bis %9 angesprochen. Mit Hilfe des Platzhalters %0 erhalten Sie Zugriff auf den Namen des gerade ausgeführten Batches. Nehmen wir an, Sie wollen für einen Batch mit dem Namen getServiceState.bat eine Logdatei schreiben, die den gleichen Namen trägt, aber die Erweiterung .log besitzt. Dann können Sie die Modifizierer aus den for-Schleifen auch hier für die Parameter verwenden, wie die nachfolgende Zeile verdeutlicht.

echo Dies ist eine Logzeile >> %~n0.log

Während %0 immer den Namen des Shell Scripts in der Form enthält, wie er auf der Kommandozeile eingegeben wurde, ist über %~f0 die vollqualifizierte Pfadangabe auslesbar. Im Beispiel oben wird mit %~n0 nur der Namensanteil ohne die Erweiterung ausgelesen. Diese Möglichkeiten haben Sie nicht nur für %0, sondern natürlich auch für alle anderen Parameter, sofern es sich dabei um Dateien handelt.

Speichern von Zusatzdaten

Für viele Batches (auch die aus dem Abschnitt „Ausgabezeilen verändern“) werden Zusatzdaten in Form von Rechnernamen, Listen von Attributen und andere Dinge benötigt. Eine Möglichkeit, diese anzugeben, ist die Kommandozeile. Damit muss aber jedes Mal der gesamte Satz an Daten über die Kommandozeile angegeben werden (und ich kenne keinen Admin, der gerne mehr als nötig tippt). Als Ausweg bietet sich an, die Daten in einer INI-Datei, hostlist.txt oder Ähnlichem zu speichern und sie später einzulesen. Dieser Ansatz ist prinzipiell nicht schlecht, erfordert für die Ausführung und die Weitergabe des Shell Scripts aber immer beide Dateien. Eine dritte Möglichkeit ist die Unterbringung der Parameter direkt in der Batchdatei.

Diese Daten werden dann in der Batchdatei gesucht (mit find, findstr, grep.) und per Kommandoersetzung (Backquoting) verarbeitet. Dazu werden die Zeilen mit den Daten als Kommentar und mit einer definierten Kennung markiert.

Das nachfolgende Listing zeigt noch einmal den ersten Teil des Scripts über die Veränderung der Ausgabezeilen, nur sind diesmal die Namen nicht direkt in der Schleife enthalten, sondern als Kommentar, der dann in der Schleife per Backquoting ausgelesen wird.

@echo off

::.item silicony
::.item bytebag
::.item 192.168.229.11

setlocal
for /F "usebackq tokens=2" %%f in (`findstr "^::.item"
%~f0`) do call :check %%f
endlocal
goto :eof

:check
:: hier die verarbeitung (s. "Ausgabezeilen verändern")

Einige Leser werden jetzt einwenden, dass dabei nichts gewonnen ist. Die Lesbarkeit und die Änderung der Daten ist mit diesem Ansatz aber deutlich besser. Spätestens dann, wenn diese Daten innerhalb des Batches nochmals irgendwo benötigt werden, liegen sie hier nur einmal in der Datei und müssen auch nur einmal gewartet werden. Zudem kann die Liste über die Shell Scripts aus dem kommenden Kapitel „Dateiverwaltung“ oder andere Programme auch geändert, ergänzt oder gelöscht werden. Dies ist bei einem direkten Eintrag in die Schleife nicht möglich.

Ausblick

Dieser Teil der Artikelserie hat sich mit allgemeinen, immer wiederkehrenden Problemstellungen des Shell Scripting beschäftigt. Der nächste Teil der Serie thematisiert sowohl die System-, als auch die Dateiverwaltung. (mzu/mja)

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. Die komplette Ausgabe dieses 320 Seiten starke Buches können Sie in unserem Buch-Partnershop informit.de bestellen.

Artikelserie

Teil 1: Allgemeine Problemstellungen

Teil 2: System- und Dateiverwaltung

Teil 3: ActiveDirectory

Teil 4: Netzwerk