pedant
Goto Top

Warum funktioniert das echo in meiner for-Schleife auch mit Sonderzeichen?

Hallo,

hier geht es um eine Verständnisfrage.
Warum funktioniert das echo in der for-Schleife auch mit Sonderzeichen?

Vorab

echo "<|&>"=>"<|&>"echo <|&>=>"|" kann syntaktisch an dieser Stelle nicht verarbeitet werden.

Soweit finde ich das nicht überraschend.
Die Überraschung (für mich) folgt in der Ausgabe der zweiten Variante meiner Batch.


Meine Batch(es)

Eine Batch die ohne Fehler läuft...
@echo off

REM set z="<|&>"  
set z="abc"  
echo %z%>temp.txt

echo 1. Ausgabe direkt
echo %z% mit Anfuehrungszeichen
echo %z:~1,-1% ohne Anfuehrungszeichen

for /f %%a in ('type temp.txt') do (  
echo 2. Ausgabe in for
echo %%a mit Anfuehrungszeichen
echo %%~a ohne Anfuehrungszeichen
call :ausgabe %%a
)
del temp.txt
goto :eof

:ausgabe
echo 3. Ausgabe per call
echo %1 mit Anfuehrungszeichen
echo %~1 ohne Anfuehrungszeichen
goto :eof

...und deren Ausgabe
1. Ausgabe direkt
"abc" mit Anfuehrungszeichen
abc ohne Anfuehrungszeichen
2. Ausgabe in for
"abc" mit Anfuehrungszeichen
abc ohne Anfuehrungszeichen
3. Ausgabe per call
"abc" mit Anfuehrungszeichen
abc ohne Anfuehrungszeichen


Wenn ich jetzt das REM von Zeile 3 zur Zeile 4 verlagere, muss ich zur Fehlervermeidung die Zeilen 9 und 23 mit einem REM versehen.

Eine Batch, die zwei weitere REM benötigt (aber nicht drei)...
@echo off

set z="<|&>"  
REM set z="abc"  
echo %z%>temp.txt

echo 1. Ausgabe direkt
echo %z% mit Anfuehrungszeichen
REM echo %z:~1,-1% ohne Anfuehrungszeichen

for /f %%a in ('type temp.txt') do (  
echo 2. Ausgabe in for
echo %%a mit Anfuehrungszeichen
echo %%~a ohne Anfuehrungszeichen
call :ausgabe %%a
)
del temp.txt
goto :eof

:ausgabe
echo 3. Ausgabe per call
echo %1 mit Anfuehrungszeichen
REM echo %~1 ohne Anfuehrungszeichen
goto :eof

...und deren Ausgabe
1. Ausgabe direkt
"<|&>" mit Anfuehrungszeichen
2. Ausgabe in for
"<|&>" mit Anfuehrungszeichen
<|&> ohne Anfuehrungszeichen
3. Ausgabe per call
"<|&>" mit Anfuehrungszeichen


Meine Frage

Wieso brauche ich in der zweiten Batch kein REM für Zeile 14 und erhalte in der Ausgabe
<|&> ohne Anfuehrungszeichen
und nicht den Fehler, den ich durch Zeile 9 und 23 (ohne REM) erhalten würde?

Gruß Frank

Content-ID: 337648

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

Printed on: November 4, 2024 at 07:11 o'clock

rubberman
Solution rubberman May 12, 2017 at 16:06:51 (UTC)
Goto Top
Hallo Frank.

Lang: Das hat etwas damit zu tun wann und wie Variablen zu ihrem Wert erweitert werden.
The Secrets Behind Batch File Interpretation
Am vollständigsten ist die Erklärung im Link von Edit2. Wenn du die Zeit hast, lies dir das mal durch...

Kurz: Normale Umgebungsvariablen werden zum Wert expandiert, bevor die Zeile ausgeführt wird. Somit werden die Sonderzeichen für den Code wirksam. FOR Variablen und Variablen die in Ausrufezeichen gefasst sind (bei eingeschalteter verzögerter Variablenerweiterung) können und werden nicht im Vorfeld expandiert. Somit werden die Sonderzeichen als normale literale Zeichen behandelt.

