为什么我不能在Windows 7上访问名为__CD__的变量?

dbe*_*ham 14 batch-file environment-variables windows-7

注意 - 此问题基于在Windows 7上观察到的行为.我认为该行为适用于从Vista开始的所有其他版本.基于MC ND的答案和Foxidrive的评论,它不适用于XP.

我可以使用伪动态变量%CD%来获取当前目录,而不是尾随\.如果我定义一个名为CD using的真实变量set "CD=someValue",则%CD%返回我指定的值,而不是当前目录.

一个鲜为人知的动态变量是%__CD__%相同的%CD%,除了它包括尾部\.但是,如果我这样做set "__CD__=someValue",我无法访问我指定的值!我可以set __CD__看到我的变量存在我的指定值,但%__CD__%总是返回当前目录!

有没有人知道这个%__CD__%机制是如何工作的(为什么它与任何其他动态变量不同)?

有人能想出一种方法来__CD__使用标准批处理命令(不解析输出set __CD__)来访问我的变量吗?

这更像是一个学术问题,而不是一个实际问题.没有人应该定义名称与动态变量匹配的变量.


我不希望解析结果的例程的原因set __CD__是因为值可以在其中包含换行符.有人可以定义两个变量,__CD__或者__CD__2,或者他们可以定义__CD__一个包含换行后跟的值__CD__2=....要区分这两种情况是不可能的.(正如我所说,这是一个学术问题,而不是一个实际问题!)

dbe*_*ham 17

我有一个关于如何以及为什么__CD__表现得如此不同于任何其他伪环境变量的理论.

我写了一个简单的ENV.JS脚本来探测过程环境.

var env=WScript.CreateObject("WScript.Shell").Environment("Process");
WScript.echo(env(WScript.Arguments.Item(0)));
Run Code Online (Sandbox Code Playgroud)

ENV.JS期望将环境变量名称作为唯一参数,并简单地打印变量的值.我在Windows 7机器上进行了测试.该脚本可以在CMD.EXE控制台中运行,也可以通过定义参数的快捷方式直接运行.

我将对各种动态变量进行分类,显示对比行为,以及关于它们工作机制的理论.

有许多动态变量可以分为三类:

1)正常查看"变量",没有任何前缀或后缀

CD              current directory
DATE            current date
TIME            current time
ERRORLEVEL      current errorlevel
RANDOM          random integer between 0 and 32767
CMDEXTVERSION   current extension level (only available if extensions are enabled)
CMDCMDLINE      the command line that invoked the current CMD.EXE level
Run Code Online (Sandbox Code Playgroud)

动态值仅可通过CMD.EXE中的扩展获得,并且仅在启用命令扩展时才可用.它们不能通过SET或ENV.JS获得.可以使用SET显式定义静态值来覆盖动态值.覆盖值可通过SET和ENV.JS获得.

C:\test>echo %cmdcmdline%
"C:\Windows\system32\cmd.exe"

C:\test>set cmdcmdline
Environment variable cmdcmdline not defined

C:\test>cscript //nologo env.js cmdcmdline
cmdcmdline=

C:\test>set cmdcmdline=override

C:\test>echo %cmdcmdline%
override

C:\test>set cmdcmdline
cmdcmdline=override

C:\test>cscript //nologo env.js cmdcmdline
cmdcmdline=override

C:\test>cmd /e:off
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\test>echo %cmdcmdline%
%cmdcmdline%
Run Code Online (Sandbox Code Playgroud)

动态值根本不是环境变量,这就是SET命令或ENV.JS无法访问它们的原因.CMD.EXE中的变量扩展代码首先检查变量的进程环境变量,并且只有在未找到时才将名称与特殊动态名称列表进行比较.每个动态"变量"必须存在特殊的CMD.EXE代码,以从适当的源派生值.

微软的Raymond Chen在他的博客The Old New Thing中描述了这类动态变量的行为

2)前缀为动态变量 =

=ExitCode
=ExitCodeAscii
=C:
=D:
...
=Z:
Run Code Online (Sandbox Code Playgroud)

