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

5

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

在文章后面有一张表格,列出了常见于命令提示符的环境变量。其中一些被列出的变量被称为 易失性(只读)。文章中有句话说:

动态环境变量是只读的,并且每次展开变量时都会计算它们。当使用 SET 列出所有变量时,它们不会出现在列表中。不要尝试直接设置动态变量。

我理解了后两个陈述,但理解不了第一个陈述。

疑问:

  • %userprofile% 是一个 非易失性 变量,它解析为 %SystemDrive%\Users\{username},而 %homepath% 是一个 易失性 变量,它解析为 Users\{Username}。这两个命令非常相似(除了 systemdrive),那么为什么一个易失性,另一个非易失性?

  • 变量成为动态变量的标准是什么?是什么使得 %appdata%(只是举个例子)成为非易失性变量?

  • 动态变量每次展开变量时都会计算,这对像 %CD% %DATE% %TIME% %RANDOM% 这样的变量有意义,因为如果它们是 非易失性 的话,它们将失去功能。但这会影响 %homepath% 吗?

  • 一些 非易失性 变量在其中具有某种动态组件。例如: %userprofile% 在其路径中具有 %SystemDrive%{username}。那么这些变量为什么不是动态的?

1个回答

8
有三种类型的变量,可以使用语法%variable%!variable!访问其值,在启用延迟环境变量扩展的情况下,使用命令setlocal的选项EnableDelayedExpansion从Windows命令提示符窗口或批处理文件中进行,即使用%SystemRoot%\System32\cmd.exe

1. 持久存储变量

在Windows注册表中存储了环境变量。
  1. User variables stored in Windows registry under the key:

    HKEY_CURRENT_USER\Environment
    
  2. System variables stored in Windows registry under the key:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
    
用户变量仅为存储它们的用户注册表(文件%UserProfile%\ntuser.dat)中的帐户定义。系统变量是为在Windows计算机上使用的所有帐户定义的(文件%SystemRoot%\System32\config\SYSTEM)。可以通过打开Windows控制面板,单击“系统”,单击(左侧)“高级系统设置”上的下一个按钮并单击“环境变量”按钮来查看、编辑和删除持久存储的变量。自Windows XP以来,上半部分是当前用户帐户的用户变量,下半部分是系统变量。自Windows XP以来,默认情况下仅定义了TEMP和TMP作为用户变量。预定义的系统变量列表自Windows XP以来如下:
ComSpec
NUMBER_OF_PROCESSORS
OS
PATH
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
TEMP
TMP
windir

在Windows Vista和更新的Windows版本中,默认定义了名为PSModulePath系统变量。

除了PATHPATHEXT之外的任何预定义系统变量都不应被删除或修改,否则可能会导致许多问题,甚至导致Windows无法启动。我强烈建议在对预定义的系统变量进行实验之前,在虚拟机上备份整个虚拟机镜像。

1.1 持久存储变量的备份

在玩弄这些变量之前,建议备份用户系统变量,方法是打开命令提示符窗口并运行例如:

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 恢复持久存储的变量

可以通过在命令提示符窗口中执行以下操作来恢复用户变量,前提是已经进行了备份

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

从命令提示符窗口(管理员身份),并在备份之前,可以使用以下命令还原系统变量:

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

建议在从备份中恢复用户系统变量后重新启动Windows,以确保所有进程都使用了恢复的变量。

1.3 使用批处理文件修改PATH

想要使用批处理文件修改用户系统PATH的批处理程序员应先阅读:

注意:绝对禁止在批处理文件中使用命令SETX来修改用户系统PATH变量中的%PATH%

唯一需要在安装程序(可执行文件或脚本)时使用批处理文件修改用户或系统 PATH 的原因是该程序主要设计用于由Windows命令行中的用户使用。如果一个程序需要其目录或其子目录之一位于 PATH 中才能正常工作,则该程序设计不良。如果一个程序将文件夹路径添加到Windows默认定义的文件夹路径左侧的系统 PATH 中,则该程序设计非常糟糕。 系统 PATH 变量应始终以以下内容开头:
%SystemRoot%\System32;%SystemRoot%;%SystemRoot%\System32\Wbem

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

