leopold.bloom
Goto Top

Problem mit CountTokens - bzw. Variablenübergabe

Hallo liebe Leute,

mein Name ist Leopold - ich bin erst seit heute Mitglied - habe hier aber schon als Gast eine Menge gelernt - ich möchte mich dafür bedanken. Ich bin zwar gelernter Programmierer, bin aber mehr im Datenbankbereich unterwegs - falls es da Fragen gibt, kann ich vielleicht Unterstützung geben. Ich habe zwar auch schon Batchprogrammierung gemacht, bin aber auf dem Gebiet kein Experte. Also bitte nicht schimpfen wenn mir etwas nicht auf Anhieb klar ist

Ich schreibe an einem Batch, der eine Ini Datei ausliest. Die Ini Datei ist in Sektionen aufgeteilt und wird auch sektionsweise ausgelesen. Die einzelnen Sätze in den Sektionen sind immer gleich aufgebaut - aber haben unterschiedliche Anzahlen von Argumenten, die durch Trennzeichen voneinander abgegrenzt werden. In Sektion1 könnten es 5 Argumente pro Satz sein, die durch ein Pipe Zeichen voneinander getrennt sind, in Sektion2 sind es vielleicht nur 2 Argumente, eventuell sind auch die Trennzeichen verschieden. Ich habe also eine unterschiedliche Anzahl von Tokens und evtl.unterschiedliche Delimiter, aber das könnte ich noch vereinheitlichen. Die unterschiedliche Anzahlan Tokens wird aber bleiben. Oder ich müsste mit Dummys auffüllen, was ich aber nicht möchte.

Es gibt hier in der Community ein wunderbares Script das die Anzahl der Tokens zählt - und das macht bei mir ein für mich nicht einleutendes Problem.

Hier mein Quellcode

Rem Aufrufendes Programm

@echo off&setlocal

set "inifile=MYBATCH.INI"  
set "section=TESTSECTION"  

for /f "delims=:" %%a in ('findstr /binc:"[%section%]" "%inifile%"') do (  
    for /f "tokens=1* delims=$" %%b in ('more +%%a^<"%inifile%"') do (  
         set "key=%%b"  
         setlocal enabledelayedexpansion
         if "!key:~,1!"=="[" (endlocal&goto :weiter)  
	 call :CountTokens  "!key!" "|"   
         echo In Section %section% hat der DS %nTokens% Tokens 	
         endlocal
  )
)
:weiter
set teststring="Hallo|Hallo|hallo|Hallo|Hallo|hallo"  
call :CountTokens  %teststring% "|"    
echo Teststring hat %nTokens% Tokens
goto:eof


Rem Subroutine Tokenszählen

:CountTokens 
setlocal 
set "str=%~1"   
set nTokens=1 

:CountTokensLoop 
call set "newstr=%%str:%~2=%%"   
if "%newstr%" neq "%str%" (   
     set /a nTokens+=1 
     call set "str=%%str:*%~2=%%"   
     goto :CountTokensLoop 
) 
endlocal & set nTokens=%nTokens% 
echo Der Satz hat %nTokens% Tokens 	 	
goto:eof 

