jeb-the-batcher
Goto Top

Unverständlicher Tokenizer in Klammern

Bei dem Versuch den Batch-Tokenizer (der eine Zeile in einzelne Teile zerlegt), dachte ich einem guten Modell zur Erklärung schon recht nah zu sein.
Aber dann habe ich, ein mir vollkommen unerklärliches Phänomen entdeckt.

Das es Probleme mit Labeln in Klammern gibt ist ja schon länger bekannt.
Bisher war ich der Meinung, das Label nur als letztes in einem Klammerblock verboten sind

@echo off
(
:MeinLabel
echo Klappt
)

(
echo Klappt nicht
:MeinLabel
)

aber dann bin ich auf einen Beitrag gestoßen, bei dem auch innerhalb eines Blocks ein Problem entsteht.

@echo off
(
echo start
:comment
:comment
echo KLAPPT
)

(
echo start
::comment
:comment
echo KLAPPT
)

(
echo start
:comment
::comment
echo KLAPPT nicht
)

Dann habe ich noch ein wenig gespielt, und bin auf ein noch ungewöhlicheres Verhalten gestoßen
@echo off
(
:label eins zwei drei ^

WegIsses echo two three
echo Nur das erste Token ist weg
)
echo ---
(
:label eins zwei drei^

WegIsses echo two three
echo Jetzt ist es ganz weg
)

:label eins zwei drei ^

WegIsses echo two three
echo Ohne Klammer ist es immer weg

Ausgabe
two three
Nur das erste Token ist weg
---
Jetzt ist es ganz weg
---
Ohne Klammer ist es immer weg

Ohne Klammern ist das Verhalten normal, das Caret am Ende wird als Multiline ausgewertet, die nächste Zeile wird gelesen, da die aber leer ist wird das <LF> der leeren Zeile Escaped, damit ist kein Zeilenende mehr da und die nächste Zeile wird auch noch an das Multiline dran gehängt.

Varianten davon
@echo off
setlocal EnableDelayedExpansion
echo Hallo ^

Dies zeigt einen Text mit Zeilenumbruch
echo  -----
set lf=^


rem Zwei Leerzeilen sind wichtig, damit wird jetzt ein einzelnes LF erzeugt
rem Allerdings kann man ein LF nur mit DelayedExpansion ausgeben
echo Erste Zeile!LF!Zweite Zeile

Ausgabe
Hallo
Dies zeigt einen Text mit Zeilenumbruch
Erste Zeile
Zweite Zeile

Aber wie kann man das verschwinden eines einzelnen Token in der Klammer erklären ?

jeb

Content-ID: 151317

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

Ausgedruckt am: 22.11.2024 um 01:11 Uhr

rubberman
rubberman 19.09.2010 um 16:51:14 Uhr
Goto Top
Hallo jeb,

unverständlich ist und bleibt das für mich auch.
Vielleicht liegt des Rätsels Lösung in der Art und Weise wie mit Zeilenumbrüchen in Multilines umgegangen wird.
@echo off &setlocal &prompt ¯¯¯ &echo on

(
echo Test
)

(
echo Hallo
echo Test
)
Ausgabe:
»»» (echo Test )
Test

»»» (
echo Hallo
 echo Test
)
Hallo
Test
Die Frage ist, woher kommen die Leerzeichen (hinter "Test" im ersten Beispiel bzw. vor dem zweiten echo im zweiten Beispiel). Werden Zeilenumbrüche tatsächlich als Leerzeichen interpretiert bzw. wird ein zusätliches Leerzeichen angehängt? Kann eigentlich nicht sein, denn die Erzeugung des Line Feed Characters würde auch im Multiline Block funktionieren.
@echo off &setlocal enabledelayedexpansion &prompt ¯¯¯ &echo on

(
set LF=^



)
echo a!LF!b
Ausgabe:
»»» (set LF=
 )

»»» echo a!LF!b
a
b
Andererseits deutet einiges auf die Ersetzung des Zeilenumbruches hin. Ein Caret escapet ja normalerweise nur ein Zeichen. Der "normale" Zeilenumbruch besteht aber aus CR+LF. Zwei Zeichen, hmm...
Und wenn man sich die Erzeugung des LF Characters ansieht, käme ja noch ein zusätzliches CR dazu, das es zu escapen gilt.

Noch habe ich da auch keine Idee, wie man nachvollziehbar testen kann.

Grüße
rubberman
miniversum
miniversum 19.09.2010 um 18:27:51 Uhr
Goto Top
Hat jetzt nicht wirklich mit diesem Thema zu tun sondern vielleicht eher ein allgemeinerer Kommentar:
Ich würde solche "fragwürdigen" Konstrukte wie Excape Zeichen am ende der Zeile und dergleichen einfach nicht benutzen. Da mach ich mit bei einer batch Datei lieber etwas ausführlicher und mehr Arbeit und dafür vermeide ich so Sachen ...
rubberman
rubberman 19.09.2010 um 18:56:57 Uhr
Goto Top
Hallo miniversum.

