首先,我将指出一些使这个问题难以完美解决的问题。然后我将介绍我能想到的最防弹的解决方案。
在本次讨论中,我将使用小写的路径来表示文件系统中的单个文件夹路径,并使用大写的 PATH 来表示 PATH 环境变量。
从实际的角度来看,大多数人想知道 PATH 是否包含给定路径的逻辑等价物,而不是 PATH 是否包含给定路径的精确字符串匹配。这可能是有问题的,因为:
路径中的尾随\
是可选的
大多数路径在有尾随和没有尾随的情况下都同样有效\
。无论哪种方式,路径在逻辑上都指向相同的位置。PATH 经常混合有尾随和不带尾随的路径\
。这可能是在 PATH 中搜索匹配项时最常见的实际问题。
- 有一个例外:相对路径(指C盘当前工作目录)与(指C盘根目录
C:
)有很大不同C:\
一些路径具有备用短名称
任何不符合旧 8.3 标准的路径都有一个符合标准的备用短形式。这是我经常看到的另一个 PATH 问题,尤其是在商业环境中。
Windows 接受/
和\
作为路径中的文件夹分隔符。
这种情况并不常见,但是可以使用/
而不是指定路径,\
并且它在 PATH 中(以及在许多其他 Windows 上下文中)可以正常工作
Windows 将连续的文件夹分隔符视为一个逻辑分隔符。
C:\FOLDER\\ 和 C:\FOLDER\ 是等价的。在处理路径时,这实际上在许多情况下都有帮助,因为开发人员通常可以附加\
到路径而无需检查尾随是否\
已经存在。但是,如果尝试执行精确的字符串匹配,这显然会导致问题。
- 例外:不仅是
C:
,不同于C:\
,而且C:\
(有效路径),不同于C:\\
(无效路径)。
Windows 从文件和目录名称中修剪尾随的点和空格。
"C:\test. "
相当于"C:\test"
。
当前.\
和父..\
文件夹说明符可能出现在路径中不太可能在现实生活中
看到,但类似于C:\.\parent\child\..\.\child\
C:\parent\child
路径可以选择用双引号括起来。
路径通常用引号括起来,以防止出现特殊字符,例如<space>
,
;
^
&
=
. 实际上,任何数量的引号都可以出现在路径之前、之中和/或之后。除了防止特殊字符之外,它们被 Windows 忽略。除非路径包含 a ;
,否则在 PATH 中永远不需要引号,但引号可能永远存在。
路径可以是完全限定的或相对的。
完全限定路径指向文件系统中的一个特定位置。相对路径位置根据当前工作卷和目录的值而变化。相对路径有三种主要风格:
D:
相对于卷 D 的当前工作目录:
\myPath
是相对于当前工作量的(可以是 C:, D: 等)
myPath
相对于当前工作卷和目录
在 PATH 中包含相对路径是完全合法的。这在 Unix 世界中很常见,因为 Unix 默认不搜索当前目录,因此 Unix PATH 通常包含.\
. 但是 Windows 默认会搜索当前目录,因此在 Windows PATH 中相对路径很少见。
因此,为了可靠地检查 PATH 是否已经包含路径,我们需要一种将任何给定路径转换为规范(标准)形式的方法。FOR 变量和参数扩展使用的~s
修饰符是解决问题 1 - 6 和部分解决问题 7 的简单方法。~s
修饰符删除封闭引号,但保留内部引号。问题 7 可以通过在比较之前从所有路径中显式删除引号来完全解决。请注意,如果路径实际上不存在,则~s
修饰符不会将 附加\
到路径,也不会将路径转换为有效的 8.3 格式。
问题~s
在于它将相对路径转换为完全限定的路径。这对于问题 8 来说是有问题的,因为相对路径永远不应该与完全限定的路径匹配。我们可以使用 FINDSTR 正则表达式将路径分类为完全限定路径或相对路径。正常的完全限定路径必须以<letter>:<separator>
but not开头<letter>:<separator><separator>
,其中 <separator> 是\
or /
。UNC 路径始终是完全限定的,并且必须以\\
. 在比较完全合格的路径时,我们使用~s
修饰符。在比较相对路径时,我们使用原始字符串。最后,我们从不将完全限定路径与相对路径进行比较。此策略为问题 8 提供了一个很好的实用解决方案。唯一的限制是两个逻辑等效的相对路径可能被视为不匹配,但这是一个小问题,因为相对路径在 Windows PATH 中很少见。
还有一些额外的问题使这个问题复杂化:
9)处理包含特殊字符的 PATH 时,正常扩展不可靠。
特殊字符不需要在 PATH 中引用,但可以。所以像 PATH 一样
C:\THIS & THAT;"C:\& THE OTHER THING"
是完全有效的,但它不能使用简单的扩展安全地扩展,因为两者"%PATH%"
都会%PATH%
失败。
10)路径定界符在路径名中也有效
A;
用于在 PATH 中定界路径,但;
也可以是路径中的有效字符,在这种情况下必须引用路径。这会导致解析问题。
jeb 在'Pretty print' windows %PATH% 变量中解决了问题 9 和 10 - 如何在 ';' 上拆分 在 CMD 外壳中
因此,我们可以将~s
修饰符和路径分类技术与我对 jeb 的 PATH 解析器的变体结合起来,以获得这种几乎防弹的解决方案,用于检查给定路径是否已存在于 PATH 中。该函数可以在批处理文件中包含和调用,也可以独立运行并作为其自己的 inPath.bat 批处理文件调用。它看起来有很多代码,但其中一半以上是注释。
@echo off
:inPath pathVar
::
:: Tests if the path stored within variable pathVar exists within PATH.
::
:: The result is returned as the ERRORLEVEL:
:: 0 if the pathVar path is found in PATH.
:: 1 if the pathVar path is not found in PATH.
:: 2 if pathVar is missing or undefined or if PATH is undefined.
::
:: If the pathVar path is fully qualified, then it is logically compared
:: to each fully qualified path within PATH. The path strings don't have
:: to match exactly, they just need to be logically equivalent.
::
:: If the pathVar path is relative, then it is strictly compared to each
:: relative path within PATH. Case differences and double quotes are
:: ignored, but otherwise the path strings must match exactly.
::
::------------------------------------------------------------------------
::
:: Error checking
if "%~1"=="" exit /b 2
if not defined %~1 exit /b 2
if not defined path exit /b 2
::
:: Prepare to safely parse PATH into individual paths
setlocal DisableDelayedExpansion
set "var=%path:"=""%"
set "var=%var:^=^^%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:<=^<%"
set "var=%var:>=^>%"
set "var=%var:;=^;^;%"
set var=%var:""="%
set "var=%var:"=""Q%"
set "var=%var:;;="S"S%"
set "var=%var:^;^;=;%"
set "var=%var:""="%"
setlocal EnableDelayedExpansion
set "var=!var:"Q=!"
set "var=!var:"S"S=";"!"
::
:: Remove quotes from pathVar and abort if it becomes empty
set "new=!%~1:"=!"
if not defined new exit /b 2
::
:: Determine if pathVar is fully qualified
echo("!new!"|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& set "abs=1" || set "abs=0"
::
:: For each path in PATH, check if path is fully qualified and then do
:: proper comparison with pathVar.
:: Exit with ERRORLEVEL 0 if a match is found.
:: Delayed expansion must be disabled when expanding FOR variables
:: just in case the value contains !
for %%A in ("!new!\") do for %%B in ("!var!") do (
if "!!"=="" endlocal
for %%C in ("%%~B\") do (
echo(%%B|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& (if %abs%==1 if /i "%%~sA"=="%%~sC" exit /b 0) ^
|| (if %abs%==0 if /i "%%~A"=="%%~C" exit /b 0)
)
)
:: No match was found so exit with ERRORLEVEL 1
exit /b 1
该函数可以这样使用(假设批处理文件名为 inPath.bat):
set test=c:\mypath
call inPath test && (echo found) || (echo not found)
通常,检查路径是否存在于 PATH 中的原因是,如果路径不存在,则要附加该路径。这通常只需使用类似
path %path%;%newPath%
. 但是第 9 期证明了这是不可靠的。
另一个问题是如何在函数结束时通过 ENDLOCAL 屏障返回最终的 PATH 值,特别是如果可以在启用或禁用延迟扩展的情况下调用该函数。!
如果启用延迟扩展,任何未转义的值都会损坏该值。
使用 jeb 在这里发明的惊人的安全返回技术解决了这些问题:http: //www.dostips.com/forum/viewtopic.php? p=6930#p6930
@echo off
:addPath pathVar /B
::
:: Safely appends the path contained within variable pathVar to the end
:: of PATH if and only if the path does not already exist within PATH.
::
:: If the case insensitive /B option is specified, then the path is
:: inserted into the front (Beginning) of PATH instead.
::
:: If the pathVar path is fully qualified, then it is logically compared
:: to each fully qualified path within PATH. The path strings are
:: considered a match if they are logically equivalent.
::
:: If the pathVar path is relative, then it is strictly compared to each
:: relative path within PATH. Case differences and double quotes are
:: ignored, but otherwise the path strings must match exactly.
::
:: Before appending the pathVar path, all double quotes are stripped, and
:: then the path is enclosed in double quotes if and only if the path
:: contains at least one semicolon.
::
:: addPath aborts with ERRORLEVEL 2 if pathVar is missing or undefined
:: or if PATH is undefined.
::
::------------------------------------------------------------------------
::
:: Error checking
if "%~1"=="" exit /b 2
if not defined %~1 exit /b 2
if not defined path exit /b 2
::
:: Determine if function was called while delayed expansion was enabled
setlocal
set "NotDelayed=!"
::
:: Prepare to safely parse PATH into individual paths
setlocal DisableDelayedExpansion
set "var=%path:"=""%"
set "var=%var:^=^^%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:<=^<%"
set "var=%var:>=^>%"
set "var=%var:;=^;^;%"
set var=%var:""="%
set "var=%var:"=""Q%"
set "var=%var:;;="S"S%"
set "var=%var:^;^;=;%"
set "var=%var:""="%"
setlocal EnableDelayedExpansion
set "var=!var:"Q=!"
set "var=!var:"S"S=";"!"
::
:: Remove quotes from pathVar and abort if it becomes empty
set "new=!%~1:"^=!"
if not defined new exit /b 2
::
:: Determine if pathVar is fully qualified
echo("!new!"|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& set "abs=1" || set "abs=0"
::
:: For each path in PATH, check if path is fully qualified and then
:: do proper comparison with pathVar. Exit if a match is found.
:: Delayed expansion must be disabled when expanding FOR variables
:: just in case the value contains !
for %%A in ("!new!\") do for %%B in ("!var!") do (
if "!!"=="" setlocal disableDelayedExpansion
for %%C in ("%%~B\") do (
echo(%%B|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& (if %abs%==1 if /i "%%~sA"=="%%~sC" exit /b 0) ^
|| (if %abs%==0 if /i %%A==%%C exit /b 0)
)
)
::
:: Build the modified PATH, enclosing the added path in quotes
:: only if it contains ;
setlocal enableDelayedExpansion
if "!new:;=!" neq "!new!" set new="!new!"
if /i "%~2"=="/B" (set "rtn=!new!;!path!") else set "rtn=!path!;!new!"
::
:: rtn now contains the modified PATH. We need to safely pass the
:: value accross the ENDLOCAL barrier
::
:: Make rtn safe for assignment using normal expansion by replacing
:: % and " with not yet defined FOR variables
set "rtn=!rtn:%%=%%A!"
set "rtn=!rtn:"=%%B!"
::
:: Escape ^ and ! if function was called while delayed expansion was enabled.
:: The trailing ! in the second assignment is critical and must not be removed.
if not defined NotDelayed set "rtn=!rtn:^=^^^^!"
if not defined NotDelayed set "rtn=%rtn:!=^^^!%" !
::
:: Pass the rtn value accross the ENDLOCAL barrier using FOR variables to
:: restore the % and " characters. Again the trailing ! is critical.
for /f "usebackq tokens=1,2" %%A in ('%%^ ^"') do (
endlocal & endlocal & endlocal & endlocal & endlocal
set "path=%rtn%" !
)
exit /b 0