Was hier passiert ist relativ simpel. In der For Schleife wird die Sektion in der Ini Datei gesucht und ab da wird satzweise gelesen, bis die nächste Sektion erreicht ist. Jeder gelesene Satz wird zum Token zählen geschickt. Wenn die Sektion zu Ende ist, geht es zur Sprungadresse :weiter weiter. Da wird noch mal ein Teststring zum Token zählen geschickt. Aus gutem Grund. Die Variable nTokens steht nämlich in der Schleife nicht zur Verfügung, wird nicht befüllt oder nicht aktualisiert. Beim Testaufruf aber schon. Und ich komme nicht dahinter, warum es so ist. In der Schleife steht in !key! ein ganzer Satz aus der IniDatei, das kann man vielleicht eleganter machen aber darum geht es mir jetzt nicht. Die Funktion CountTokens wird ordnungsgemäß aufgerufen und am Ende der Funktion CountTokens wird nTokens auch ordnungsgemäß befüllt und ausgegeben. Auch bei dem Aufruf aus der Schleife. Trotzdem steht die Variable im aufrufenden Programm nicht zur Verfügung. Bei meinem Teststring passiert im Prinzip das Gleiche. Es wird ein String übergeben und der Delimiter und da steht im aufrufenden Programm die Variable nTokens dann anschließend zur Verfügung. Zwei im Prinzip gleiche Aufrufe mit unterschiedlichen Folgen. Ich weiß nicht woran es liegt - ist am Aufruf innerhalb der Schleife was falsch? - kann eigentlich nicht sein, weil unten in der Funktion alles richtig läuft. Gibt die Funktion den Rückgabewert nicht richtig zurück? - kann eigentlich auch nicht sein, weil es beim Teststring ja funktioniert. Es liegt auch nicht am setlocal - auch wenn ich den Aufruf in der Schleife nach endlocal mache habe ich das gleiche Problem. Es liegt auch nicht an den Sätzen aus der Ini Datei. Wenn ich meinen Teststring in die Inidatei schreibe, habe ich das gleiche Problem. Wie kann ich die Funktion überreden den Wert richtig zurückzugeben bzw. die Schleife dazu bewegen, die Funktion richtig aufzurufen oder den zurückgegebenen Wert auch anzunehmen? Im "richtigen" Programm soll natürlich innerhalb der Schleife abhängig von der Anzahl der Tokens etwas passieren - ich kann weder auf die Schleife noch auf das Zählen der Tokens verzichten. Ich bitte um Hinweise. Vielen Dank für Eure Mühe.

LG aus HH
Leopold

Content-ID: 179617

Url: https://administrator.de/contentid/179617

Ausgedruckt am: 22.11.2024 um 18:11 Uhr

bastla
bastla 26.01.2012 um 17:36:29 Uhr
Goto Top
Hallo Leopold und willkommen als Mitglied!

Unabhängig vom Rest (insb der Sprung mit "goto" aus der Schleife sollte nicht nötig sein): Verwende die Schreibweise !nTokens! ...

Grüße
bastla
icsat
icsat 26.01.2012 um 20:11:54 Uhr
Goto Top
Hallo bastla,

Zitat von @bastla:
Verwende die
Schreibweise !nTokens! ...

ich bin mir sehr sicher, dass Du damit recht hast. Allerdings stehe ich gerade auf dem Schlauch und verstehe nicht warum. "nTokens" wird doch in einer neuen cmd-Instanz gesetzt. Warum steht die Variable dann in der for-Schleife bei Verwendung mit % nicht zur Verfügung?


Gruß icsat
Leopold.Bloom
Leopold.Bloom 26.01.2012 um 20:15:53 Uhr
Goto Top
Vielen Dank, das war es schon.

Einen sauberen Ausstieg aus einer doppelten For Schleife habe ich noch nicht gefunden. Ich habe Deinen Ansatz gesehen mit if not_defined_break - aber aus einer doppelten Schleife führt es so noch nicht beim ersten Versuch. Goto ist zwar häßlich aber an der Stelle inhaltlich nicht falsch, die Schleife soll sofort abgebrochen werden und die Schleifenbedingungen sollen nicht unbedingt noch mal geprüft werden. Bei geschachtelten Schleifen müsste dann ja ein weiteres break gesetzt werden, welches auch die äußere Schleife beendet. Wenn ich es richtig verstanden habe ist es eigentlich überhaupt nicht vorgesehen, dass For Schleifen abgebrochen werden. Ich werde aber noch mal ein bisschen probieren und forschen.

Ahja - jetzt geht es doch - ja es ist besserer Programmierstil. Das hier sieht schon besser aus.

for /f "delims=:" %%a in ('findstr /binc:"[%section%]" "%inifile%"')  do  if not defined done (  
  for /f "tokens=1* delims=$" %%b in ('more +%%a^<"%inifile%"') do  if not defined done (   
    set "key=%%b"  
    setlocal enabledelayedexpansion
    if "!key:~,1!"=="["  ( set done=break   
	) else ( 
			call :CountTokens  "!key!" "|"   
			if  "!max_tokens!"  LSS  "!nTokens!"  set max_tokens=!nTokens!  
		     )	
	)
  )
