I'm using the following commands:
C:\>for %I in (a: b: c: ">:" "&:") do @rem %~fI
C:\>pushd c:
C:\>set "
and the output:
=&:=&:\
=>:=>:\
=A:=A:\
=B:=B:\
=C:=C:\
....
As the =Drive:
variables are storing the last accessed path the corresponding drive , it looks like the %~fI
expansion somehow accessed not existing drive (which is not possible) . (all parameter expansions create such variables)
When a modifier is used in the for
replaceable parameter to request a path element, the for
command (well, a function that retrieves the contents of the variables being read) uses the GetFullPathName
function to adapt the input string to something that could be handled. This API function (well, some of the OS base functions called by this API) generates the indicated behaviour when a relative path is requested. You can test this c code (sorry, just a quick code test), calling the executable with the ex. ;:
as the first argument.
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 4096
int main(int argc, char **argv){
char buffer[BUFFER_SIZE];
DWORD ret;
LPTSTR lpszVariable;
LPTCH lpvEnv;
if (argc != 2) return 1;
if (0 == GetFullPathName( argv[1], BUFFER_SIZE, buffer, NULL )){
printf ("GetFullPathName failed (%d)\n", GetLastError());
return 2;
}
printf("current active directory: %s\r\n", buffer );
if (NULL == (lpvEnv = GetEnvironmentStrings())) {
printf("GetEnvironmentStrings failed (%d)\n", GetLastError());
return 3;
}
lpszVariable = (LPTSTR) lpvEnv;
while (*lpszVariable) {
if (lpszVariable[0]== '=') printf("%s\n", lpszVariable);
lpszVariable += lstrlen(lpszVariable) + 1;
}
FreeEnvironmentStrings(lpvEnv);
return 0;
}
to get something like
D:\>test ;:
current active directory: ;:\
=;:=;:\
=C:=C:\Windows\System32
=D:=D:\
=ExitCode=00000000
EDITED 2016/12/23
This is for windows 10, but as windows 7 behaves the same it should share the same or similar code.
The output of environment strings to console is handled by DisplayEnvVariable
function. In older windows versions (checked and XP did it this way) this function calls GetEnvironmentStrings
to retrive the values, but now (checked and in Vista it was changed) a pointer to a memory area is used. Somehow (sorry, at this moment I can not give this problem more time), it points to a non updated copy of the environment (in this case the updated was not generated by cmd
command, but from a base Rtl
function called when resolving the current drive path), generating the observed behaviour.
It is not necessary to execute a pushd
or cd
command, any change to the environment or any process creation will result in an update of the pointer.
@echo off
setlocal enableextensions disabledelayedexpansion
echo = before ------------------------------
set "
for %%a in ( ";:" ) do rem %%~fa
echo = after -------------------------------
set "
<nul >nul more
echo = after more --------------------------
set "
You can replace the more
line with a simple set "thisIsNotSet="
to get the same result
I think there are two things contributing:
A drive letter can actually be every character other than white-spaces, /
and \
. Check out the subst
command, which accepts also an &
, for example (although it is not listed by subst
):
C:\>subst X: C:\
C:\>subst ^&: C:\
C:\>subst
X:\: => C:\
C:\>X:
X:\>^&:
&:\>
for
does not access the file system unless it really needs to, which is the case when:
?
, *
) are used in the set (where the file system needs to be accessed by the for
command immediately);the for
reference (%I
) expansion requires information from the file system:
~s
, ~a
, ~t
, ~z
and ~$ENV:
, information from the file system is required, of course;~n
, ~x
and ~p
, and also the corresponding parts of ~f
, which is nothing but ~dpnx
, the file system is accessed for case preservation (if the path does not exist, the original case is maintained);~n
, ~x
, ~p
and ~d
, and also ~f
, the file system needs to be accessed in case a relative path is provided, with or without a dedicated drive specified (for instance, abc\def
, D:data
, P:
), because the current working directory path (of the given drive in case) needs to be determined;
So the ~d
modifier, and also the corresponding part of ~f
, is handled by pure string manipulation as long as the file system is not accessed according to the aforementioned conditions.
Simply try your original code but with absolute paths, like for %I in (a:\ b:\ c:\ ">:\" "&:\") do @rem %~fI
, etc., and you will find that there are no corresponding =Drive:
variables.
Drive letters can literally be almost any characters (see subst
).
As soon as for
accesses the file system to search for drives and paths, the accessed drives are recorded in the =Drive:
variables.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With