CMD中动态环境变量和普通环境变量的区别

Difference between Dynamic Environment Variables and Normal Environment Variables in CMD

我在 ss64 上阅读了一篇关于命令提示符中的环境变量的文章。

本文后面有一个 table,其中说明了命令提示符中常见的环境变量。其中列出的一些变量被称为 Volatile(只读)。文章中的一句话是这样写的:-

Dynamic environment variables are read-only and are computed each time the variable is expanded. When all variables are listed with SET, these will not appear in the list. Do not attempt to directly SET a dynamic variable.

后两句我明白了。但是第一个看不懂

疑惑:-

使用命令 EnableDelayedExpansion 的选项 EnableDelayedExpansion 启用延迟环境变量扩展时,使用语法 %variable%!variable! 访问三种类型的变量。 =28=] 来自 Windows 命令提示符 window 或批处理文件,即使用 %SystemRoot%\System32\cmd.exe.

1。持久存储变量

环境 变量永久存储在 Windows 注册表中。

  1. User 变量存储在 Windows 注册表项下:

    HKEY_CURRENT_USER\Environment
    
  2. System 变量存储在 Windows 注册表项下:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
    

user 变量仅针对存储它们的用户注册表配置单元(文件 %UserProfile%\ntuser.dat)的帐户定义。 系统 变量是为 Windows 机器(文件 %SystemRoot%\System32\config\SYSTEM)上使用的所有帐户定义的。

打开Windows 控制面板,点击系统,可以查看、编辑和删除持久存储变量,单击 高级系统设置 上的下一步(左侧),然后单击 环境变量 按钮。上半部分用于当前用户帐户的 user 变量,下半部分用于自 Windows XP 以来的 system 变量。

自 Windows XP 以来,默认定义为 user 变量 TEMPTMP

预定义的列表系统变量是因为Windows XP:

ComSpec
NUMBER_OF_PROCESSORS
OS
PATH
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
TEMP
TMP
windir

在 Windows Vista 和较新的 Windows 版本上默认定义了 系统 变量 PSModulePath.

None 的预定义 system 变量(PATHPATHEXT 除外)应该删除或修改,因为这可能会导致很多甚至可能导致 Windows 不再启动的问题。我强烈建议使用虚拟机来试验预定义的 system 变量,在开始试验之前存在整个虚拟机映像的备份。

1.1 持久存储变量的备份

建议在打开命令提示符 usersystem 变量之前备份它们 window 和 运行 例如:

md C:\VariablesBackup 2>nul
%SystemRoot%\System32\reg.exe EXPORT HKCU\Environment "C:\VariablesBackup\UserVariables.reg"
%SystemRoot%\System32\reg.exe EXPORT "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "C:\VariablesBackup\SystemVariables.reg"

1.2 持久存储变量的恢复

恢复 user 变量可以在命令提示符下完成 window 之前使用以下命令进行备份:

%SystemRoot%\System32\reg.exe DELETE "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /f
%SystemRoot%\System32\reg.exe IMPORT "C:\VariablesBackup\UserVariables.reg"

恢复系统变量可以在命令提示符下完成window打开之前以管理员身份进行备份 与:

%SystemRoot%\System32\reg.exe DELETE HKCU\Environment /f
%SystemRoot%\System32\reg.exe IMPORT "C:\VariablesBackup\SystemVariables.reg"

建议从备份中恢复 usersystem 变量后重新启动 Windows 以确保真正所有进程都使用恢复的变量。

1.3 使用批处理文件修改PATH

考虑使用批处理文件修改 usersystem PATH 的批处理文件程序员应首先阅读:

  • Why are other folder paths also added to system PATH with SetX and not only the specified folder path?
  • How to search and replace a string in environment variable PATH?
  • Adding the current directory to Windows path permanently

注意:绝对不行-永远不要使用命令SETX在批处理文件中使用 %PATH% 修改 usersystem PATH 变量。

在安装程序(可执行或script) 是该程序主要供用户从 Windows 命令行使用。如果一个程序要求其目录或其子目录之一在 PATH 中才能工作,则该程序的设计很糟糕。如果程序将 system PATH 的文件夹路径添加到 Windows.

默认定义的文件夹路径,则该程序的设计非常糟糕

system PATH 变量应始终以:

开头
%SystemRoot%\System32;%SystemRoot%;%SystemRoot%\System32\Wbem

Windows系统目录是包含大多数可执行文件和动态链接库的目录。因此,它应该始终是在当前目录之后搜索可执行文件和库的第一个目录。

2。 Windows shell 变量

还有很多 Windows environment 预定义变量,可以在以下位置看到:

这些变量由 Windows shell 定义,默认情况下 explorer.exe 在 Windows 上启动 Windows shell 根据注册表项下的注册表值 Shell

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