echo In Section %section% hat der DS  !max_tokens! Tokens 	
endlocal
goto:eof

Schließt hier jeder selbst die Threads oder macht das der Moderator?

LG aus HH

Leopold
Biber
Biber 26.01.2012 um 20:21:25 Uhr
Goto Top
Moin Leopold.Bloom,

willkommen im Forum.

Zitat von @Leopold.Bloom:
Schließt hier jeder selbst die Threads oder macht das der Moderator?
Natürlich!

Grüße
Biber
icsat
icsat 26.01.2012 um 20:24:02 Uhr
Goto Top
Hallo Leopold,

ich denke Du darfst Deine Threads schon selber als gelöst kennzeichen. Wer außer Dir sollte sonst beurteilen können ob Dein Problem für dich ausreichend gelöst wurde?
Gelöst war das Problem ja schon mit bastla's Post aber es hätte ja durchaus sein können, dass Du noch Fragen nach dem "Warum?" (so wie ich oben) hast.

Und außerdem: Warum solltest Du die Gelöst funktion nicht nutzen, wo sie Dir doch zur Verfügung steht?


Gruß icsAT
bastla
bastla 26.01.2012 um 20:55:01 Uhr
Goto Top
Hallo Leopold!
Einen sauberen Ausstieg aus einer doppelten For Schleife
... brauchst Du nicht, wenn Du die erste Schleife eliminierst - die Zeilennummer kannst Du ja vorweg bestimmen und in einer Variablen platzieren - im Gegenzug könntest Du dann die Variable !key! einsparen, wenn grundsätzlich "delayedExpansion" vermieden werden soll und die Prüfung auf "[ ist enthalten" schon genügen würde (genauer wäre natürlich findstr /b, was aber am für Batch kontraproduktiven Delimiter "|" scheitert); schließlich kann noch per Schaltervariablen (ich hatte mich schon für %InSection% entschieden bevor ich Deine "%done%-Variante" gesehen habe) die Schleife "ordnungsgemäß" beendet werden:
for /f "delims=:" %%a in ('findstr /binc:"[%section%]" "%inifile%"') do set /a Zeile=%%a  
set "InSection=True"  

setlocal enabledelayedexpansion
for /f "tokens=1* delims=$" %%b in ('more +%Zeile%^<"%inifile%"') do (  
    echo "%%b"|findstr "[">nul && set "InSection="  
    if defined InSection (
        call :CountTokens  "%%b" "|"  
        if  !max_tokens!  LSS  !nTokens!  set /a max_tokens=!nTokens!
    )
    echo In Section %section% hat der DS  !max_tokens! Tokens
)
endlocal
Performancemäßig sollte allerdings Dein ursprünglicher Ansatz Vorteile haben (habe ich nicht getestet und spielt vielleicht bei einer Ini-Datei auch keine so große Rolle) ...

Noch ein Hinweis zum Vergleich mit "LSS": Wenn Du Anführungszeichen verwendest, wird ein String-Vergleich durchgeführt (und dann ist "15" kleiner als "3") ...

Grüße
bastla
bastla
bastla 26.01.2012 um 20:59:09 Uhr
Goto Top
Hallo icsAT!
Warum steht die Variable dann in der for-Schleife bei Verwendung mit % nicht zur Verfügung?
Bei "nicht-verzögerter Variablenauflösung" (also im Normalfall) wird zu Beginn der Schleife der jeweilige Wert der Varaiblen ermittelt - Änderungen daran können zwar in der Schleife (oder einem davon aufgerufenen Unterprogramm) erfolgen, allerdings "interessiert" sich der Interpreter erst nach Ende der Schleife dafür - mit dem Auftrag zur "verzögerten" Auflösung der Variablen (durch die Schreibweise !nTokens!) wird diese bei jedem Schleifendurchlauf neuerlich vorgenommen ...

