首页 > 解决方案 > CMD中动态环境变量和普通环境变量的区别

问题描述

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

在本文后面有一个,其中说明了命令提示符中常见的环境变量。那里列出的一些变量被称为易失性(只读)。文章中发现的一句话说:-

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

我理解后两种说法。但我看不懂第一个。

疑问:-

标签: cmdenvironment-variablescommand-prompt

解决方案


存在三种类型的变量,它们的值是使用语法访问的,%variable%或者使用Windows 命令提示符窗口或批处理文件中的命令!variable!选项启用延迟环境变量扩展,即使用.EnableDelayedExpansionsetlocal%SystemRoot%\System32\cmd.exe

1. 持久化存储变量

Windows 注册表中永久存储了一些环境变量。

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

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

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

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

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

从 Windows XP 开始,默认情况下仅定义为用户变量。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

任何预定义的系统变量都不应删除或修改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.3PATH用批处理文件修改

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

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

在安装程序(可执行文件或脚本)时使用批处理文件修改用户系统 PATH的唯一原因是该程序主要由 Windows 命令行的用户使用。如果一个程序需要它的目录或其子目录之一完全PATH可以工作,那么它的设计就是糟糕的。如果一个程序将系统 PATH的文件夹路径添加到 Windows 默认定义的文件夹路径中,则该程序的设计非常糟糕 。

系统变量 PATH应始终以:

%SystemRoot%\System32;%SystemRoot%;%SystemRoot%\System32\Wbem

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

2. Windows shell 变量

可以在以下位置看到更多预定义的 Windows环境变量:

这些变量由 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 启动可执行文件)时,都会复制当前的环境变量列表。

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

大多数 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

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

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

函数CreateEnvironmentBlock和私有 shell32 函数与GetUserNameExWGetComputerNameExWRegenerateUserEnvironment一起用于创建由 Windows Explorer 在使用 Windows 内核库函数CreateProcess从 Windows shell 启动可执行文件时复制的环境变量列表。explorer.exe

另请参阅Eryk Sun关于问题cmd.exe 的环境变量存储在哪里的评论?

64 位 Windows 上有一些环境变量取决于启动 64 位或 32 位可执行文件。Microsoft 将它们记录在WOW64 实施细节中,如下所示:

64位进程:

PROCESSOR_ARCHITECTURE=AMD64PROCESSOR_ARCHITECTURE=IA64PROCESSOR_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%

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

另请参阅 Microsoft 文档:

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

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

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

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

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

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

在命令提示符窗口中运行的命令SET输出的帮助中列出了一些变量,这些set /?变量在运行时的环境变量列表中找不到set

这些变量是:

CD
DATE
TIME
RANDOM
ERRORLEVEL
CMDEXTVERSION
CMDCMDLINE
HIGHESTNUMANODENUMBER

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

最常用的动态变量是:

  1. CD
    当前目录路径不以反斜杠结尾,但当前目录是驱动器的根目录。
  2. DATE
    当前本地日期,格式为在 Windows 的区域和语言设置中为帐户定义的格式。
  3. TIME
    当前本地时间,格式为在 Windows 的区域和语言设置中为帐户定义的格式。
  4. ERRORLEVEL
    先前执行的命令或程序的退出值。
  5. RANDOM
    0 到 32767 之间的随机十进制数。

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

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

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

4. 访问动态变量的值

每个环境变量都可以在已处理批处理文件的本地环境中删除或重新定义。没有只读的环境变量。

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

动态变量的值与启用或启用延迟扩展的环境变量的值一样被引用。但如果存在具有指定名称的变量,Windows 命令处理器总是首先在当前环境变量列表中搜索。仅当没有具有该名称的环境变量时,如果存在具有指定名称的动态变量,则在其内部动态变量列表中搜索下一个。%variable%!variable!cmd

在定义与动态变量同名的环境变量时,不能再访问动态变量的当前值。出于这个原因,批处理文件编写器永远不应使用动态变量名称之一作为环境变量的名称。

下面是一个代码,它演示了如果批处理文件编写器真的不好用 name 定义环境变量会发生什么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函数wcstol由命令IF使用,因为运算符EQU014由于前导0和字符串而被解释为八进制数的字符串12转换为 32 位有符号整数值,并在相等时比较它们。因此第一个条件为真。

第二个IF条件if %ERRORLEVEL% == 014也导致替换%ERRORLEVEL%为字符串014,因为存在使用此字符串值定义的环境变量。ERRORLEVEL但是现在函数lstrcmpW因为 operator被命令IF==使用。因此,第二个条件也成立。

第三个IF条件使用在命令提示符窗口中运行时命令IF输出的帮助解释的推荐语法。if /?可以看出,即使使用 name 定义了环境变量,使用推荐的语法也会导致评估 Windows 命令处理器的内部动态变量的值。评估先前执行的命令或程序的退出代码的推荐语法始终在批处理文件中的任何位置工作,如此处所示。ERRORLEVELERRORLEVEL

也可以看看:

此外,必须考虑到访问动态变量的当前值总是在由 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%导致打印到控制台窗口三倍于相同的日期/时间,因为在完全执行命令FOR之前,Windows 命令处理器在解析整个命令块时已经扩展了这两个变量引用。%RANDOM%由于相同的原因导致打印三个相同的数字,而!RANDOM!通常打印三个不同的数字。

也可以看看:

if errorlevel numberif not errorlevel number在命令块内工作!

动态环境变量只能通过默认启用的命令扩展来访问。否则,Windows 命令处理器会模拟COMMAND.COMMS-DOS 和 Windows 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修改动态变量的值,因为它会导致使用优先于动态变量的动态变量的名称 定义环境变量。


推荐阅读