windows - 如何检查 %PATH% 中是否存在目录?

标签 windows batch-file

如何检查 PATH 环境变量中是否已经存在目录?这是一个开始。不过,我对下面的代码所做的只是回显 %PATH% 中的第一个目录。由于这是一个 FOR 循环,您会认为它会枚举 %PATH% 中的所有目录,但它只获取第一个。

有没有更好的方法来做到这一点?像在 %PATH% 变量上运行的 find 或 findstr 之类的东西?我只想检查 %PATH% 中的目录列表中是否存在一个目录,以避免添加可能已经存在的内容。

FOR /F "delims=;" %%P IN ("%PATH%") DO (
    @ECHO %%~P
)

最佳答案

首先我将指出一些使这个问题难以完美解决的问题。然后我将介绍我能想出的最防弹的解决方案。

在本次讨论中,我将使用小写路径表示文件系统中的单个文件夹路径,并使用大写 PATH 表示 PATH 环境变量。

从实际的角度来看,大多数人想知道 PATH 是否包含给定路径的逻辑等效项,而不是 PATH 是否包含给定路径的精确字符串匹配。这可能有问题,因为:

  • 尾随 \在路径中是可选的
    大多数路径在使用和不使用尾随 \ 时同样有效.无论哪种方式,路径在逻辑上都指向相同的位置。 PATH 经常包含带有和不带有尾随 \ 的路径的混合。 .这可能是在 PATH 中搜索匹配项时最常见的实际问题。
  • 有一个异常(exception):相对路径 C: (意思是 C 盘的当前工作目录)与 C:\ 非常不同(即C盘根目录)
  • 某些路径具有备用短名称
    任何不符合旧 8.3 标准的路径都有一个符合标准的替代短格式。这是我经常看到的另一个 PATH 问题,尤其是在商业环境中。
  • Windows 接受 /\作为路径中的文件夹分隔符。
    这并不常见,但可以使用 / 指定路径。而不是 \并且它会在 PATH 中正常运行(以及在许多其他 Windows 上下文中)
  • Windows 将连续的文件夹分隔符视为一个逻辑分隔符。
    C:\FOLDER\\和 C:\FOLDER\是等价的。这实际上在处理路径时在许多情况下都有帮助,因为开发人员通常可以附加 \到路径而无需检查尾随 \已经存在。但是,如果尝试执行精确的字符串匹配,这显然会导致问题。
  • 异常(exception):不仅是 C: ,不同于 C:\ ,但是 C:\ (有效路径),不同于 C:\\ (无效路径)。
  • Windows 从文件和目录名称中删除尾随的点和空格。 "C:\test. "相当于 "C:\test" .
  • 当前.\和 parent ..\文件夹说明符可能出现在路径
    在现实生活中不太可能看到,但类似于 C:\.\parent\child\..\.\child\相当于 C:\parent\child
  • 可以选择将路径括在双引号内。
    路径通常用引号括起来以防止特殊字符,如 <space> , ; ^ & = .实际上,任何数量的引号都可以出现在路径之前、之内和/或之后。 Windows 会忽略它们,除非是为了防止特殊字符。除非路径包含 ;,否则 PATH 中永远不需要引号,但引号可能永远存在。
  • 路径可以是完全限定的或相对的。
    完全限定的路径正好指向文件系统中的一个特定位置。相对路径位置根据当前工作卷和目录的值而变化。相对路径有三种主要风格:
  • D: 相对于卷 D 的当前工作目录:
  • \myPath 是相对于当前工作量(可以是 C:、D: 等)
  • myPath 相对于当前工作卷和目录

  • 在 PATH 中包含相对路径是完全合法的。这在 Unix 世界中很常见,因为 Unix 默认不搜索当前目录,所以 Unix PATH 通常会包含 .\ .但是 Windows 默认会搜索当前目录,因此 Windows PATH 中很少有相对路径。
    因此,为了可靠地检查 PATH 是否已经包含路径,我们需要一种将任何给定路径转换为规范(标准)形式的方法。 ~s FOR 变量和参数扩展使用的修饰符是解决问题 1 - 6 和部分解决问题 7 的简单方法。 ~s修饰符删除封闭引号,但保留内部引号。通过在比较之前从所有路径中明确删除引号,可以完全解决问题 7。请注意,如果路径实际上不存在,则 ~s修饰符不会附加 \到路径,也不会将路径转换为有效的 8.3 格式。
    ~s 的问题是否将相对路径转换为完全限定的路径。这对于问题 8 来说是有问题的,因为相对路径不应该与完全限定的路径匹配。我们可以使用 FINDSTR 正则表达式将路径分类为完全限定的或相对的。正常的完全限定路径必须以 <letter>:<separator> 开头但不是 <letter>:<separator><separator> ,其中 \/ . UNC 路径始终是完全限定的,并且必须以 \\ 开头.在比较完全限定的路径时,我们使用 ~s修饰符。在比较相对路径时,我们使用原始字符串。最后,我们从不将完全限定的路径与相对路径进行比较。此策略为问题 8 提供了一个很好的实用解决方案。唯一的限制是两个逻辑上等效的相对路径可能被视为不匹配,但这是一个次要问题,因为相对路径在 Windows PATH 中很少见。

    还有一些其他问题使这个问题复杂化:

    9) 处理包含特殊字符的 PATH 时,正常扩展是不可靠的。
    特殊字符不需要在 PATH 中引用,但可以引用。所以一个 PATH 像C:\THIS & THAT;"C:\& THE OTHER THING"是完全有效的,但不能使用简单的扩展安全地扩展它,因为 "%PATH%"%PATH%将失败。

    10) 路径分隔符在路径名 中也有效
    一个 ;用于在 PATH 中分隔路径,但 ;也可以是路径中的有效字符,在这种情况下必须引用路径。这会导致解析问题。

    jeb 在 'Pretty print' windows %PATH% variable - how to split on ';' in CMD shell 解决了问题 9 和 10

    所以我们可以结合~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
    

    关于windows - 如何检查 %PATH% 中是否存在目录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/141344/

    相关文章:

    c# - Xamarin Studio,GTK 2.0 C# 应用程序,运行时错误,无法加载 libglib-2.0-0.dll

    android - 亚行没有启动

    windows - 将 %USERNAME% 中的空格替换为下划线

    batch-file - 批处理文件 - 忽略子批处理文件中的 CLS

    powershell - 更改 Windows 10 屏幕刷新率。 60 则 59,59 则 60

    C++如何从DLL(windows)的文件名中获取进程ID?

    windows - 什么语法将检查是否定义了包含空格的变量名?

    python - 如何通过 Python 自定义文件夹的图标?

    mysql - 批处理文件 CD,然后运行 ​​MySQL 查询

    windows - 批处理 - 无法打印字符串开头的空格