Grüße
bastla
icsat
icsat 26.01.2012 um 21:43:12 Uhr
Goto Top
Hallo bastla,

danke für die Erklärung, wobei mir das Thema grundsätzlich klar ist. Ich war vorhin nur der Meinung mit der Verwendung von call und einer neuen cmd-Instanz könnte man das umgehen aber da war ich wohl auf dem Holzweg. Ich glaube ich habe da was mit der Verwendung von dynamischen Variablennamen verwechselt.

Gruß icsAT
bastla
bastla 26.01.2012 um 21:49:57 Uhr
Goto Top
Hallo icsAT!
Verwendung von dynamischen Variablennamen
Du meinst damit vermutlich etwas in der Art:
call echo %%Var%Nr%%%
- das ist dann tatsächlich eine andere Baustelle ... face-wink

Grüße
bastla
Leopold.Bloom
Leopold.Bloom 27.01.2012 um 03:37:32 Uhr
Goto Top
Hallo Bastla und auch an die anderen Helfer,

vielen Dank für Eure schnelle und unkomplizierte Hilfe. Und danke, dass ich so gut hier angekommen bin. Das ist leider nicht bei jedem Forum der Fall.

@bastla

Auf die Performance kommt es in der Tat nicht an. Was mir wichtig ist, ist, dass ich möglichst weiß, was ich da eigentlich tue - und dass der Code so klar ist, dass ich es auch in einem halben Jahr noch weiß und es notfalls eigenständig ändern kann. Auch wenn es vielleicht nicht so super toll kommentiert ist. Die Entzerrung der beiden Schleifen finde ich für die Klarheit gut, die Schleife ohne Sprungadresse zu verlassen finde ich auch gut. Selbst dass die Schreibweise !nTokens! zwingend erforderlich war, ist mir klar - nur drei Dinge sind mir noch nicht klar. Zum einen - ich hatte ursprünglich den LSS Vergleich ohne Anführungszeichen, habe aber immer Meldungen bekommen, dass der dann folgende Befehl an der Stelle syntaktisch nicht verarbeitbar sei (WinXP SP3 im Command Fenster) - und zwar eindeutig in dieser Zeile - er hat erst Ruhe gegeben, als ich die Anführungszeichen gesetzt habe. Jetzt funktioniert es auch ohne. Kann es sein, dass sich da irgendein anderer Fehler manifestiert hat? Das zweite was mir nicht klar ist - warum sind Pipes als Delimiter kontraproduktiv? Bzw. was wäre besser? Ursprünglich hatte ich in der Ini Datei Gleichheitszeichen in diesem Stil und habe im Batch nach einzelnen Schlüsseln gesucht.

[PFADE]
encfs=C:\tools\encfs4win\encfs.exe

dann sind aber bei manchen Eintragungen weitere Argumente dazugekommen und ich lese inzwischen nicht mehr einzelne Schlüssel sondern die ganze Sektion ein - ich wollte nicht noch weitere Gleichheitszeichen in eine Zeile schreiben. = ist ein Zuweisungsoperator und somit eigentlich systemseitig besetzt, das gilt für die Pipe auch, nicht dass es ein Zuweisungsoperator wäre aber, dass es systemseitig besetzt ist. CountTokens geht in die Knie, wenn ich der Routine ein Gleichheitszeichen als Delimiter anbiete, die Pipe nimmt sie klaglos. Ich könnte natürlich § nehmen oder $ als Delimiter - aber die anderen Zeichen könnten fast alle in den Argumenten vorkommen.

Eine dritter Fragenkomplex noch.

Die Zeile

echo "%%b"|findstr "[">nul && set "InSection="   