在执行定义它的某个命令之前,这些动态值中的每一个都是未定义的.例如,一个全新的CMD.EXE控制台会话以= ExitCode undefined开头.一旦执行了设置返回代码的外部命令,它就会被定义.

有一个例外={driveLetter}:,即使首次启动CMD.EXE,也将始终定义与当前目录对应的变量.

不可能使用SET来定义任何这些变量,因为SET不允许=使用变量名.但是底层的流程环境变量空间确实允许=变量名称.它必须在CMD.EXE上下文之外完成.

我写了一个额外的TEST.JS来帮助测试这些变量:

var shell=WScript.CreateObject("WScript.Shell");
var env=shell.Environment("Process");
WScript.echo('Within JScript: ExitCode='+env("=ExitCode"));
env("=ExitCode") = "override";
WScript.echo('Within JScript after override: ExitCode='+env("=ExitCode"));
WScript.echo('Within JScript test.bat return code = '+shell.run("cmd /c test.bat",10,1));
WScript.echo('Within JScript after test.bat: ExitCode='+env("=ExitCode"));
Run Code Online (Sandbox Code Playgroud)

反过来,TEST.JS调用TEST.BAT:

@echo off
echo Within test.bat: ExitCode=%=ExitCode%
cmd /c exit 10
echo =Within test.bat: ExitCode=%=ExitCode%
pause
exit %errorlevel%
Run Code Online (Sandbox Code Playgroud)

以下是一些使用的测试结果=ExitCode,从全新的CMD.EXE会话开始:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\test>echo %=ExitCode%
%=ExitCode%

C:\test>set "=ExitCode=override"
The syntax of the command is incorrect.

C:\test>cscript //nologo env.js =ExitCode
=ExitCode=

C:\test>REM cscript is an external command that set the value!

C:\test>echo %=ExitCode%
00000000

C:\test>set =
The syntax of the command is incorrect.

C:\test>set ""|findstr /b =ExitCode
=ExitCode=00000000

C:\test>cscript //nologo env.js =ExitCode
=ExitCode=00000000

C:\test>cscript //nologo test.js
Within JScript: ExitCode=00000000
Within JScript after override: ExitCode=override
Within JScript test.bat return code = 10
Within JScript after test.bat: ExitCode=override

C:\test>
Run Code Online (Sandbox Code Playgroud)

以下是TEST.JS在新窗口中启动的TEST.BAT的结果:

Within test.bat: ExitCode=override
Within test.bat: ExitCode=0000000A
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

我相信这些动态变量是真正的环境变量,这就是SET和JScript都可以访问它们的原因.(SET只能使用特殊SET ""语法访问该值.)每次执行相关命令时,CMD.EXE都会动态定义(或更新)变量.ENV.JS和TEST.JS可以看到调用CMD.EXE会话设置的值.TEST.BAT cmd会话可以看到TEST.JS设置的继承覆盖值.但是,在TEST.BAT退出后,JScript继续获取覆盖值,因为CMD.EXE不在那里用返回代码更新值.

无论是启用还是禁用命令扩展,都可以使用此类动态变量.即使禁用扩展,动态值也会保留,如下所示:

C:\test>cmd /e:off
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\test>echo %=c:%
C:\test

C:\test>cd test

C:\test\test>echo %=c:%
C:\test\test
Run Code Online (Sandbox Code Playgroud)

3)__ CD__ - 一个特殊情况本身!
编辑 - 实际上,__ APPDIR__的工作方式相同

CMD.EXE和JScript始终可以使用此动态变量.可以定义覆盖值,但CMD.EXE和JScript都不能看到覆盖值,除了SET命令可以列出覆盖值.(另外,未显示,但是jeb发现SET/A可以读取覆盖值,如果它是数字).

我写了另一个TEST2.JS来探测这个变量.

var shell=WScript.CreateObject("WScript.Shell");
var env=shell.Environment("Process");
WScript.echo('Within JScript: __CD__='+env("__CD__"));
env("__CD__") = "JS override";
WScript.echo('Within JScript after override: __CD__='+env("__CD__"));
shell.run('cmd /c "set __CD__&pause",1,0');
Run Code Online (Sandbox Code Playgroud)