Grüße
rubberman
Pedant
Pedant May 12, 2017 at 16:59:05 (UTC)
Goto Top
Hallo rubberman,

danke für Deine Erklärung und den Link

Zitat von @rubberman:
Wenn du die Zeit hast, lies dir das mal durch...

Die deutsche Version habe ich mir durchgelesen.
Die engliche bisher nur überpflogen, aber wenn ich wieder auf Merkwürdigkeiten stoße, weiß ich wo nachlesen muss.

An eindringlichsten blieb dieser Satz hängen:
Zitat von @jeb-the-batcher:
Was wiederum bedeutet, dass der Entwickler unter Drogen gestanden hat als er cmd.exe programmierte

Ich habe jetzt eine dritte Variante meiner Batch...
@echo off
setlocal EnableDelayedExpansion
set z="<|&>"  
REM set z="abc"  
echo %z%>temp.txt

echo 1. Ausgabe direkt
echo %z% mit Anfuehrungszeichen
echo !z:~1,-1! ohne Anfuehrungszeichen

for /f %%a in ('type temp.txt') do (  
echo 2. Ausgabe in for
echo %%a mit Anfuehrungszeichen
echo %%~a ohne Anfuehrungszeichen
call :ausgabe %%a
)
del temp.txt
goto :eof

:ausgabe
echo 3. Ausgabe per call
set tempvar=%1
echo !tempvar! mit Anfuehrungszeichen
set tempvar=%~1
echo !tempvar! ohne Anfuehrungszeichen
goto :eof

...und deren Ausgabe
<code type=plain">1. Ausgabe direkt
"<|&>" mit Anfuehrungszeichen
<|&> ohne Anfuehrungszeichen
2. Ausgabe in for
"<|&>" mit Anfuehrungszeichen
<|&> ohne Anfuehrungszeichen
3. Ausgabe per call
"<|&>" mit Anfuehrungszeichen
"|" kann syntaktisch an dieser Stelle nicht verarbeitet werden.


Zitat von @jeb-the-batcher:
Phase6(call): Nur wenn das erste Token "call" ist, bei mehreren "call"'s wird diese Phase mehrfach durchlaufen
- Es wird ein Neustart der Phasen durchgeführt, beginnend bei Phase 0 und endet mit Phase2
- DelayedExpansion scheint generell deaktiviert zu sein, unabhängig von setlocal, cmd /V:on oder Registry Einstellungen

Warum nur das letzte Echo scheitert liegt wohl daran, dass es in einem call-Block steht.

Gruß und Dank
Frank
rubberman
rubberman May 12, 2017 at 17:32:53 (UTC)
Goto Top
Warum nur das letzte Echo scheitert liegt wohl daran, dass es in einem call-Block steht.
Wenn du ECHO ON setzt wirst du sehen, wo genau der Fehler auftritt.
Zeile 24
set "tempvar=%~1"

Grüße
rubberman
Pedant
Pedant May 13, 2017 at 09:00:22 (UTC)
Goto Top
Hallo rubberman,

ja, tatsächlich scheitert es schon in 24 und nicht erst beim echo in 25.
24: set tempvar=%~1 
25: echo !tempvar! ohne Anfuehrungszeichen

Wenn ich 24 weglasse und 25 variiere, gibt es keinen Fehler mehr.
echo !tempvar:~1,-1! ohne Anfuehrungszeichen


Dann gibt es eine vierte Variante meiner Batch...
@echo off
setlocal EnableDelayedExpansion
set z="<|&>"  
REM set z="abc"  
echo %z%>temp.txt

echo 1. Ausgabe direkt
echo %z% mit Anfuehrungszeichen
echo !z:~1,-1! ohne Anfuehrungszeichen

