cmd - CMD中动态环境变量和普通环境变量的区别
问题描述
我正在阅读一篇关于ss64的关于命令提示符中的环境变量的文章。
在本文后面有一个表,其中说明了命令提示符中常见的环境变量。那里列出的一些变量被称为易失性(只读)。文章中发现的一句话说:-
动态环境变量是只读的,每次扩展变量时都会计算。当所有变量都用 SET 列出时,这些变量不会出现在列表中。不要尝试直接设置动态变量。
我理解后两种说法。但我看不懂第一个。
疑问:-
%userprofile%
是一个非易失性变量,解析为%SystemDrive%\Users\{username}
,并且%homepath%
是一个易失性变量,解析为Users\{Username}
。这两个命令非常相似(除了systemdrive
)。那么为什么一个是易失性的而另一个是非易失性的呢?变量动态的标准是什么?是什么让
%appdata%
(只是一个例子)一个非易失性变量?每次扩展变量时都会计算动态变量,这对于诸如此类的变量是有意义的,因为如果它们是非易失性
%CD% %DATE% %TIME% %RANDOM%
的,它们将失去其功能。但是效果如何呢?%homepath%
一些非易失性变量在其中具有某种动态成分。前任。
%userprofile%
有%SystemDrive%
并且{username}
在它的路径中。那么这些变量怎么不是动态的呢?
解决方案
存在三种类型的变量,它们的值是使用语法访问的,%variable%
或者使用Windows 命令提示符窗口或批处理文件中的命令!variable!
选项启用延迟环境变量扩展,即使用.EnableDelayedExpansion
setlocal
%SystemRoot%\System32\cmd.exe
1. 持久化存储变量
Windows 注册表中永久存储了一些环境变量。
用户变量存储在 Windows 注册表项下:
HKEY_CURRENT_USER\Environment
系统变量存储在 Windows 注册表项下:
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
。
任何预定义的系统变量都不应删除或修改PATH
,PATHEXT
因为这可能会导致很多麻烦,甚至可能导致 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
应首先阅读:
- “X 不是内部或外部命令、可运行程序或批处理文件”是什么原因?
- 为什么其他文件夹路径也使用 SetX 添加到系统 PATH 而不仅仅是指定的文件夹路径?
- 如何使用 .bat 文件从 PATH 环境变量中删除特定标记?
- 如何在环境变量 PATH 中搜索和替换字符串?
- 将当前目录永久添加到 Windows 路径
注意:绝对不能- 永远不要在批处理文件中使用命令SETX%PATH%
来修改用户或系统 PATH
变量。
在安装程序(可执行文件或脚本)时使用批处理文件修改用户或系统 PATH
的唯一原因是该程序主要由 Windows 命令行的用户使用。如果一个程序需要它的目录或其子目录之一完全PATH
可以工作,那么它的设计就是糟糕的。如果一个程序将系统 PATH
的文件夹路径添加到 Windows 默认定义的文件夹路径中,则该程序的设计非常糟糕 。
系统变量 PATH
应始终以:
%SystemRoot%\System32;%SystemRoot%;%SystemRoot%\System32\Wbem
Windows 系统目录是包含大多数可执行文件和动态链接库的目录。因此,它应该始终是在当前目录之后搜索可执行文件和库的第一个目录。
2. Windows shell 变量
可以在以下位置看到更多预定义的 Windows环境变量:
- SS64:操作方法: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_SZ
和Shell Folders
类型中REG_SZ
。但是有一些注册表字符串值仅存在于两个注册表项之一下。
另请参阅:如何在用户的桌面目录中创建目录?
在这个答案中详细解释了 Windows 资源管理器如何评估这些注册表字符串值并在用户手动修改时使用regedit.exe
或reg.exe
在 shell 文件夹的示例上处理它们Desktop
。
函数CreateEnvironmentBlock和私有 shell32 函数与GetUserNameExW和GetComputerNameExWRegenerateUserEnvironment
一起用于创建由 Windows Explorer 在使用 Windows 内核库函数CreateProcess从 Windows shell 启动可执行文件时复制的环境变量列表。explorer.exe
另请参阅Eryk Sun关于问题cmd.exe 的环境变量存储在哪里的评论?
64 位 Windows 上有一些环境变量取决于启动 64 位或 32 位可执行文件。Microsoft 将它们记录在WOW64 实施细节中,如下所示:
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%
如果安装的 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
环境变量决定了启动的可执行文件可以使用的环境变量。
大多数应用程序使用CreateProcess
with 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
. 动态变量在其他可执行文件或脚本中不可用,因为这些变量不是环境变量。
最常用的动态变量是:
- CD
当前目录路径不以反斜杠结尾,但当前目录是驱动器的根目录。 - DATE
当前本地日期,格式为在 Windows 的区域和语言设置中为帐户定义的格式。 - TIME
当前本地时间,格式为在 Windows 的区域和语言设置中为帐户定义的格式。 - ERRORLEVEL
先前执行的命令或程序的退出值。 - 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.exe
cmd.exe
最常用的动态变量的值由 Windows 命令处理器本身动态更改,而环境变量的值仅在批处理文件执行期间使用命令SET重新定义环境变量时才会更改。这是环境变量和动态变量之间的重要区别。
4. 访问动态变量的值
每个环境变量都可以在已处理批处理文件的本地环境中删除或重新定义。没有只读的环境变量。
cmd.exe
在其代码内部包含文件扩展名列表,如果本地环境变量列表中不存在环境变量,则该列表.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC
用作值,以便能够找到在命令行或没有文件扩展名的批处理文件中指定的脚本和可执行文件。但如果本地环境中不存在环境变量,则不包含文件夹路径列表作为后备列表。因此,无论出于何种原因,批处理文件编写器都应该小心修改本地环境变量。PATHEXT
PATHEXT
cmd.exe
PATH
PATH
动态变量的值与启用或启用延迟扩展的环境变量的值一样被引用。但如果存在具有指定名称的变量,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使用,因为运算符EQU
将014
由于前导0
和字符串而被解释为八进制数的字符串12
转换为 32 位有符号整数值,并在相等时比较它们。因此第一个条件为真。
第二个IF条件if %ERRORLEVEL% == 014
也导致替换%ERRORLEVEL%
为字符串014
,因为存在使用此字符串值定义的环境变量。ERRORLEVEL
但是现在函数lstrcmpW因为 operator被命令IF==
使用。因此,第二个条件也成立。
第三个IF条件使用在命令提示符窗口中运行时命令IF输出的帮助解释的推荐语法。if /?
可以看出,即使使用 name 定义了环境变量,使用推荐的语法也会导致评估 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%
导致打印到控制台窗口三倍于相同的日期/时间,因为在完全执行命令FOR之前,Windows 命令处理器在解析整个命令块时已经扩展了这两个变量引用。%RANDOM%
由于相同的原因导致打印三个相同的数字,而!RANDOM!
通常打印三个不同的数字。
也可以看看:
if errorlevel number
并if not errorlevel number
在命令块内工作!
动态环境变量只能通过默认启用的命令扩展来访问。否则,Windows 命令处理器会模拟COMMAND.COM
MS-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修改动态变量的值,因为它会导致使用优先于动态变量的动态变量的名称 定义环境变量。
推荐阅读
- django - 在具有嵌套资源的 Tastypie 中,对于给定的 url,获取视图函数名称的最佳方法是什么?
- php - Yii:尽管安装了 memcache/memcached,CMemCache 仍需要加载 PHP memcache 扩展
- python - Python pkg_resources 找不到模块
- postgresql - INSERT 到只有一个串行列的表实际上并没有插入任何东西
- server - 是否可以在 WSL/Linux 上设置 DHCP 服务器?
- sql - ORACLE Pro*C/C++ 代码未对结果进行四舍五入
- sql - 查找已注册特定学生完成的所有课程的学生
- python - 包括记录器类的对象清单类覆盖字典条目
- sql - 使用内部连接更新表
- redirect - 在 Razor 页面中重定向到带有 ID 的页面