2. Windows shell 变量

有很多预定义的 Windows 环境 变量,可以在以下网址中看到:

这些变量由 Windows shell 定义,Windows shell 默认情况下是以 registry 键 Shell 下的值为 explorer.exe 的方式启动的。

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

用户最常注意到的Windows shell元素是Windows桌面、Windows开始菜单和Windows任务栏(包括系统托盘)。

Windows shell在其内存中定义了许多环境变量,这些变量取决于Windows注册表中各种值的当前用户帐户,不像上述所述,这些变量不会持久存储在Windows注册表中。当前环境变量列表会在创建新进程(例如从Windows shell启动可执行文件)时复制。

由Windows shell定义的环境变量列表包括持久存储的用户系统变量以及当前用户帐户的shell变量,可以通过打开命令提示符窗口并运行无任何附加参数的SET命令来查看。

大多数Shell变量是从注册表字符串中定义的。

  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders(类型为REG_SZ)。
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders(类型为REG_EXPAND_SZ)。
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders(类型为REG_SZ)。
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders(类型为REG_EXPAND_SZ)。

大多数注册表字符串值存在于类型为REG_EXPAND_SZUser Shell Folders和类型为REG_SZShell Folders中。但是有一些注册表字符串值仅存在于这两个注册表键中的一个。

另请参阅:如何在用户桌面目录中创建目录?
在这个答案中详细解释了Windows资源管理器如何评估这些注册表字符串值,并在用户使用regedit.exereg.exe手动修改时如何处理它们,以Desktop文件夹为例。

函数CreateEnvironmentBlock和私有的shell32函数RegenerateUserEnvironmentexplorer.exeGetUserNameExWGetComputerNameExW一起使用,用于创建环境变量列表。当使用Windows内核库函数CreateProcess从Windows shell启动可执行文件时,Windows Explorer会复制该列表。

另请参阅Eryk Sun在问题Where are the environment variables for cmd.exe stored?上的评论。

在64位Windows上,有一些环境变量取决于启动64位或32位可执行文件。Microsoft在WOW64 Implementation Details中记录了它们的详细信息:

64位进程:

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

Windows Server 2008、Windows Vista、Windows Server 2003和Windows XP:从Windows 7和Windows Server 2008 R2开始,添加了ProgramW6432和CommonProgramW6432环境变量。

32位进程:

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

环境变量PROCESSOR_ARCHITECTURE的值不能用于在批处理文件中确定安装的Windows是32位(x86)还是64位(AMD64)。该值取决于64位%SystemRoot%\Sysem32\cmd.exe或32位%SystemRoot%\SysWOW64\cmd.exe对64位Windows的批处理文件的处理。

另请参阅Microsoft文档:

Windows shell定义的环境变量在编写批处理文件时必须明智使用,这些文件应该设计为在同一台或不同的Windows机器上由其他帐户执行。许多在批处理文件作者的环境中正常工作的批处理文件,在以系统帐户作为计划任务或在不同机器上运行相同批处理文件的环境中却无法正常工作,因为环境变量列表存在差异。
通过Windows内核库函数CreateProcess启动可执行文件时,进程定义的环境变量决定了启动的可执行文件可以使用哪些环境变量。
大多数应用程序将lpEnvironment参数设置为null,使用CreateProcess。因此,CreateProcess会复制当前进程的当前环境变量。因此,从Windows桌面、开始菜单或任务栏启动的每个可执行文件都会获得由运行为Windows shell的explorer.exe实例定义的环境变量。
一个非常好的编码可执行文件或脚本,使用默认定义的Windows环境变量明确验证每个已使用的环境变量是否确实被定义并使用适当的默认值,例如在环境变量SystemRoot未定义的情况下使用C:\Windows,并检查是否真的存在一个目录C:\Windows,在重要的环境变量未定义之前退出并显示适当的错误消息,以防止可能造成的损害。
SystemRoot是Windows shell变量的一个示例,由explorer.exe定义为环境变量,不是由shell文件夹的注册表字符串值确定。一些环境变量值不应该由用户随时修改,而与其真实来源无关,这是脚本作者永远不需要知道的Windows实现细节。
3. Windows命令处理器的动态变量
在命令提示符窗口中运行set /?命令时,SET输出的帮助中列出了一些变量,这些变量在仅运行set命令时无法找到它们的环境变量列表中。
CD
DATE
TIME
RANDOM
ERRORLEVEL
CMDEXTVERSION
CMDCMDLINE
HIGHESTNUMANODENUMBER