for /f %%a in ('type temp.txt') do (  
echo 2. Ausgabe in for
echo %%a mit Anfuehrungszeichen
echo %%~a ohne Anfuehrungszeichen
call :ausgabe %%a
)
del temp.txt
goto :eof

:ausgabe
echo 3. Ausgabe per call
set tempvar=%1
echo !tempvar! mit Anfuehrungszeichen
echo !tempvar:~1,-1! ohne Anfuehrungszeichen
goto :eof

...und deren Ausgabe
1. Ausgabe direkt
"<|&>" mit Anfuehrungszeichen
<|&> ohne Anfuehrungszeichen
2. Ausgabe in for
"<|&>" mit Anfuehrungszeichen
<|&> ohne Anfuehrungszeichen
3. Ausgabe per call
"<|&>" mit Anfuehrungszeichen
<|&> ohne Anfuehrungszeichen

Aber wehe ich ersetzte in Zeile 3 das | durch ein !...
set z="<!&>"
und maskiere es dann einmal
set z="<^!&>"
und zweimal
set z="<^^!&>"
und dreimal
set z="<^^^!&>"

...dann hab ich wieder was zum Nachdenken.
Wer keine Kreuzworträtsel hat, der nehme cmd.

Gruß Frank
rubberman
Solution rubberman May 13, 2017 at 12:11:02 (UTC)
Goto Top
Hehe. Es ist ratsam ein paar Regeln aufzustellen, wenn man einigermaßen sicher arbeiten will.

Eine meiner eigenen Regeln ist beispielsweise, dass ich immer versuche, den Variablenwert ohne umschließende Anführungszeichen zu speichern. Der Grund dafür ist, dass insbesondere bei der Übergabe von Argumenten an ein Script (oder eine Subroutine) Argumente mal mit und mal ohne Anführungszeichen ankommen. Beispielsweise wenn Dateien per Drag&Drop auf ein Batchscript gezogen werden. Alle Pfade die Leerzeichen enthalten, kommen automatisch mit umschließenden Anführungszeichen, alle anderen nicht. Es ist aber sehr einfach mit Hilfe der Tilde (z.B %~1) den Wert im Parameter %1 immer ohne Anführungszeichen zu bekommen (egal ob der Parameterwert mit oder ohne eingegangen ist). Ebenso einfach ist es, Anführungszeichen im Nachgang wieder um eine Variable zu setzen, wenn man sie benötigen sollte. Somit braucht man später im Script nicht mehr viel nachdenken, welcher Wert nun mit oder ohne Anführungszeichen ist. Bei der Arbeit mit Pfaden setze ich dann bspw. grundsätzlich wieder welche, weil Pfade in Anführungszeichen immer funktionieren, auch wenn sie keine Leerzeichen enthalten face-wink
Mit solchen einfachen Regeln machst du es dir wesentlich einfacher. Mal ein einfaches Beispiel:
@echo off
setlocal DisableDelayedExpansion
set "file=%~1"  
echo "%file%"  
pause
Ziehe im Explorer einfach eine Datei auf die Batchdatei. Egal ob der Pfad Leerzeichen enthält oder nicht, der Wert von %file% ist immer ohne, die Ausgabe immer mit Anführungszeichen.
In der dritten Zeile siehst du bereits die nächste Regel. Wenn ein Wert zugewiesen wird, der ohne Anführungszeichen ist, dann kommt es an dieser Stelle zu Problemen mit Sonderzeichen. Setzt man das Variable=Wert Paar bei der SET Anweisung in Anführungszeichen, gibt es keine Probleme mehr (und man vermeidet, dass sich versehentlich mal ein Leerzeichen hinter dem Wert einmogelt, wenn man beim Schreiben des Codes nicht aufgepasst hat.)

Noch ein Schritt weiter ...
Es gibt eine Daumenregel, die besagt, dass die Zuweisung einer Variable bei ausgeschalteter, das Arbeiten mit dem Wert dann aber bei eingeschalteter verzögerter Variablenerweiterung einigermaßen sicher ist.
Machen wir ein paar Tests