用户最注意的 Window shell 元素是 Windows 桌面、Windows 开始菜单和Windows 带有系统托盘的任务栏。

Windows shell 在其内存中定义了许多环境变量,这些变量取决于 Windows 注册表中的各种值,当前用户帐户未永久存储在 Windows 注册表中如上所述。每当创建新进程时都会复制当前的环境变量列表,例如从 Windows shell.

启动可执行文件

由Windowsshell定义的环境变量列表,由持久存储的usersystem组成变量和当前用户帐户的 shell 变量可以通过打开命令提示符 window 和 运行 命令 SET 查看 没有任何附加参数。

大多数 shell 变量是根据

下的注册表字符串定义的
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders

大多数注册表字符串值存在于 REG_EXPAND_SZ 类型的 User Shell FoldersREG_SZ 类型的 Shell Folders 中。但是有一些注册表字符串值仅存在于两个注册表项之一下。

另请参阅:
在这个答案中详细解释了 Windows Explorer 如何评估这些注册表字符串值并在用户使用 regedit.exereg.exe 对 shell 的示例进行手动修改时处理它们文件夹 Desktop.

函数CreateEnvironmentBlock and the private shell32 function RegenerateUserEnvironment are used by explorer.exe with GetUserNameExW and GetComputerNameExW to create the environment variables list copied by Windows Explorer on starting an executable from Windows shell using the Windows kernel library function CreateProcess.

另见Eryk Sun on question Where are the environment variables for cmd.exe stored?

写的评论

64 位 Windows 上有一些环境变量取决于启动 64 位或 32 位可执行文件。 Microsoft 在 WOW64 Implementation Details 上将它们记录如下:

64-bit process:

PROCESSOR_ARCHITECTURE=AMD64 or PROCESSOR_ARCHITECTURE=IA64 or PROCESSOR_ARCHITECTURE=ARM64
ProgramFiles=%ProgramFiles%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles%
CommonProgramW6432=%CommonProgramFiles%

Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The ProgramW6432 and CommonProgramW6432 environment variables were added starting with Windows 7 and Windows Server 2008 R2.

32-bit process:

PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=%PROCESSOR_ARCHITECTURE%
ProgramFiles=%ProgramFiles(x86)%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles(x86)%
CommonProgramW6432=%CommonProgramFiles%

如果安装的 Windows 是 32 位的,则 environment 变量 PROCESSOR_ARCHITECTURE 的值不能用于从批处理文件中查找(x86) 或 64 位 (AMD64) Windows。该值取决于 64 位 %SystemRoot%\Sysem32\cmd.exe 或 32 位 %SystemRoot%\SysWOW64\cmd.exe 在 64 位 Windows.

上对批处理文件的处理

另请参阅 Microsoft 文档:

environment 变量的使用必须明智地编写由 Windows shell 定义的批处理文件,该批处理文件应设计为由其他人执行相同或不同 Windows 机器上的帐户。许多批处理文件在批处理文件作者的环境中运行良好,但由于 环境变量列表。

进程在使用Windows内核库函数启动可执行文件时定义的环境变量CreateProcess决定了启动的可执行文件可以使用的环境变量。

大多数应用程序使用 CreateProcess,参数 lpEnvironment 的值为 null。因此 CreateProcess 复制当前进程的当前环境变量。出于这个原因,每个从 Windows 桌面、开始菜单或任务栏启动的可执行文件都会获得由 explorer.exe 实例 运行 定义为 Windows shell 的环境变量。

一个非常好的编码可执行文件或脚本,使用在 Windows 上默认定义的环境变量,明确验证每个使用的环境变量是否真正定义,否则使用合适的默认值,如 C:\Windows环境变量 SystemRoot 未定义,检查是否确实存在目录 C:\Windows,并退出并在可能造成损坏之前未定义的重要环境变量上显示适当的错误消息。

SystemRoot 是由 explorer.exe 定义的 Windows shell 变量作为环境变量的示例,它不是由 [=609] 的注册表字符串值确定的=] 文件夹。某些环境变量值不应由用户在任何时候独立于其真实来源进行修改,脚本作者永远不需要知道这些是 Windows 实现细节。

3。 Windows 命令处理器的动态变量

在命令提示符 window set /? 中 运行 输出的命令 SET 的帮助中列出了一些无法找到的变量在 运行 上的环境变量列表中只是 set.

这些变量是:

CD
DATE
TIME
RANDOM
ERRORLEVEL
CMDEXTVERSION
CMDCMDLINE
HIGHESTNUMANODENUMBER

动态变量是cmd.exe内部变量。因此,这些变量仅在 Windows 命令提示符 window 中可用,它是 运行 cmd.exe 进程或由 cmd.exe 处理的批处理文件。 动态变量在其他可执行文件或脚本中不可用,因为这些变量不是环境变量。

