I had already updated jeb's cmd parsing rules on StackOverflow to show that a command token that begins with : is detected as a label in phase 2, and the command is aborted - the later phases are not executed.
But my recent experiments show that some labels with preceding redirection actually execute the redirection in phase 5.5, yet the label still is not executed (no error message). This implies that command execution in phase 7 must be able to recognize that commands that begin with : are labels, and should not be executed.
I did a bunch of additional tests, and came up with the following rules:
There are two major types of labels:
- Unexecuted Label - A label that is fully detected in phase 2
- The command is aborted, the remainder of the line is not parsed, and the subsequent phases do not take place for the label. Edit - Except ^ line continuation still occurs
- Generally, if a command token discovered in phase 2 begins with a colon, then it is an Unexecuted Label.
But there are exceptions listed under the Executed Label section - An Unexecuted Label within a parenthesized block will result in a fatal syntax error in phase 2 unless it is immediately followed by a command or executed label on the very next line
- Executed Label - A label that is not fully detected until phase 7
All prior phases are applied to the label, but then during execution, the label is ignored (not really executed), or else results in an error, or in rare cases, changes the current volume.
There are two basic classes of executed labels- Delayed label - The command token did not begin with a colon in phase 2. The leading colon only appears after FOR expansion in phase 4 or delayed expansion in phase 5
. - Phase 2 Executed Label exceptions - A command token that begins with : in phase 2 will be executed under any of the following circumstances:
- The command token begins with : and there is redirection that precedes the command token, and there is &, &&, or || command concatenation or a | pipe on the line
- The command token begins with : and there is redirection that precedes the command token, and the command is within a parenthesized block
- The command token begins with : and it is the very first command on a line within a parenthesized block, and the line immediately above ended with an unexecuted label
- The label, its arguments, and its redirection are all excluded from any echo output in phase 3
- Any subsequent concatenated commands on the line are fully parsed and executed
- Delayed label - The command token did not begin with a colon in phase 2. The leading colon only appears after FOR expansion in phase 4 or delayed expansion in phase 5
Steps that are important to labels are in bold italics.
Note that there are slight differences between batch mode and command line mode that are important for executed labels.
Phase 7) Execute the command
- 7.1 - Execute internal command - If the command token is not quoted and it is an internal command, then execute the internal command
. - 7.2 - Execute volume change - Else if the command token does not begin with a quote, is exactly two characters long, and the 2nd character is a colon, then change the volume
- All argument tokens are ignored
- If the volume specified by the first character cannot be found, then abort with an error.
- A command token of :: will always result in an error unless SUBST is used to define a volume for ::
If SUBST is used to define a volume for :: then the volume will be changed, it will not be treated as a label
- 7.3 - Execute external command - Else try to treat the command as an external command
- If the 2nd character is a colon, then verify the volume specified by the 1st character can be found.
If the volume cannot be found, then abort with an error - If in batch mode and the command token is a label that begins with : then goto 7.4
Note that if the label begins with :: then this will not be reached because the preceding step will have issued an error unless SUBST is used to define a volume for :: - Identify the external command to execute
This is a complex process that can involve the current volume, current directory, PATH variable, PATHEXT variable, and file associations
If a valid external command cannot be identified, then abort with an error - If in command line mode and the command token is a label that begins with : then goto 7.4
Note that this is rarely reached because the preceding step will have identified the label as an error unless the command token begins with :: and SUBST is used to define a volume for :: and the entire command token is a valid path to an external command - Execute the external command
- If the 2nd character is a colon, then verify the volume specified by the 1st character can be found.
- 7.4 - Ignore a label - Ignore the command and all its arguments if the command token begins with :
Rules in 7.2 and 7.3 may prevent a label from reaching this point
The remainder of this post is evidence for and/or examples of the rules I outlined above for the batch context only.
In the future I may do a similar set of tests for command line mode.
I have two methods to probe whether a label is parsed beyond phase 2 (unexecuted label), or continues on to phase 7 (executed label)
- Test whether delayed expansion phase 5 is performed
At first I didn't think I could probe this because delayed expansion never results in a fatal syntax error, and labels never have any output.
But then I remembered that find/replace and substring operations on the pseudo CMDCMDLINE variable actually modifies the underlying value.
So I simply add an argument after the label that uses delayed expansion to modify CMDCMDLINE.
If the value changes, then phase 5 must have been performed. If not, then phase 5 must not have been performed. - Test whether redirection phase 5.5 is performed
I create a text file with length greater than 0 prior to executing the label command.
Then for the label "command" I redirect stdout to the file.
A label "command" never has any output, so if the file size goes to 0, then redirection must have occurred. If not, then redirection did not occur.
If both phase 5 and 5.5 are executed, then the presumption is that the label is processed all the way through phase 7.
I don't want to permanently mess with the CMDCMDLINE value for my current cmd session, so I execute each test in a new cmd.exe process.
I built a test harness that allows me to perform multiple tests within a single script, each test within its own cmd process.
If there is a fatal syntax error, then I cannot probe the delayed expansion because the test cmd.exe process will terminate and I will lose my CMDCMDLINE value.
But the file still exists after a fatal syntax error, so I check the file size after the test cmd.exe process closes.
If no arguments are given, then all 26 tests are run.
If a single START integer argument is given, then the test starts with the START test, and continues to the end.
If both START and STOP are given, then the test starts with the START test, and continues through the STOP test.
Here is my set of batch mode tests:
Code: Select all
@echo off
setlocal
set "beg=%~1"
set "end=%~2"
if not defined beg set "beg=1"
if not defined end set "end=26"
for /l %%N in (%beg% 1 %end%) do if %0=="%~dp0Test%%N\..\%~nx0" (
echo(
set "print="
for /f "usebackq delims=" %%A in ("%~f0") do (
for /f %%B in ("%%A") do if "%%B"=="@exit" (set print=) else if "%%B"==":test%%N" set print=1
if defined print for /f "eol=@ delims=" %%B in ("%%A") do echo(%%B
)
<nul set /p "=------------------------------------------"
echo content >test1.txt
echo content >test2.txt
setlocal enableDelayedExpansion
break "!cmdcmdline:*.bat""= 1 2!"
echo on
call :test%%N
@echo off
echo cmdCmdLine=!cmdcmdline!
exit /b
)
cls
prompt echo$g
for /l %%N in (%beg% 1 %end%) do (
cmd /c ^""%~dp0Test%%N\..\%~nx0""
for %%F in (test1.txt test2.txt) do <nul set /p "=%%F size=%%~zF "
echo(
)
prompt
exit /b
:test1 - A normal label is not executed
@echo(
:label !cmdcmdline:1=Mod1! >test1.txt & echo ignored
@exit /b
:test2 - A normal label is still not executed if concatenated to a command
echo exec & :label !cmdcmdline:1=Mod1! >test1.txt & echo ignored
@exit /b
:test3 - This delayed label is executed because it doesn't exist until phase 4
for %%A in (:label) do %%A !cmdcmdline:1=Mod1! >test1.txt & echo executed
@exit /b
:test4 - This delayed label is executed because it doesn't exist until phase 5
set "var=:label" & !var! !cmdcmdline:1=Mod1! >test1.txt & echo executed
@exit /b
:test5 - A label with preceding redirection is not executed if no command concatenation
@echo(
>test1.txt :label !cmdcmdline:1=Mod1!
@exit /b
:test6 - A label with preceding redirection is executed if it is concatenated to a command
echo executed & >test1.txt :label !cmdcmdline:1=Mod1!
@exit /b
:test7 - A label with preceding redirection is also executed if a concatenated command follows
>test1.txt :label !cmdcmdline:1=Mod1! & echo exec
@exit /b
:test8 - A label with preceding redirection is executed even if the concatenated command is an unexecuted label
>test1.txt :label !cmdcmdline:1=Mod1! & :label !cmdcmdline:2=Mod2! >test2.txt
@exit /b
:test9 - Of course the label with preceding redirection is executed if sandwiched between executing commands
echo executed & >test1.txt :label !cmdcmdline:1=Mod1! & echo executed
@exit /b
:test10 - Both concatenated labels with preceding redirection are executed
>test1.txt :label !cmdcmdline:1=Mod1! & >test2.txt :label !cmdcmdline:2=Mod2!
@exit /b
:test11 - Executed label beginning with :: normally gives an error
>test1.txt ::label !cmdcmdline:1=Mod1! & echo executed
@exit /b
:test12 - Executed label beginning with :: is OK if volume :: is defined via SUBST
subst :: . & >test1.txt ::label !cmdcmdline:1=Mod1! & subst :: /d
@exit /b
:test13 - Executed :: "label" with volume :: defined is actually an executed change volume command
subst :: . & >test1.txt :: !cmdcmdline:1=Mod1!
cd & subst :: /d
@exit /b
:test14 - label with preceding redirection and conditional && concatenation also forces label execution
>test1.txt :label !cmdcmdline:1=Mod1! && echo executed label SUCCESS
@exit /b
:test15 - label with preceding redirection and conditional || concatenation also forces label execution
>test1.txt ::label !cmdcmdline:1=Mod1! || echo executed label beginning with :: FAILED
@exit /b
:test16 - Within parens: unexecuted label without immediately following command gives fatal syntax error
@echo(
(
:label !cmdcmdline:1=Mod1! >test1.txt Syntax Error
)
@exit /b
:test17 - Within parens: unexecuted label without immediately following command gives fatal syntax error
@echo(
(
:label !cmdcmdline:1=Mod1! >test1.txt
echo Syntax Error
)
@exit /b
:test18 - Within parens: uexecuted label immediately followed by command is valid syntax
(
:validLabel !cmdcmdline:1=Mod1! >test1.txt
echo exec
)
@exit /b
:test19 - Within parens: 1st label not executed, 2nd label is executed, yielding valid syntax
(
:label 1 !cmdcmdline:1=Mod1! >test1.txt
:label 2 !cmdcmdline:2=Mod2! >test2.txt
)
@exit /b
:test20 - As expected, ::label2 is executed, but normally gives an error
(
::label1 !cmdcmdline:1=Mod1! >test1.txt
::label2 !cmdcmdline:2=Mod2! >test2.txt
)
@exit /b
:test21 - As expected, defining volume :: allows ::label2 to execute without error
(
subst :: .
::label1 !cmdcmdline:1=Mod1! >test1.txt
::label2 !cmdcmdline:2=Mod2! >test2.txt
subst :: /d
)
@exit /b
:test22 - Within parens: delayed label is executed, so it is valid syntax even though no following command
(
set "var=:label"
!var! !cmdcmdline:1=Mod1! >test1.txt
)
@exit /b
:test23 - Within parens: lone label with preceding redirection is executed without fatal syntax error, even without concatenated command
(
>test1.txt :label !cmdcmdline:1=Mod1!
)
@exit /b
:test24 - Within parens: lone label without preceding redirection is not executed, no following command gives fatal syntax error
@echo(
(
:label !cmdcmdline:1=Mod1! >test1.txt
)
@exit /b
:test25 - Within parens: unexecuted label2 is not followed by executed command, so fatal syntax error
@echo(
(
>test1.txt :label1 !cmdcmdline:1=Mod1! & :label2 !cmdcmdline:2=Mod2! >test2.txt
)
@exit /b
:test26 - Within parens: unexecuted label2 is followed by a command, so valid syntax
(
>test1.txt :label1 !cmdcmdline:1=Mod1! & :label2 !cmdcmdline:2=Mod2! >test2.txt
echo executed
)
@exit /b
I run each test with ECHO ON so you can see how labels affect phase 3 output.
Each test output contains the following components:
- The test number and lesson learned
- The actual test code
- A ------------------------- separator
- The test results
- Phase 3 output (ECHO ON output) follows the echo> prompt
. - The CMDCMDLINE probe can have four results. Only the first two are valid if there is only one label:
- cmdCmdLine= 1 2 - no label was executed
- cmdCmdLine= Mod1 2 - label 1 was executed
- cmdCmdLine= 1 Mod2 - label 2 was executed
- cmdCmdLine= Mod1 Mod2 - both label 1 and label 2 were executed
. - The redirection probe can have four results. Only the first two are valid if there is only one label:
- test1.txt size=10 test2.txt size=10 - no label was executed
- test1.txt size=0 test2.txt size=10 - label 1 was executed
- test1.txt size=10 test2.txt size=0 - label 2 was executed
- test1.txt size=0 test2.txt size=0 - both label 1 and label 2 were executed
- Phase 3 output (ECHO ON output) follows the echo> prompt
Code: Select all
:test1 - A normal label is not executed
:label !cmdcmdline:1=Mod1! >test1.txt & echo ignored
------------------------------------------
cmdCmdLine= 1 2
test1.txt size=10 test2.txt size=10
Code: Select all
:test2 - A normal label is still not executed if concatenated to a command
echo exec & :label !cmdcmdline:1=Mod1! >test1.txt & echo ignored
------------------------------------------
echo> echo exec
exec
cmdCmdLine= 1 2
test1.txt size=10 test2.txt size=10
Code: Select all
:test3 - This delayed label is executed because it doesn't exist until phase 4
for %%A in (:label) do %%A !cmdcmdline:1=Mod1! >test1.txt & echo executed
------------------------------------------
echo> for %A in (:label) do %A !cmdcmdline:1=Mod1! 1>test1.txt & echo executed
echo> & echo executed
executed
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test4 - This delayed label is executed because it doesn't exist until phase 5
set "var=:label" & !var! !cmdcmdline:1=Mod1! >test1.txt & echo executed
------------------------------------------
echo> set "var=:label" & !var! !cmdcmdline:1=Mod1! 1>test1.txt & echo executed
executed
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test5 - A label with preceding redirection is not executed if no command concatenation
>test1.txt :label !cmdcmdline:1=Mod1!
------------------------------------------
cmdCmdLine= 1 2
test1.txt size=10 test2.txt size=10
Code: Select all
:test6 - A label with preceding redirection is executed if it is concatenated to a command
echo executed & >test1.txt :label !cmdcmdline:1=Mod1!
------------------------------------------
echo> echo executed &
executed
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test7 - A label with preceding redirection is also executed if a concatenated command follows
>test1.txt :label !cmdcmdline:1=Mod1! & echo exec
------------------------------------------
echo> & echo exec
exec
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test8 - A label with preceding redirection is executed even if the concatenated command is an unexecuted label
>test1.txt :label !cmdcmdline:1=Mod1! & :label !cmdcmdline:2=Mod2! >test2.txt
------------------------------------------
echo>
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test9 - Of course the label with preceding redirection is executed if sandwiched between executing commands
echo executed & >test1.txt :label !cmdcmdline:1=Mod1! & echo executed
------------------------------------------
echo> echo executed & & echo executed
executed
executed
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test10 - Both concatenated labels with preceding redirection are executed
>test1.txt :label !cmdcmdline:1=Mod1! & >test2.txt :label !cmdcmdline:2=Mod2!
------------------------------------------
echo> &
cmdCmdLine= Mod1 Mod2
test1.txt size=0 test2.txt size=0
Code: Select all
:test11 - Executed label beginning with :: normally gives an error
>test1.txt ::label !cmdcmdline:1=Mod1! & echo executed
------------------------------------------
echo> & echo executed
The system cannot find the drive specified.
executed
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test12 - Executed label beginning with :: is OK if volume :: is defined via SUBST
subst :: . & >test1.txt ::label !cmdcmdline:1=Mod1! & subst :: /d
------------------------------------------
echo> subst :: . & & subst :: /d
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test13 - Executed :: "label" with volume :: defined is actually an executed change volume command
subst :: . & >test1.txt :: !cmdcmdline:1=Mod1!
cd & subst :: /d
------------------------------------------
echo> subst :: . &
echo> cd & subst :: /d
::\
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test14 - label with preceding redirection and conditional && concatenation also forces label execution
>test1.txt :label !cmdcmdline:1=Mod1! && echo executed label SUCCESS
------------------------------------------
echo> && echo executed label SUCCESS
executed label SUCCESS
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test15 - label with preceding redirection and conditional || concatenation also forces label execution
>test1.txt ::label !cmdcmdline:1=Mod1! || echo executed label beginning with :: FAILED
------------------------------------------
echo> || echo executed label beginning with :: FAILED
The system cannot find the drive specified.
executed label beginning with :: FAILED
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test16 - Within parens: unexecuted label without immediately following command gives fatal syntax error
(
:label !cmdcmdline:1=Mod1! >test1.txt Syntax Error
)
------------------------------------------
) was unexpected at this time.
echo> )
test1.txt size=10 test2.txt size=10
Code: Select all
:test17 - Within parens: unexecuted label without immediately following command gives fatal syntax error
(
:label !cmdcmdline:1=Mod1! >test1.txt
echo Syntax Error
)
------------------------------------------
The syntax of the command is incorrect.
echo>
test1.txt size=10 test2.txt size=10
Code: Select all
:test18 - Within parens: uexecuted label immediately followed by command is valid syntax
(
:validLabel !cmdcmdline:1=Mod1! >test1.txt
echo exec
)
------------------------------------------
echo> (echo exec )
exec
cmdCmdLine= 1 2
test1.txt size=10 test2.txt size=10
Code: Select all
:test19 - Within parens: 1st label not executed, 2nd label is executed, yielding valid syntax
(
:label 1 !cmdcmdline:1=Mod1! >test1.txt
:label 2 !cmdcmdline:2=Mod2! >test2.txt
)
------------------------------------------
echo> ()
cmdCmdLine= 1 Mod2
test1.txt size=10 test2.txt size=0
Code: Select all
:test20 - As expected, ::label2 is executed, but normally gives an error
(
::label1 !cmdcmdline:1=Mod1! >test1.txt
::label2 !cmdcmdline:2=Mod2! >test2.txt
)
------------------------------------------
echo> ()
The system cannot find the drive specified.
cmdCmdLine= 1 Mod2
test1.txt size=10 test2.txt size=0
Code: Select all
:test21 - As expected, defining volume :: allows ::label2 to execute without error
(
subst :: .
::label1 !cmdcmdline:1=Mod1! >test1.txt
::label2 !cmdcmdline:2=Mod2! >test2.txt
subst :: /d
)
------------------------------------------
echo> (
subst :: .
subst :: /d
)
cmdCmdLine= 1 Mod2
test1.txt size=10 test2.txt size=0
Code: Select all
:test22 - Within parens: delayed label is executed, so it is valid syntax even though no following command
(
set "var=:label"
!var! !cmdcmdline:1=Mod1! >test1.txt
)
------------------------------------------
echo> (
set "var=:label"
!var! !cmdcmdline:1=Mod1! 1>test1.txt
)
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test23 - Within parens: lone label with preceding redirection is executed without fatal syntax error, even without concatenated command
(
>test1.txt :label !cmdcmdline:1=Mod1!
)
------------------------------------------
echo> ()
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Code: Select all
:test24 - Within parens: lone label without preceding redirection is not executed, no following command gives fatal syntax error
(
:label !cmdcmdline:1=Mod1! >test1.txt
)
------------------------------------------
) was unexpected at this time.
echo> )
test1.txt size=10 test2.txt size=10
Code: Select all
:test25 - Within parens: unexecuted label2 is not followed by executed command, so fatal syntax error
(
>test1.txt :label1 !cmdcmdline:1=Mod1! & :label2 !cmdcmdline:2=Mod2! >test2.txt
)
------------------------------------------
) was unexpected at this time.
echo> )
test1.txt size=10 test2.txt size=10
Code: Select all
:test26 - Within parens: unexecuted label2 is followed by a command, so valid syntax
(
>test1.txt :label1 !cmdcmdline:1=Mod1! & :label2 !cmdcmdline:2=Mod2! >test2.txt
echo executed
)
------------------------------------------
echo> ( & echo executed )
executed
cmdCmdLine= Mod1 2
test1.txt size=0 test2.txt size=10
Dave Benham