1)
@echo off
setlocal DisableDelayedExpansion
set line=Der Lehrer veraergert: Ruhe! Seid leise & hoert mir zu!


echo %line%

pause>nul
Der Befehl "hoert" ist entweder falsch geschrieben oder
konnte nicht gefunden werden.
Der Lehrer veraergert: Ruhe! Seid leise
Klar, bereits beim SET geht's in die Hose. Also Variable=Wert in Anführungszeichen setzen:
2)
@echo off
setlocal DisableDelayedExpansion
set "line=Der Lehrer veraergert: Ruhe! Seid leise & hoert mir zu!"  


echo %line%

pause>nul
Der Lehrer veraergert: Ruhe! Seid leise
Der Befehl "hoert" ist entweder falsch geschrieben oder
konnte nicht gefunden werden.
Der gleiche Fehler, jetzt aber beim ECHO. Also verzögerte Variablenerweiterung:
3)
@echo off
setlocal EnableDelayedExpansion
set "line=Der Lehrer veraergert: Ruhe! Seid leise & hoert mir zu!"  


echo !line!

pause>nul
Der Lehrer veraergert: Ruhe
Nanu, was ist passiert? Die Zeichenfolge ! Seid leise & hoert mir zu! wird bereits beim SET wie eine Variable behandelt. Da sie undefiniert ist, wird sie durch einen leeren String ersetzt. Also, für die Zuweisung die verzögerte Variablenerweiterung ausschalten, für die ECHO Ausgabe einschalten:
4)
@echo off
setlocal DisableDelayedExpansion
set "line=Der Lehrer veraergert: Ruhe! Seid leise & hoert mir zu!"  

setlocal EnableDelayedExpansion
echo !line!

pause>nul
Der Lehrer veraergert: Ruhe! Seid leise & hoert mir zu!
Na also face-smile

Grüße
rubberman
Pedant
Pedant May 14, 2017 at 11:14:14 (UTC)
Goto Top
Hallo rubberman,

Zitat von @rubberman:
Es gibt eine Daumenregel, die besagt, dass die Zuweisung einer Variable bei ausgeschalteter, das Arbeiten mit dem Wert dann aber bei eingeschalteter verzögerter Variablenerweiterung einigermaßen sicher ist.
Danke für den Tipp und die plakativen Beispiele.

Dann gibt es jetzt die fünfte Variante meiner Batch...
@echo off
setlocal DisableDelayedExpansion
set z="<!|&>"  
REM set z="abc"  

setlocal EnableDelayedExpansion
echo !z!>temp.txt

echo 1. Ausgabe direkt
echo !z! mit Anfuehrungszeichen
echo !z:~1,-1! ohne Anfuehrungszeichen

for /f %%a in ('type temp.txt') do (  
echo 2. Ausgabe in for
setlocal DisableDelayedExpansion
set zeile=%%a
setlocal EnableDelayedExpansion
echo !zeile! mit Anfuehrungszeichen
echo !zeile:~1,-1! ohne Anfuehrungszeichen
call :ausgabe !zeile!
)
del temp.txt
goto :eof

:ausgabe
echo 3. Ausgabe per call
setlocal DisableDelayedExpansion
set tempvar=%1
setlocal EnableDelayedExpansion
echo !tempvar! mit Anfuehrungszeichen
echo !tempvar:~1,-1! ohne Anfuehrungszeichen
goto :eof

...und deren Augabe
1. Ausgabe direkt
"<!|&>" mit Anfuehrungszeichen
<!|&> ohne Anfuehrungszeichen
2. Ausgabe in for
"<!|&>" mit Anfuehrungszeichen
<!|&> ohne Anfuehrungszeichen
3. Ausgabe per call
"<!|&>" mit Anfuehrungszeichen
<!|&> ohne Anfuehrungszeichen

So jetzt höre ich aber auf. Das Fass hat einfach keinen Boden.

setlocal DisableDelayedExpansion
set "gruss=Der Schueler: Danke! & Gruß Frank"
setlocal EnableDelayedExpansion