动态变量是cmd.exe的内部变量。因此,这些变量仅在运行cmd.exe进程或由cmd.exe处理的批处理文件中的Windows命令提示窗口中可用。动态变量在其他可执行文件或脚本中不可用,因为这些变量不是环境变量。
最常用的动态变量有:
1. CD 当前目录路径(不以反斜杠结尾),除非当前目录是驱动器的根目录。
2. DATE 当前本地日期的格式,与Windows区域和语言设置中定义的帐户相同。
3. TIME 当前本地时间的格式,与Windows区域和语言设置中定义的帐户相同。
4. ERRORLEVEL 先前执行的命令或程序的退出值。
5. RANDOM 0到32767之间的随机十进制数。
还有一些更少使用的动态变量,但它们在批处理文件中很少使用。
此外,还有一个变量__AppDir__,它包含当前运行的cmd.exe路径,始终以反斜杠结尾,这是微软未记录的。我建议不要使用这个未记录的变量,因为不能保证未来版本的cmd.exe仍具有此变量。例如,在64位Windows上,__AppDir__%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命令处理器自身动态更改的,而环境变量的值仅在执行批处理文件期间使用命令SET重新定义环境变量时才会更改。这是环境变量和动态变量之间的重要区别。
4. 访问动态变量的值
每个环境变量都可以在处理的批处理文件的本地环境中被删除或重新定义。没有环境变量是只读的。

cmd.exe 内部包含文件扩展名列表 .COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC,如果本地环境变量列表中不存在环境变量PATHEXT,则用作其值,以便在命令行或批处理文件中指定没有文件扩展名的脚本和可执行文件。但是,cmd.exe 不包含文件夹路径列表作为后备列表,如果本地环境中不存在环境变量PATH,则无法找到这些文件。因此,批处理文件编写者应谨慎修改本地环境变量PATH

动态变量的值可以像环境变量的值一样被引用,在启用延迟扩展时使用%variable%!variable!。但Windows命令处理器总是首先在当前的环境变量列表中搜索是否有特定名称的变量。只有当没有同名的环境变量时,cmd才会在其内部的动态变量列表中搜索是否有指定名称的变量。
在定义与动态变量同名的环境变量时,无法再访问动态变量的当前值了。因此,批处理文件作者不应将动态变量名称之一用作环境变量的名称。
下面是一个演示批处理文件作者定义名称为ERRORLEVEL的环境变量后发生的情况的代码。
@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.

调试批处理文件中可以看到,第一个IF条件if %ERRORLEVEL% EQU 12会将%ERRORLEVEL%替换为字符串014,因为存在环境变量ERRORLEVEL定义为该字符串值。由于操作符EQU,命令IF使用函数wcstol将被解释为八进制数的字符串014和字符串12转换为32位有符号整数值并进行比较。因此第一个条件为真。
第二个IF条件if %ERRORLEVEL% == 014也会将%ERRORLEVEL%替换为字符串014,因为存在定义为该字符串值的环境变量ERRORLEVEL。但是由于操作符==,命令IF现在使用函数lstrcmpW。因此第二个条件也为真。

第三个IF条件使用命令IF在命令提示符窗口中运行if /?输出所解释的建议语法。可以看到,使用建议的语法会导致评估Windows命令处理器的内部动态变量ERRORLEVEL的值,即使已经定义了名为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% 会在控制台窗口中打印三次相同的日期/时间,因为这两个变量引用已经被 Windows 命令处理器在执行命令 FOR 之前解析整个命令块时展开了。 %RANDOM% 由于同样的原因会打印三次相同的数字,而 !RANDOM! 则通常会打印三个不同的数字。

另请参见:

if errorlevel numberif not errorlevel number 也可以在命令块内使用!

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

@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命令修改动态变量的值,因为这会导致定义一个以动态变量命名的环境变量,该变量优先于动态变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接