Ответ 1
Сначала я укажу на ряд проблем, которые затрудняют решение этой проблемы. Тогда я представим самое пуленепробиваемое решение, с которым я смог придумать.
Для этого обсуждения я буду использовать строчный путь для представления одного пути к папке в файловой системе, а верхний регистр 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, кроме как для защиты от специальных символов. Кавычки никогда не требуются в 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) Ограничение пути также допустимо в пределах имени пути
A ;
используется для разграничения путей в PATH, но ;
также может быть допустимым символом в пути, и в этом случае путь должен быть указан. Это вызывает проблему синтаксического анализа.
jeb решила обе проблемы 9 и 10 в 'Довольно печатать' окна% PATH% variable - как разбить на ';' в оболочке 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-барьеру в конце функции, особенно если функция может быть вызвана с задержкой расширения, включен или отключен. Любой unescaped !
приведет к повреждению значения, если включено замедленное расширение.
Эти проблемы решаются с помощью удивительной безопасной технологии возврата, которую изобрел здесь 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