verstehe ich noch nicht richtig. Warum müssen an der Stelle zwei & sein? Warum reicht nicht eines? Das Umlenken nach nul ist mir klar. Aber wie arbeitet der erste Teil dieser Anweisung? Suche im String %%b nach dem Zeichen "[" - falls Du es gefunden hast setze InSection auf nichts. Damit wird die Schleife nicht beendet - sie läuft weiter bis zum Ende der Ini Datei - es wird nur nichts mehr zum Tokens zählen geschickt. Also kein Schleifenabbruch. Was würde sich bei einem anderen Delimiter ändern und wie kommt da findstr /b ins Spiel? Verstehe ich es richtig - die Zeile sucht nicht nur am String Anfang nach "[" sondern imgesamten String, oder? Das wäre nicht günstig, weil das Zeichen in einem Argument vorkommen kann - nur nicht am Anfang einer Zeile. Würde das hier helfen?

 echo "%%b:~,1"|findstr "[">nul && set "InSection="   

Ichkriege zumindestkeinen Syntaxfehler. Ahja - ich habe nachgeschaut - findstr /b sucht nur am Zeilenanfang. Warum scheitert das an der Pipe als Delimiter? Und noch was zum besseren Verständnis. Hier wird ja nirgendwo die Datei explizit geöffnet zum Lesen und auch nicht explizit geschlossen. Wann passiert das?

Der erste Lesezugriff findet bei der Zeilenfeststellung statt - wird da die Datei geöffnet? Oder schon beim SET? Und wann wird sie wieder geschlossen? Nach jedem Lesezugriff? Oder wenn eof erreicht ist? Oder bleibt sie die ganze Zeit offen bis der Batch zu Ende ist? Was passiert mit der Datei, wenn der Batch abschmiert? Bleibt sie dann offen? Ich muss noch viel lernen. Wenn Dir das zu viele Fragen sind ist es nicht schlimm. Ich will Deine Zeit nicht über Gebühr in Anspruch nehmen. Ich bin sehr froh überhaupt voranzukommen und ein bisschen was zu kapieren.

LG aus HH

Leopold
Skyemugen
Skyemugen 27.01.2012 um 08:29:24 Uhr
Goto Top
Aloha Leopold,

ui, so viel Text mussten wir schon lange nicht mehr im Batch-Bereich lesen, schon gar nicht so viele Fragen, die sonst keiner stellt *gg*
Also als Erstes mal:

Der Unterschied zwischen & und && ist der, dass ein Befehl nach einer & Verknüpfung immer ausgeführt wird, während mit einem && der nachfolgende Befehl nur ausgeführt wird, wenn der erste Befehl erfolgreich war, sprich:
echo "abc"|findstr "[">nul & echo OK
würde dir ein OK ausgeben, egal, ob findstr nun erfolgreich etwas gefunden hat oder nicht, im Prinzip ist es wie
echo "abc"|findstr "[">nul  
echo OK
Im Gegenzug jedoch gibt dir
echo "abc"|findstr "[">nul && echo OK
kein OK aus, weil findstr nicht erfolgreich war, der Gegenpart dazu wäre
echo "abc"|findstr "[">nul || echo OK
Wenn findstr nicht erfolgreich, dann echo.

Du hast zwar findstr /b inzwischen gefunden, dennoch erneut der Hinweis:
echo "%%b:~,1"
kann nicht funktionieren, wie du es willst, denn die Ausgabe wäre jetzt "abc:~,1", was natürlich keinen Syntaxfehler verursacht, warum auch.
Derartige Spielchen gingen nur, wenn
set "Var=%%b"  
echo "%Var:~,1%" BZW echo "!Var:~,1!"  

Das mit dem Lesezugriff ist eine gute Frage, ich nehme an, dieser beginnt beim ersten findstr und endet nach der Schleife, ganz sicher bin ich mir jedoch nicht, als anderes Beispiel kann ich dir aber z.B. aufzeigen:
echo Text>%datei%
echo Text2>>%datei%
echo Text3>>%datei%
macht die Datei 3x auf und 3x zu, während
(echo Text
echo Text2
echo Text3)>%datei%
1x auf- und zumacht

Was passiert, wenn die Batch abschmiert, ganz einfach: Dann gibt es keinen Zugriff auf die Datei und sie ist somit nicht in Benutzung, also geschlossen.

greetz André