最常用的动态变量是:

  1. CD
    当前目录路径不以反斜线结尾,除了当前目录是驱动器的根目录。
  2. [=384=日期
    Windows. 的区域和语言设置中为帐户定义的格式的当前本地日期
  3. 时间
    Windows.
  4. 的区域和语言设置中为帐户定义的格式的当前本地时间
  5. 错误级别
    先前执行的命令或程序的退出值。
  6. 随机
    0 到 32767 之间的随机十进制数。

还有一些动态变量,但很少在批处理文件中使用。

另外还有变量 __AppDir__ 包含当前 运行 cmd.exe 的路径,总是以 Microsoft 未记录的反斜杠结尾。我建议不要使用这个 undocumented 变量,因为不能保证 cmd.exe 的未来版本仍然有这个变量。 __AppDir__ 在 64 位上 Windows,例如,%SystemRoot%\System32\ 在 64 位上 %SystemRoot%\System32\cmd.exe 当前 运行,或 %SystemRoot%\SysWOW64\ 在 32 位上%SystemRoot%\SysWOW64\cmd.exe 当前 运行,或将 cmd.exe 复制到任何其他文件夹并启动此副本 cmd.exe 的任何其他路径。 SS64 页面 How-to: Windows Environment Variables 上列出了更多 未记录的动态 变量,出于同样的原因,在使用时应特别小心。

最常用的 动态 变量的值由 Windows 命令处理器本身动态更改,而环境变量的值仅在命令 [=285] 时更改=]SET 在执行批处理文件期间用于重新定义 environment 变量。这是 environmentdynamic 变量之间的重要区别。

4。访问动态变量的值

每个 environment 变量都可以在已处理批处理文件的 local 环境中删除或重新定义。没有 environment 变量 read-only.

cmd.exe 在其代码内部包含文件扩展名列表 .COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC,如果 environment 变量 PATHEXT,它用作 PATHEXT 的值 PATHEXT 不存在于 local 环境变量列表中,以便能够在命令行或批处理文件中找到没有文件扩展名的脚本和可执行文件。但是如果 environment 变量 PATHlocal[=421= 中不存在,cmd.exe 不包含文件夹路径列表作为后备列表] 环境。因此,无论出于何种原因,批处理文件编写者在修改 本地环境 变量 PATH 时应该小心。

dynamic 变量的值被引用,就像 environment 变量的值被 %variable%!variable! 启用 delayed expansion。但是 Windows 命令处理器总是首先在当前的 environment 变量列表中搜索是否存在具有指定名称的变量。仅当不存在具有该名称的 environment 变量时,cmd 在其内部动态变量列表中搜索下一个是否存在具有指定名称的变量。

dynamic 变量的当前值无法再在定义与 environment 变量同名的变量时被访问=]动态变量。因此,批处理文件编写器永远不应使用 dynamic 变量名称之一作为 environment 变量的名称。

这是一个代码,它演示了如果批处理文件编写者有一个非常糟糕的想法来定义名称为 ERRORLEVEL.[=145 的 environment 变量会发生什么=]

@echo off
setlocal EnableExtensions DisableDelayedExpansion
echo/
echo Define environment variable ERRORLEVEL with string value "014".
echo/
set ERRORLEVEL=014
if %ERRORLEVEL% EQU 12 (echo EQU: ERRORLEVEL is 12.) else echo EQU: ERRORLEVEL is not 12.
if %ERRORLEVEL% == 014 (echo  ==: ERRORLEVEL is 14.) else echo  ==: ERRORLEVEL is not 14.
if errorlevel 0 (
    if not errorlevel 1 (echo  IF: ERRORLEVEL is 0.) else echo  IF: ERRORLEVEL is greater than 0.
) else echo  IF: ERRORLEVEL is less than 0.
echo/
echo Delete the environment variable ERRORLEVEL.
echo/
set ERRORLEVEL=
if %ERRORLEVEL% EQU 12 (echo EQU: ERRORLEVEL is 12.) else echo EQU: ERRORLEVEL is not 12.
if %ERRORLEVEL% == 014 (echo  ==: ERRORLEVEL is 14.) else echo  ==: ERRORLEVEL is not 14.
if errorlevel 0 (
    if not errorlevel 1 (echo  IF: ERRORLEVEL is 0.) else echo  IF: ERRORLEVEL is greater than 0.
) else echo  IF: ERRORLEVEL is less than 0.
echo/
echo In all test cases the value of dynamic variable ERRORLEVEL was 0.
echo/
for %%I in (%CMDCMDLINE%) do if /I "%%~I" == "/c" pause & goto EndBatch
:EndBatch
endlocal

这个批处理文件的输出是:

