The following illustrates a problem I found using for /f %l in ('<command>') do @(echo %l)
. (/f
is the for
command's parameter for "iterating and file parsing.") This works as expected when <command>
is cd
or chdir
, but not when <command>
is pushd
:
C:\>cd
C:\
C:\>for /f %l in ('cd') do @(echo %l)
C:\
C:\>chdir
C:\
C:\>for /f %l in ('chdir') do @(echo %l)
C:\
C:\>pushd Windows
C:\Windows>pushd
C:\
C:\Windows>for /f %l in ('pushd') do @(echo %l)
C:\Windows>pushd > nul
C:\Windows>
I would expect the last command to print C:\
but it does not execute the do
block at all. I added pushd > nul
to check that pushd
prints to stdout
.
How can I process the output of pushd
in a for /f
loop?
CodePudding user response:
Information about command processing by FOR /F
The usage of a for /F
loop with a command enclosed in '
or in `
on using usebackq
results in starting in background one more command process using %ComSpec% /c
and the command appended as additional argument(s).
The usage of for /f %l in ('pushd') do @(echo %l)
results in background execution of:
C:\Windows\System32\cmd.exe /c pushd
That can be seen by downloading, extracting and running the free Windows Sysinternals tool Process Monitor as administrator which logs the execution of two cmd.exe
processes with different process identifiers on running the commands as posted in the question. There must be double clicked on any line in log of Process Monitor of second cmd.exe
in the middle of the log to open the Event Properties window and selected the second tab Process to see the Command Line which was used by first cmd.exe
to start the second cmd.exe
for the execution of the command pushd
.
There cannot be executed just a single command with a for /F
loop. There can be executed an entire command line which can even have multiple commands. But it is necessary to take into account all the information given by the usage help of cmd
output on running cmd /?
in a command prompt window on using a complex command line with for /F
and processing its output as written to handle STDOUT of in background started cmd.exe
.
Command operators like &
, &&
and ||
as well as redirection operators like |
, 2>
and 2>&1
in the command line to execute by for /F
are processed by two cmd.exe
, first the one executing the entire for /F
loop and another one started in background with the command line of which output is of interest. That is the reason why many for /F
loops with a complex command line are with the escape character ^
left to each &
and |
and >
in the command line to get these characters interpreted literally by cmd.exe
parsing and executing the entire for /F
loop while being interpreted as command/redirection operators by the second cmd.exe
started in background which is really executing the command line.
Information about output of PUSHD
The Windows command PUSHD outputs on execution without any directory path the list of directory paths pushed on stack of current command process.
There can be executed in a command prompt window following commands:
cd /D %SystemDrive%\
pushd %SystemRoot%
pushd inf
pushd
popd
popd
The fifth command pushd
results usually for typical Windows installations in the output:
C:\Windows
C:\
But there is nothing output by using as fifth command instead of pushd
the command line:
for /F "delims=" %I in ('pushd') do @echo %I
The reason is the execution of pushd
by one more cmd.exe
started in background which has no directory paths pushed on its stack. The internal command PUSHD does not output anything at all for that reason to handle STDOUT of in background started cmd.exe
.
The command process running for /F
cannot capture any output text. The for /F
loop cannot process therefore any line after cmd.exe
started in background finished the execution of pushd
and closed itself.
How to process the directory paths pushed on stack?
It would be necessary to use the following command lines in the command prompt window to process the directory paths pushed on stack of the current command process:
pushd >"%TEMP%\DirectoryList.tmp"
if exist "%TEMP%\DirectoryList.tmp" for /F "usebackq delims=" %I in ("%TEMP%\DirectoryList.txt") do @echo %I
del "%TEMP%\DirectoryList.tmp" 2>nul
The redirection of the output of PUSHD executed by the command process which executed also the two pushd
command lines before into a temporary file makes it possible to process the directory paths pushed on stack of current command process.
The for /F
option usebackq
is necessary to get the string inside "
interpreted as file name of which lines to process by the FOR loop and not as string to process.
The for /F
option delims=
is necessary to define an empty list of string delimiters as a directory path can contain one or more spaces. There is by default split up a line into substrings using normal space and horizontal tab as string delimiters and assigned to the loop variable is just the first space/tab delimited string instead of the entire directory path. The line splitting is turned off by the definition of an empty list of delimiters. The entire directory path is assigned therefore always to the loop variable in this case with processing always full directory paths never starting with the default end of line character ;
as the first character is always either a drive letter or a backslash in case of a UNC directory path.
Why does the command CD work with a FOR /F loop?
The command CD works also with for /F "delims=" %I in ('cd') do @echo %I
because of cmd.exe
calls the Windows kernel library function CreateProcess on starting the additional command process for execution of a command specified as set of a for /F
loop with value NULL for the function parameter lpCurrentDirectory
. The current directory of in background started cmd.exe
is set by CreateProcess
for that reason with the current directory of the command process executing the for /F
command line. Both running command processes have the same current directory during the execution of the command of a for /F
loop.
Information about environment variable ComSpec
ComSpec
is an environment variable defined by default with %SystemRoot%\system32\cmd.exe
(with s
at beginning of System32
instead of S
as the folder name is in real by default) as system environment variable stored in Windows registry under the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
.
It is really not advisable to ever modify or even delete the environment variable ComSpec
neither in local environment of a running process nor in the Windows registry. That would cause a lot of programs to stop working normally as lots of executables depend internally on the correct definition of this environment variable including cmd.exe
itself.
The reason is that many applications and scripts use internally the function system which uses the environment variable ComSpec
to start on Windows cmd.exe
for execution of a command line.
There are many batch files running internal command ver
of cmd.exe
to get the Windows version like:
for /F "tokens=2 delims=[]" %%G in ('ver') do for /F "tokens=2" %%H in ("%%G") do echo %%H
That works only for Windows NT based Windows versions using cmd.exe
as command processor and not for older Windows versions using COMMAND.COM
like Windows 95/98/ME for processing a batch file. It works only with enabled command extensions which are enabled by Windows default, but can be disabled by command setlocal DisableExtension
in a batch file, on starting cmd.exe
with /E:OFF
or by a registry value which should be really never used and therefore not written here. The command ver
must be really executed by %SystemRoot%\System32\cmd.exe
because of in real is output the version of cmd.exe
and not the version of Windows.
However, that command line is very good to demonstrate what happens on usage of for /F
if the environment variable ComSpec
is not defined in environment of cmd.exe
on running the batch file with the two for /F
loops.
ComSpec usage by cmd.exe on Windows XP
On Windows XP is always called C:\WINDOWS\system32\cmd.exe /c ver
even on doing following:
- Copying
cmd.exe
to the directoryF:\Temp\system32
. - Starting
F:\Temp\system32\cmd.exe
with a double click in Windows Explorer. - Running
set ComSpec=F:\Temp\system32\cmd.exe
andset SystemRoot=F:\Temp
andset SystemDrive=F:
andset windir=F:\Temp
. - Verifying with
echo %__APPDIR__%
thatF:\Temp\system32\
is output. - Running the batch file with the command line as posted above.
That can be seen with Process Monitor v3.61 on Windows XP x86.
There can be even modified the local environment variable PATH
to begin with F:\Temp\system32
instead of C:\WINDOWS\system32
or the local environment variable ComSpec
is deleted with set ComSpec=
. The Windows Command Processor of Windows XP in directory F:\Temp\system32
calls nevertheless always C:\WINDOWS\system32\cmd.exe
with the option /c
and the command ver
on running the batch file immediately after closing the batch file containing just the single command line in the text editor.
There cannot be seen in Process Monitor even a Windows registry access by cmd.exe
of Windows XP to get any directory path.
Please read further why cmd.exe
of Windows XP in a different directory than %SystemRoot%\System32\cmd.exe
calls nevertheless the command processor executable in Windows system directory even after modification of local environment variable ComSpec
or its deletion.
ComSpec usage of cmd.exe on Windows Vista/7/8/8.1/10/11
The same procedure as described above for Windows XP can be executed also on Windows 7 x64 and newer 64-bit Windows versions.
The double clicked 64-bit F:\Temp\system32\cmd.exe
copied from C:\Windows\System32
outputs on Windows 7 and currently latest Windows 11 22H2 first:
The system cannot find message text for message number 0x2350 in the message file for Application.
(c) Microsoft Corporation. All rights reserved.
Note: The copyright message line depends on version of cmd.exe
. The output above is from cmd.exe
of version 10.0.22621.963 (Windows 11 22H2).
The Windows Command Processor cmd.exe
of version 6.1.7601 of Windows 7 outputs additionally the line:
Not enough storage is available to process this command.
The execution of the internal command ver
executed in the command prompt window opened with a double click on F:\Temp\System32\cmd.exe
fails on Windows 7 and all later Windows versions up to currently latest Windows 11 22H2 which is the reason for the strange output on starting F:\Temp\System32\cmd.exe
with a double click.
There can be redefined also the environment variables as described above and done on German Windows XP by me on my tests. A verification of the output of echo %__APPDIR__%
works and shows F:\Temp\system32\
as expected. So, it works to access the string value of the internal dynamic variable of cmd.exe
even on executed cmd.exe
is not in the directory %SystemRoot%\System32
.
But the batch file execution from within the command prompt of F:\Temp\system32\cmd.exe
as on Windows XP results in no output at all.
In log of Process Monitor v3.70 or a newer Process Monitor version can be seen that on Windows 7 and Windows 11 22H2 is executed F:\Temp\System32\cmd.exe /c ver
. That means it is indeed possible that another cmd.exe
is executed instead of C:\Windows\System32\cmd.exe
on Windows 7 and newer Windows versions.
But why is no version output?
Well, the execution of ver
results in the output of the error message:
The system cannot find message text for message number 0x2350 in the message file for Application.
64-bit cmd.exe
is not working in F:\Temp\system32
at all.
The usage of 32-bit cmd.exe
copied from C:\Windows\SysWOW64
to F:\Temp\system32
makes no difference. The same error messages are output already on starting F:\Temp\system32\cmd.exe
and on running next ver
or the batch file after modification of local environment variable ComSpec
.
Conclusion: cmd.exe
of Windows Vista and newer versions are not fully working on being stored outside the appropriate system directory %SystemRoot%\System32
or %SystemRoot%\SysWOW64
while cmd.exe
of Windows XP works fine in any directory.
Caching of ComSpec value
I found out with lots of further tests that the string value of the environment variable ComSpec
is indeed used to find cmd.exe
to run command ver
on execution of the batch file with the for /F
loop. But there is a caching mechanism on Windows Vista and newer Windows versions.
There must be run immediately set ComSpec=F:\Temp\system32\cmd.exe
after starting F:\Temp\system32\cmd.exe
before running the batch file. This results in calling F:\Temp\system32\cmd.exe /c ver
. If there is next executed set ComSpec=C:\Windows\System32\cmd.exe
to redefine the variable with correct value and run the batch file once again, there is nevertheless run now not working F:\Temp\system32\cmd.exe /c ver
.
The same caching mechanism can be seen on starting F:\Temp\system32\cmd.exe
, then running the batch file resulting in calling in background C:\Windows\System32\cmd.exe /c ver
, next running set ComSpec=F:\Temp\system32\cmd.exe
and running now the batch file again. There is executed once again C:\Windows\System32\cmd.exe /c ver
although the value of the environment variable ComSpec
is now F:\Temp\system32\cmd.exe
.
It looks like cmd.exe
of Windows Vista/7/8/8.1/10/11 reads the value of ComSpec
only once and keeps its value in memory for further usage without reading the environment variable a second time on string value for the default command interpreter being already in its internal memory.
That is quite clever in my opinion. The string value of ComSpec
is read only once from the environment variable on first usage and then is used that string internally on further usage because of the value of the environment variable ComSpec
changes usually never as long as cmd.exe
is running.
It looks like cmd.exe
of Windows XP has nearly the same caching mechanism for the string value of the environment variable ComSpec
. The difference is that cmd.exe
of Windows XP
reads the value of ComSpec
already on starting it and not on first usage and so all changes done on local environment variable ComSpec
of an already running cmd.exe
have no effect on the execution of one more cmd.exe
on processing a for /F
loop with a command line to execute, capturing the output and processing it as I could find out with further tests on German Windows XP.