Ich gebe dir ja Recht. Man muss es nicht nutzen, es gibt bessere Möglichkeiten. Aber das ist auch nicht der Sinn der Übung.
Es geht nur darum herauszufinden, wie cmd.exe solchen Code verarbeitet, um möglichst allgemeingültige Regeln davon abzuleiten. Das wiederum hilft, wenn irgendein Batchfile nicht so funktioniert, wie man es erwartet. Ggf. findet man dann den Grund und bestenfalls auch eine Lösung ...
Ich persönlich finde es sehr gut, dass sich mal jemand mit dieser "Grundlagenforschung" beschäftigt. Das Internet hat da so gut wie nichts parat.

Grüße
rubberman
jeb-the-batcher
jeb-the-batcher 19.09.2010 um 19:12:27 Uhr
Goto Top
Zitat von @miniversum:
Ich würde solche "fragwürdigen" Konstrukte wie Excape Zeichen am ende der Zeile und dergleichen einfach nicht
benutzen. Da mach ich mit bei einer batch Datei lieber etwas ausführlicher und mehr Arbeit und dafür vermeide ich so
Sachen ...

Wenn man wissen möchte wie und warum etwas funktioniert, muss man sich eben Tests ausdenken die etwas über die inneren Strukturen verraten.
Ohne sowas würden wir noch in Höheln wohnen und die Erde für eine Scheibe halten(und den Batch Interpreter für einen Zufallsgenerator) face-smile

Zitat von @rubberman:
Andererseits deutet einiges auf die Ersetzung des Zeilenumbruches hin. Ein Caret escapet ja normalerweise nur ein Zeichen. Der
"normale" Zeilenumbruch besteht aber aus CR+LF. Zwei Zeichen, hmm...
Und wenn man sich die Erzeugung des LF Characters ansieht, käme ja noch ein zusätzliches CR dazu, das es zu escapen
gilt.


Das mit dem "verschwundenen" <CR> ergibt sich aus Phase1.1

Nach dem ersetzen der Prozent Phase, werden alle <CR> entfernt (Daher können die ja auch nur mit !CR! ausgegeben werden).
Daher klappt der LF Trick.

Als Test für das <CR> verschwinden
@echo off
cls
setlocal EnableDelayedExpansion
call :CreateCR
set "empty="  
echo Eine %CR%Zeile1
echo Eine ^%CR%Zeile2
echo Eine ^%CR%&Zeile3
echo Leer ^%empty%& geht auch
echo Eine Laus oder doch !CR!eINE mAUS
goto :eof

::: Erzeugt ein einzelnes CR Zeichen in einer Variable
:CreateCR 
setlocal EnableDelayedExpansion EnableExtensions 
set "X=."   
for /L %%c in (1,1,13) DO set "X=!X:~0,4093!!X:~0,4093!"  
REM In X werden 8186 Punkte abgelegt, man kann aber auch was anderes nehmen

REM Jetzt muss genau die richtige Länge ausgegeben werden
REM Dann passt beim expandieren der Zeile und der anschliessenden Ausgabe das <CR><LF> nicht mehr in den Puffer
REM sondern nur noch das <CR>
echo !X!123 > %temp%\cr.tmp

REM Damit hinterher aber die Zeile komplett gelesen werden kann, noch direkt ein Extra <CR><LF> anhängen
echo\>> %temp%\cr.tmp 

REM Jetzt noch die Datei einlesen, im ersten Token sind dann die Punkte im Zweiten das goldene <CR>
for /f "tokens=2 usebackq" %%a in ("%temp%\cr.tmp") do (   
	endlocal 
	set "cr=%%a"  
	goto :eof 
) 
REM Am Schluss noch ein goto :eof, falls es Probleme mit der Datei gab
goto :eof

Ausgabe
Eine Zeile1
Eine Zeile2
Eine &Zeile3
Leer & geht auch
eINE mAUS oder doch

Bei der letzten Zeile ist natürlich der gesamte Satz ausgegeben worden, aber durch das CR ist der Cursor halt wieder zum Zeilenanfang gesprungen.