以下是一些测试的结果:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\test>echo %__CD__%
C:\test\

C:\test>set __CD__
Environment variable __CD__ not defined

C:\test>set "__CD__=Batch override"

C:\test>echo %__CD__%
C:\test\

C:\test>set __CD__
__CD__=Batch override

C:\test>cscript //nologo test2.js
Within JScript: __CD__=C:\test\
Within JScript after override: __CD__=C:\test\

C:\test>
Run Code Online (Sandbox Code Playgroud)

这是TEST2.JS打开的CMD.EXE窗口的结果:

__CD__=JS override
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

如果我定义ENV.JS的快捷方式如下:

Target:   C:\test\env.js __CD__
Start in: C:\test\xyz
Run Code Online (Sandbox Code Playgroud)

然后当我点击它时,我得到一个警告框说明:

__CD__=C:\test\xyz\
Run Code Online (Sandbox Code Playgroud)

我发现这些结果令人着迷.动态值不能是真正的环境变量.据推测,有一个低级别的OS环境变量访问例程,只要它被要求返回值,就会自动返回进程的当前目录__CD__.即使__CD__定义了一个名为true的静态环境变量,它也会这样做.

CMD.EXE SET命令必须以与大多数其他上下文不同的方式访问环境变量.我想可以编写一个C程序来获取指向进程环境内存的指针并解析任何真正的用户定义__CD__值,就像SET命令一样.

鉴于它是一个提供此值的低操作系统例程,%__CD__%即使禁用命令扩展,也可以使用它并不奇怪.

C:\test>cmd /e:off
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\test>echo %__cd__%
C:\test\

C:\test>cd test

C:\test\test>echo %__cd__%
C:\test\test\
Run Code Online (Sandbox Code Playgroud)

我认为从OS的角度来看,当前目录的概念对于每个进程都是至关重要的,MS决定通过动态虚拟环境变量为进程提供访问权限.XP OS允许扩展任何用户定义的__CD__覆盖值.也许这会导致某些应用程序出现问题,MS决定修改环境访问例程(从Vista开始?),以便始终返回真正的当前目录,而不管用户定义的任何__CD__变量.

因此,基于理论它是一个返回当前目录的低级例程,我现在认为不可能使用本机批处理命令来可靠地获取任何用户定义的值__CD__.

  • 对于`echo%cmdcmdline %% cd %% __ cd __%'禁用扩展名时,现在存在差异 (2认同)

Aac*_*ini 5

[ 这应该是一个评论,但是那将是一个很大的评论!]

我想添加%CD%变量也是一种特殊情况,原因如下:如果setlocal执行了一个命令,然后endlocal更改了当前目录,则后一条命令将当前目录改回setlocal执行时的活动目录:

@echo off

echo Original: %CD%
setlocal
md newdir
cd newdir
echo In newdir: %CD%
endlocal
echo After endlocal: %CD%
Run Code Online (Sandbox Code Playgroud)

此行为表明%CD%动态变量以setlocals 保存,并以(显式或隐式)endlocals 恢复,与普通变量相同。但是,奇怪的是,即使在setlocal!之前或之后定义了用户CD变量,该机制仍然有效。结论是该setlocal命令将当前目录保存在与环境变量分开的区域中,并且该endlocal命令从该区域恢复当前目录。


Hav*_*ard 3

根据此命令行参考,存在只读环境变量,其中包含 __CD__ 。

从该页面:

未记录的动态变量(只读)

%__CD__% 当前目录,以反斜杠结尾。

%=C:% C: 驱动器的当前目录。

%=D:% 如果驱动器 D: 已在当前 CMD 会话中访问,则为 D: 驱动器的当前目录。

%=ExitCode% EXIT /B 设置的最后一个返回码的十六进制值

%=ExitCodeAscii% 如果大于 32,则为 EXIT /B 设置的最后一个返回码的 ASCII 值。

  • @Leptonator - 我没有看到 %~p 的相关性。FOR 变量是与环境变量完全不同的构造。 (3认同)