echo !gruss!
rubberman
rubberman May 14, 2017 updated at 12:00:11 (UTC)
Goto Top
Für Escapesequencen bei Literalen vielleicht noch dieser Link
http://www.robvanderwoude.com/escapechars.php
Aufpassen, was bei den Bemerkungen steht ...

So jetzt höre ich aber auf. Das Fass hat einfach keinen Boden.
So ist es. Batch ist eine der schwersten Sprachen überhaupt. Nicht weil die Syntax so kompliziert ist und auch nicht weil der Sprachumfang so groß ist (im Gegenteil). Aber
  • Es ist fast unmöglich Code zu schreiben, der idiotensicher und ohne Bugs ist.
  • Batch lässt unterschiedliche Datentypen vermissen. Bspw. gibt es als Typ grundsätzlich nur den String. Bestimmte Kommandos konvertieren numerische Strings on the fly zu ganzzahligen Werten, die aber auch immer einen 32 Bit breiten Signed Integer repräsentieren. Fließkommazahlen, Datums-/Zeittypen, Arraytypen, ... alles Fehlanzeige.
  • Wichtige sprachliche Mittel fehlen, bspw. WHILE Schleifen. Dafür gibt's GOTO, das dazu verleitet unlesbaren und unwartbaren Spaghetticode zu schreiben.
  • Batch lebt nur von den Kommandozeilentools. Die Hälfte der sogenannten Befehle sind eigentlich externe Programme (find.exe, fc.exe, more.com, wmic.exe, xcopy.exe, ...). Gibt es für ein bestimmtes Problem kein vorgefertigtes Tool, ist Ebbe. Du stößt also bei jeder Gelegenheit an die Grenzen von Batch. Das Resultat ist, das versucht wird jeden Bug, jedes undefinierte Verhalten und Sideeffects der cmd.exe und der anderen Tools zu missbrauchen und zu nutzen. In einem Code bei dem es auf Zuverlässigkeit ankommt, möchte ich so etwas nicht stehen haben ... Und - das ständige wiederholte Aufrufen von externen Programmen macht Batch sooo laaangsaaam ...

Ich gebe also jedem uneingeschränkt Recht, der sagt dass Batch nicht mehr zeitgemäß ist und für die PowerShell plädiert.
Für mich persönlich macht das leider keinen Sinn. Ich schreibe Scripts für Automationen, die mir meinen Job erleichtern. Solange PowerShell auf unseren Firmenrechnern per GPO ausgehebelt ist, ist sie für mich nutzlos. Natürlich habe ich auch an PowerShell Kritiken. Wenn ich mir die Scripts hier im Forum ansehe, dann scheint es ein Volkssport zu sein möglichst alles in eine Zeile zu packen. Die Möglichkeit de facto alles per Pipe weiterzureichen, verleitet auch dazu. Lesbar ist das dann allerdings nicht mehr...

Grüße
rubberman
Pedant
Pedant May 14, 2017 at 14:01:27 (UTC)
Goto Top
Hallo rubberman,

Zitat von @rubberman:
Für Escapesequencen bei Literalen vielleicht noch dieser Link
http://www.robvanderwoude.com/escapechars.php
Über den bin ich schonmal gestolpert.

Zitat von @rubberman:
...zu ganzzahligen Werten, die aber auch immer einen 32 Bit breiten Signed Integer repräsentieren
...aber nicht immer in dem Zahlensystem, das man vielleicht erwartet.
set /a acht=010set /a neun=%acht% + 1echo %acht% + 1 = %neun%Ausgabe: 8 + 1 = 9

Gruß Frank
rubberman
rubberman May 14, 2017 at 14:11:39 (UTC)
Goto Top
Gut, das ist aber hinreichend dokumentiert face-wink
set /?
... wobei ich die letzten 20 Jahre noch nicht erlebt habe, dass die dort beschriebene 0b Syntax für Binärzahlen jemals funktioniert hätte.
Jaja, der Drogeneinfluss ... face-smile

Grüße
rubberman