jeb
miniversum
miniversum 19.09.2010 um 19:23:06 Uhr
Goto Top
Zitat von @jeb-the-batcher:
Wenn man wissen möchte wie und warum etwas funktioniert, muss man sich eben Tests ausdenken die etwas über die inneren
Strukturen verraten.
Ohne sowas würden wir noch in Höheln wohnen und die Erde für eine Scheibe halten(und den Batch Interpreter für
einen Zufallsgenerator) face-smile
1. Testest du das mit allen Windowsversionen und alles Servicepacks? Kannst du sicherstellen das sich nicht ab einem gewissen Versionsstand das Verhalten anders ist?
2. Würdest du nach deiner "Untersuchung" solche Konstrukte wirklich ruhigen Gewissens verwenden?
3. Was nützt dir diese "Untersuchung" undokumentierter Funktionen für die du keine Garantie hast das es nicht doch noch Ausnahmefälle geben wird die ein anderes Verhalten hervorrufen?
4. Praktischer nutzen?
rubberman
rubberman 19.09.2010 um 19:43:59 Uhr
Goto Top
Zitat von @jeb-the-batcher:
Das mit dem "verschwundenen" <CR> ergibt sich aus Phase1.1
Stimmt.
Was ist aber mit den (zumindest angezeigten) zusätzlichen Leerzeichen? Irgendetwas muss im Multiline Block anders laufen als sonst.

Grüße
rubberman
jeb-the-batcher
jeb-the-batcher 19.09.2010 um 20:19:06 Uhr
Goto Top
1. Ich teste mit den Versionen die ich habe, und ich gehe davon aus, dass auch bei Microsoft niemand etwas ohne guten Grund ändert.
2. Es geht mir nicht um die Konstrukte, mir geht es darum zu erklären warum man z.B. für verschiedene Sonderzeichen unterschiedliche Wege gehen muss um sie auszugeben.
echo Zeig mir ein Prozent %%
echo Zeig mir ein Ampersand ^&
echo Zeig mir ein Ausrufezeichen ^^! 
Ohne Untersuchung, ist das bloß rumgerate.

3. Wie bei 2., es geht nicht um die undokumentierten Funktionen an sich, aber wenn ich den Parser verstehe, dann weiß ich auch was machbar ist und was eben nicht.
Du hast natürlich recht, bei den Grenzfällen kann man nicht davon ausgehen, dass die immer in allen Versionen funktionieren, weil das möglicherweise nicht gewollte sondern nur Nebeneffekte der Programmierung sind.
z.B. verhält sich
set "caret=^"  
call echo und jetzt ein Vista Crash %%caret%%
rem für Vista hilft nur ein Kill, XP dagegen ignoriert das Multiline beim zweiten Expandieren.
Es läßt sich naturbedingt hier nicht feststellen, ob es noch unentdecktes Verhalten gibt, dazu müßte man wirklich alles testen (und soviel Zeit habe ich grade nicht).

4. Wie in 2. und 3. beschrieben, Wissen über die genaue Syntax.
Und Forschung hat selten einen direkten praktischen Nutzen, sollte man es dann lieber direkt lassen?

Ich bin professioneller Ansi-C Programmierer, und kann mich noch an meine Anfangszeit erinnern, bei der ich Dinge übernommen habe ohne zu verstehen, warum die da so stehen, heute weiß ich bei jedem Ausdruck was der Compiler oder der Linker dazu denkt und daraus erzeugt.
Und wenn was nicht läuft kann ich herrausfinden ob mein Code einen Fehler hat oder der Compiler, früher konnte ich nur im Nebel stochern.
miniversum
miniversum 19.09.2010 um 22:29:42 Uhr
Goto Top
Nur so ein Beispiel warum ich das Problematisch sehe. Dein Code:
echo Zeig mir ein Prozent %%
echo Zeig mir ein Ampersand ^&
echo Zeig mir ein Ausrufezeichen ^^! 
ergibt bei mir (Windows xp pro mit sp3 und den aktuellen Updates) :
Zeig mir ein Prozent %
Zeig mir ein Ampersand &
Zeig mir ein Ausrufezeichen ^!
und
echo Zeig mir ein Ausrufezeichen ^! 
ergibt:
Zeig mir ein Ausrufezeichen !

Also hast du hier anscheinend schon Unterschiede, nur mal als ein Beispiel.
Der Vergleich mit ANSI-C ist auch interessant weil ich selbst aus Erfahrung weiß das verschiedene (mehr oder weniger gute, aber angeblich immer ANSI konforme Compiler) den identischen C Code in unterschiedlichen Maschinencode umsetzen können, wenn es z.B. um Strukturen geht.
Das Argument
1. Ich teste mit den Versionen die ich habe, und ich gehe davon aus, dass auch bei Microsoft niemand etwas ohne guten Grund ändert.
schließt ja nicht aus, dass mit einem bestimmtem Update dennoch eine Änderung dazu kommt, der Programmierer auch den Grund kennt, Du aber nicht.
Und da Fehler und unerwartetes Verhalten in Batch Skripten erwiesenermaßen abhängig von der Version und der eingestellten Sprache ist (ja das ist bei einigen Befehlen wirklich so) und ich auch schon Unterschiede in Abhängigkeit vom Speicherort der Batch Datei (Lokal oder Netzwerkpfad) erlebt habe bevorzuge ich es eben solche "grenzwertigen" Konstrukte nicht zu benutzen und im Fehlerfall lieber das echo off bzw. an der betreffenden Stelle ein echo on einzufügen um zu sehen was wirklich geschieht.
jeb-the-batcher
jeb-the-batcher 20.09.2010 um 12:59:44 Uhr
Goto Top
Zitat von @miniversum:
Nur so ein Beispiel warum ich das Problematisch sehe. Dein Code:
...
ergibt:
Zeig mir ein Ausrufezeichen !
> 

