Сначала я укажу на ряд проблем, которые затрудняют решение этой проблемы. Затем я представлю самое пуленепробиваемое решение, которое мне удалось найти.
В этом обсуждении я буду использовать путь в нижнем регистре для представления пути к одной папке в файловой системе, а путь в верхнем регистре для представления переменной среды 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, за исключением защиты от специальных символов. Кавычки никогда не требуются в переменной PATH, если путь не содержит ;
, но, тем не менее, кавычки могут присутствовать.
Путь может быть полностью определенным или относительным.
Полный путь указывает на одно конкретное место в файловой системе. Относительное расположение пути изменяется в зависимости от значения текущих рабочих томов и каталогов. Существует три основных варианта относительных путей:
D:
относительно текущего рабочего каталога тома D:
\myPath
относительно текущего рабочего объема (может быть C :, D: и т. Д.)
myPath
относительно текущего рабочего объема и каталога
Вполне законно включать относительный путь в PATH. Это очень распространено в мире Unix, потому что Unix не ищет текущий каталог по умолчанию, поэтому PATH Unix часто будет содержать .\
. Но Windows выполняет поиск в текущем каталоге по умолчанию, поэтому относительные пути в пути к Windows редко встречаются.
Итак, чтобы надежно проверить, содержит ли 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, который содержит специальные символы.
Специальные символы не нужно заключать в кавычки, но они могут быть. Так что ПУТЬ как
C:\THIS & THAT;"C:\& THE OTHER THING"
совершенно допустимо, но его нельзя безопасно развернуть с помощью простого расширения, поскольку оба "%PATH%"
и %PATH%
завершатся с ошибкой.
10) Разделитель пути также допустим в имени пути
;
используется для разграничения путей внутри PATH, но ;
также может быть допустимым символом в пути, и в этом случае путь должен быть заключен в кавычки. Это вызывает проблему разбора.
Джеб решил обе проблемы 9 и 10 в Переменная «Pretty print» Windows% PATH% - как разделить на «;» в оболочке 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 демонстрирует, как это ненадежно.
Другая проблема заключается в том, как вернуть окончательное значение PATH через барьер ENDLOCAL в конце функции, особенно если функцию можно вызывать с включенным или отключенным отложенным расширением. Любой неэкранированный !
повредит значение, если включено отложенное расширение.
Эти проблемы решаются с помощью удивительной техники безопасного возврата, изобретенной здесь Джебом: 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