edit: Es ging irgendwo noch um eine Pipe? Ich sag's ja: Zu viel Text vor'm Frühstück face-wink
bastla
bastla 27.01.2012 um 08:47:11 Uhr
Goto Top
Hallo Leopold!

Nur ganz kurz wegen des "|": Wie Du schon festgestellt hast, wird das Pipe-Symbol vom System verwendet (bzw speziell interpretiert) - daher kannst Du es nicht einfach per
echo 1|2|3
ausgeben, sondern musst es mit "^" maskieren oder unter Anführungszeichen setzen.

Tatsächlich ginge es aber in diesem Fall trotzdem, nur den Anfang der Zeile zu überprüfen - es müsste eben das Anführungszeichen am Anfang in die "findstr"-Suche einbezogen (und dafür innerhalb des Suchstrings verdoppelt) werden - fördert zwar die Nachvollziehbarkeit nicht, sollte aber funktionieren:
echo "%%b"|findstr /b """[">nul && set "InSection="
Ich würde aber trotzdem ein anderes Trennzeichen (eher "harmlos" wären zB §$#_) verwenden ...

Grüße
bastla
Leopold.Bloom
Leopold.Bloom 27.01.2012 um 09:51:24 Uhr
Goto Top
Moin,

noch mal vielen Dank für Euren großen Einsatz. Seid Ihr eigentlich Profi Supporter oder macht Ihr das in Eurer Freizeit?

@Skyemugen

Tut mir leid, wenn ich geschwätzig war - ich habe immer Sorgen, dass ich sonst mein Anliegen nicht klar genug formuliert kriege. Und dass meine Fragen sicher schon mal gestellt wurden, kann ich mir lebhaft vorstellen. Jeder der mit dieser Art Programmierung anfängt, wird auf die gleichen Probleme oder ähnliche stoßen. Das geht mir mit SQL und anderen Sprachen nicht anders. Nur, dass ich da oft die Fragen beantworten kann.

Jedenfalls haben Deine Ausführungen mir wieder etwas mehr Klarheit verschafft. Danke.

@bastla

Es funktioniert mit findstr /b auch so - ich habe es getestet. In der Zeile mit den Section Überschriften kommt das Trennzeichen nicht vor, es steht nur in den Sätzen darunter als Trennzeichen zwischen den Argumenten. Da kommt wiederum die linke Klammer nicht als erstes Zeichen vor. Folglich müsste es für diese Abfrage unerheblich sein. Ich werde aber trotzdem lieber ein anderes Trennzeichen verwenden.

Auch Dir vielen Dank. Ich komme voran. Sehr schön.

LG

Leopold
Skyemugen
Skyemugen 27.01.2012 um 10:12:23 Uhr
Goto Top
Zitat von @Leopold.Bloom:
noch mal vielen Dank für Euren großen Einsatz. Seid Ihr eigentlich Profi Supporter oder macht Ihr das in Eurer Freizeit?
*hust* Wenn ich nach 18 Uhr schreibe, mach ich es in der Freizeit, sonst ist es eigentlich meine Arbeitszeit, wo ich arbeiten sollte aber wenn man mal Luft hat ...

@Skyemugen

Tut mir leid, wenn ich geschwätzig war - ich habe immer Sorgen, dass ich sonst mein Anliegen nicht klar genug formuliert kriege. Und dass meine Fragen sicher schon mal gestellt wurden, kann ich mir lebhaft vorstellen. Jeder der mit dieser Art Programmierung anfängt, wird auf die gleichen Probleme oder ähnliche stoßen. Das geht mir mit SQL und anderen Sprachen nicht anders. Nur, dass ich da oft die Fragen beantworten kann.

Wer hat etwas von geschwätzig geschrieben? Es war nur ungewohnt face-wink Wir sind immer froh, wenn Leute nicht nur herkommen, um c&p die Lösung vorgesetzt zu bekommen, sondern selbst etwas lernen, hey ich habe Rumbätscheln auch erst mit/bei Administrator.de gelernt.
Gerade lieber etwas genauer beschrieben als eine unklare Anfrage, wie wir sie zu 80% im Batch-Bereich haben.

greetz André