Also hast du hier anscheinend schon Unterschiede, nur mal als ein Beispiel.

Beim Ausrufenzeichen gibt es keinen Unterschied, ich habe nur vergessen das
setlocal EnableDelayedExpansion

mit anzugeben.

Und spätestens an der Stelle ist es dann doch recht hilfreich, wenn man versteht, warum man bei Ausrufezeichen zwei Carets benötigt, bei allen anderen aber nur eins, und warum man ein Prozent gar nicht mit Carets escapen kann.


Der Vergleich mit ANSI-C ist auch interessant weil ich selbst aus Erfahrung weiß das verschiedene (mehr oder weniger gute,
aber angeblich immer ANSI konforme Compiler) den identischen C Code in unterschiedlichen Maschinencode umsetzen können, wenn
es z.B. um Strukturen geht.
Aber das ist ja auch kein Problem und durchaus erlaubt, wenn man die ISO/IEC-Norm gelesen hat, weiß man an welchen Stellen ein Ansi-C Compiler Freiheiten hat, und wodrauf man sich absolut verlassen kann (Sonst ist er halt nicht mehr ISO-konform).
jeb-the-batcher
jeb-the-batcher 21.09.2010 um 18:47:56 Uhr
Goto Top
Ich hab ein wenig geforscht...

Und ich denke ich kann es jetzt halbwegs beschreiben.

In einem Klammerblock wirken sich label direkt auf die nächste Zeile aus.
Der erste Token der nächsten Zeile wird als Kommando betrachtet, was ja im allgemeinen ohnehin so ist.
Allerdings in diesem Fall ohne Ausnahme, sprich auch ein Label oder eine schliessende Klammer wird nur noch als Kommando gesehen.


Sprich, hier wird versucht ::label2 (beachte die zwei Doppelüunkte) auszuführen, was fehlschlägt
(
:label1
::label2
)

Dies schlägt komplett fehl weil keine schliessende Klammer gefunden wird(die wird als Kommando angesehen)
(
echo 1
:label1
)

Das klappt allerdings
(
echo 1
:label1
echo nix)

Und alle "geraden" Lableblöcke funktionieren
(
echo 1
:label1a
:label1b
echo2
:label2a
:label2b
)

Der spannende Teil ist, wie die zweite Zeile interpretiert wird
(
echo 1
:label der eine Sonderbehandlung der naechsten Zeile erzeugt
:\..\%~nx0&echo Und dadruch sehen wir dies
)

jeb - immer auf der Suche
rubberman
rubberman 21.09.2010 um 23:36:30 Uhr
Goto Top
Erstaunlich ^^
Ähnliches hatte ich auch getestet, war aber mit dem Leerzeichen als vermeintlichen Übeltäter auf der falschen Fährte.

Der letzte Code endete bei mir erst mal in einem Fehler (Leerzeichen im Name face-wink).
(
echo 1
:label der eine Sonderbehandlung der naechsten Zeile erzeugt
:\..\%~snx0&echo Und dadruch sehen wir dies
)
... schaffte Abhilfe.

Grüße
rubberman
jeb-the-batcher
jeb-the-batcher 21.09.2010 um 23:56:07 Uhr
Goto Top
Der Effekt ist ja auch an sich erstaunlich,

es wird nach der Datei gesucht, aber gestartet wird sie nicht!

Einfacher ist es daher
(
:lab2
:&echo Einfache Ausgabe

:lab3
:: 2>nul 
)

Aber ganz zufrieden bin ich nicht.

( 
:label eins zwei drei ^ 

WegIsses echo two three 
echo Nur das erste Token ist weg 
) 
echo --- 
( 
:label eins zwei drei^ 

WegIsses echo two three 
echo Jetzt ist es ganz weg 
) 
Denn diesen Teil kann ich mir damit immer noch nicht richtig erklären

Bei deinen Leerzeichen könnte es sich um versuchte Formatierung handeln (gewollt aber nicht so richtig gekonnt), oder eben doch um einen Hinweis, wie Klammern ausgewertet werden.

jeb