Define environment variable ERRORLEVEL with string value "014".

EQU: ERRORLEVEL is 12.
 ==: ERRORLEVEL is 14.
 IF: ERRORLEVEL is 0.

Delete the environment variable ERRORLEVEL.

EQU: ERRORLEVEL is not 12.
 ==: ERRORLEVEL is not 14.
 IF: ERRORLEVEL is 0.

In all test cases the value of dynamic variable ERRORLEVEL was 0.

可以看出 that the first IF condition if %ERRORLEVEL% EQU 12 results in replacing %ERRORLEVEL% by the string 014 because of there is the environment variable ERRORLEVEL defined with this string value. The function wcstol 被命令 IF 使用,因为运算符 EQU 将字符串 014 转换为被解释为八进制数因为前导 0 和字符串 12 到 32 位有符号整数值并比较它们是否相等。因此第一个条件为真。

第二个 IF 条件 if %ERRORLEVEL% == 014 也导致用字符串 014 替换 %ERRORLEVEL% 因为有 environment 变量 ERRORLEVEL 使用此字符串值定义。但是现在函数 lstrcmpW 被命令 IF 使用,因为运算符 ==。所以第二个条件也成立。

第三个 IF 条件使用命令 IF 输出的帮助解释的推荐语法 运行 if /? 在命令提示符 window 中。可以看出,即使 environment 变量定义为名称 ERRORLEVEL。评估先前执行的命令或程序的退出代码的推荐语法始终适用于批处理文件中的任何位置,如此处所示。

看到一个所以:

此外,必须考虑到访问 动态 变量的当前值总是在被 Windows 命令处理器扩展的变量引用上,而不是真正的执行命令或可执行文件。

证明差异的代码:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
for /L %%I in (1,1,3) do (
    echo %%DATE%% %%TIME%% is:  %DATE% %TIME%
    echo ^^!DATA^^! ^^!TIME^^! is:  !DATE! !TIME!
    echo %%RANDOM%%/^^!RANDOM^^!: %RANDOM%/!RANDOM!
    if exist %SystemRoot%\System32\timeout.exe (
        %SystemRoot%\System32\timeout.exe /T 3 /NOBREAK >nul
    ) else (
        %SystemRoot%\System32\ping.exe 127.0.0.1 -n 4 >nul
    )
)
for %%I in (%CMDCMDLINE%) do if /I "%%~I" == "/c" pause & goto EndBatch
:EndBatch
endlocal

输出例如:

%DATE% %TIME% is:  31.01.2021 13:54:30,67
!DATA! !TIME! is:  31.01.2021 13:54:30,68
%RANDOM%/!RANDOM!: 18841/27537
%DATE% %TIME% is:  31.01.2021 13:54:30,67
!DATA! !TIME! is:  31.01.2021 13:54:33,12
%RANDOM%/!RANDOM!: 18841/16705
%DATE% %TIME% is:  31.01.2021 13:54:30,67
!DATA! !TIME! is:  31.01.2021 13:54:36,16
%RANDOM%/!RANDOM!: 18841/32668

%DATE% %TIME% 导致打印到控制台 window 三倍相同 date/time 因为这两个变量引用已经被 Windows 命令处理器在解析命令 FOR 之前的整个命令块都被执行了。 %RANDOM% 出于同样的原因导致打印三倍相同的数字,而 !RANDOM! 通常打印三个不同的数字。

另请参阅:

  • How does the Windows Command Interpreter (CMD.EXE) parse scripts?

if errorlevel numberif not errorlevel number 也可以在命令方块中工作!

动态 环境变量只能在默认启用命令扩展的情况下访问。否则 Windows 命令处理器模拟 MS-DOS 和 Windows 的 COMMAND.COM 行为(或多或少) 95/98/ME 不支持 动态 正如这段代码所示:

@echo off
setlocal DisableExtensions DisableDelayedExpansion
echo/
echo With command extensions disabled:
echo/
echo Date/time is: %DATE% %TIME%
echo Current dir: "%CD%"
endlocal
setlocal EnableExtensions DisableDelayedExpansion
echo/
echo With command extensions enabled:
echo/
echo Date/time is: %DATE% %TIME%
echo Current dir: "%CD%"
echo/
for %%I in (%CMDCMDLINE%) do if /I "%%~I" == "/c" pause & goto EndBatch
:EndBatch
endlocal

输出例如:

With command extensions disabled:

Date/time is:
Current dir: ""

With command extensions enabled:

Date/time is: 31.01.2021 14:17:42,92
Current dir: "C:\Temp\Development & Test!"

动态变量的值只能读取。无法使用命令 SET 修改 dynamic 变量的值,因为它会导致定义 environment 变量具有 dynamic 变量的名称,该变量优先于 dynamic 变量。