Как проверить, существует ли каталог в% PATH%? - PullRequest
58 голосов
/ 26 сентября 2008

Как проверить, существует ли каталог в переменной среды PATH? Вот начало. Все, что мне удалось сделать с помощью приведенного ниже кода, - это отобразить первый каталог в% PATH%. Поскольку это цикл FOR, можно подумать, что он перечислит все каталоги в% PATH%, но он получит только первый.

Есть ли лучший способ сделать это? Что-то вроде find или findstr, работающего с переменной% PATH%? Я просто хотел бы проверить, существует ли каталог в списке каталогов в% PATH%, чтобы избежать добавления чего-то, что уже может быть там.

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

Ответы [ 21 ]

87 голосов
/ 08 ноября 2011

Сначала я укажу на ряд проблем, которые затрудняют решение этой проблемы. Затем я представлю самое пуленепробиваемое решение, которое мне удалось найти.

В этом обсуждении я буду использовать путь в нижнем регистре для представления пути к одной папке в файловой системе, а путь в верхнем регистре для представления переменной среды PATH.

С практической точки зрения большинство людей хотят знать, содержит ли PATH логический эквивалент заданного пути, а не содержит ли PATH точное совпадение строки заданного пути. Это может быть проблематично, потому что:

  1. Трейлинг \ необязателен в пути
    Большинство путей работают одинаково хорошо как с трейлингом \, так и без него. Путь логически указывает на одно и то же место в любом случае. ПУТЬ часто имеет смесь путей как с, так и без трейлинга \. Это, вероятно, самая распространенная практическая проблема при поиске совпадений в PATH.

    • Существует одно исключение: относительный путь C: (имеется в виду текущий рабочий каталог диска C) очень отличается от C:\ (имеется в виду корневой каталог диска C).

  2. Некоторые пути имеют альтернативные короткие имена
    Любой путь, который не соответствует старому стандарту 8.3, имеет альтернативную краткую форму, которая соответствует стандарту. Это еще одна проблема PATH, с которой я сталкиваюсь с определенной частотой, особенно в бизнес-настройках.

  3. Windows принимает и / и \ в качестве разделителей папок в пути.
    Это не часто встречается, но путь можно указать, используя / вместо \, и он будет нормально работать в PATH (как и во многих других контекстах Windows)

  4. Windows рассматривает последовательные разделители папок как один логический разделитель.
    C: \ FOLDER \\ и C: \ FOLDER \ эквивалентны. Это действительно помогает во многих контекстах при работе с путем, потому что разработчик обычно может добавить \ к пути, не потрудившись проверить, существует ли конечный \. Но это, очевидно, может вызвать проблемы при попытке выполнить точное совпадение строк.

    • Исключения: Не только C:, отличается от C:\, но C:\ (допустимый путь), отличается от C:\\ (неверный путь).

  5. Windows удаляет конечные точки и пробелы из имен файлов и каталогов.
    "C:\test. " эквивалентно "C:\test".

  6. Текущие .\ и родительские ..\ описатели папок могут отображаться в пути
    Вряд ли это можно увидеть в реальной жизни, но что-то вроде C:\.\parent\child\..\.\child\ эквивалентно C:\parent\child

  7. Путь может быть заключен в двойные кавычки.
    Путь часто заключен в кавычки для защиты от специальных символов, таких как <space> , ; ^ & =. На самом деле любое количество кавычек может появляться до, внутри и / или после пути. Они игнорируются Windows, за исключением защиты от специальных символов. Кавычки никогда не требуются в переменной PATH, если путь не содержит ;, но, тем не менее, кавычки могут присутствовать.

  8. Путь может быть полностью определенным или относительным.
    Полный путь указывает на одно конкретное место в файловой системе. Относительное расположение пути изменяется в зависимости от значения текущих рабочих томов и каталогов. Существует три основных варианта относительных путей:

    • 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
36 голосов
/ 26 сентября 2008

Я давно не программировал пакетные файлы, но:

echo ;%PATH%; | find /C /I ";<string>;"

должен выдавать 0, если строка не найдена, и 1 или более, если она есть.

EDIT: добавлен флаг без учета регистра, благодаря Panos.

21 голосов
/ 01 апреля 2010

Еще один способ проверить, находится ли что-то в пути, - выполнить невинный исполняемый файл, который не потерпит неудачу, если он там есть, и проверить результат. Например, следующий фрагмент кода проверяет, находится ли maven в пути:

mvn --help > NUL 2> NUL 
if errorlevel 1 goto mvnNotInPath

Поэтому я пытаюсь запустить mvn --help , игнорировать вывод (на самом деле не хочу видеть помощь, если maven есть) (> NUL), а также не отображать ошибку сообщение, если maven не был найден (2> NUL).

8 голосов
/ 28 октября 2013

После прочтения ответов здесь я думаю, что могу представить новую точку зрения: если целью этого вопроса является узнать , существует ли путь к определенному исполняемому файлу в %PATH% и если нет вставьте его (и это единственная причина сделать это, я думаю), тогда это может быть решено немного по-другому: «Как проверить, существует ли каталог определенной исполняемой программы в% PATH % "? Этот вопрос может быть легко решен следующим образом:

for %%p in (programname.exe) do set "progpath=%%~$PATH:p"
if not defined progpath (
   rem The path to programname.exe don't exist in PATH variable, insert it:
   set "PATH=%PATH%;C:\path\to\progranname"
)

Если вы не знаете расширения исполняемого файла, просто просмотрите все из них:

set "progpath="
for %%e in (%PATHEXT%) do (
   if not defined progpath (
      for %%p in (programname.%%e) do set "progpath=%%~$PATH:p"
   )
)
6 голосов
/ 01 октября 2008

Используя for и delims, вы не можете захватить произвольное количество полей (как Адам также указал ), поэтому вам необходимо вместо этого используйте технику зацикливания. Следующий командный сценарий перечислит каждый путь в переменной среды PATH в отдельной строке:

@echo off 
setlocal 
if "%~1"=="" (
    set PATHQ=%PATH%
) else (
    set PATHQ=%~1 ) 
:WHILE
    if "%PATHQ%"=="" goto WEND
    for /F "delims=;" %%i in ("%PATHQ%") do echo %%i
    for /F "delims=; tokens=1,*" %%i in ("%PATHQ%") do set PATHQ=%%j
    goto WHILE 
:WEND

Имитирует классическую , в то время как wend конструкция, встречающаяся во многих языках программирования. Имея это в виду, вы можете использовать что-то вроде findstr для последующей фильтрации и поиска определенного пути. Например, если вы сохранили вышеупомянутый скрипт в файле с именем tidypath.cmd, то вот как вы можете передать по каналу findstr, ища пути в каталоге стандартных программ (используя регистронезависимое совпадение):

> tidypath | findstr /i "%ProgramFiles%"
4 голосов
/ 07 апреля 2011

Это будет искать точное, но без учета регистра совпадение, так что следите за любыми обратными слешами и т. Д.

for %P in ("%path:;=";"%") do @if /i %P=="PATH_TO_CHECK" echo %P exists in PATH

или в пакетном файле (например, checkpath.bat), который принимает аргумент:

@for %%P in ("%path:;=";"%") do @if /i %%P=="%~1" echo %%P exists in PATH

В последнем виде можно назвать, например, checkpath "%ProgramFiles%" чтобы узнать, существует ли указанный путь в PATH.

Обратите внимание, что в данной реализации предполагается, что в одном элементе пути нет точек с запятой или кавычек.

2 голосов
/ 14 марта 2011

Добавить каталог в PATH, если он еще не существует:

set myPath=c:\mypath
For /F "Delims=" %%I In ('echo %PATH% ^| find /C /I "%myPath%"') Do set pathExists=%%I 2>Nul
If %pathExists%==0 (set PATH=%myPath%;%PATH%)
2 голосов
/ 27 сентября 2008

Я взял вашу реализацию, используя цикл для , и расширил ее до чего-то, что повторяет все элементы пути. Каждая итерация цикла for удаляет первый элемент пути (% p) из всего пути (содержится в% q и% r).

@echo off
SET MYPATHCOPY=%PATH%

:search
for /f "delims=; tokens=1,2*" %%p in ("%MYPATHCOPY%") do (
   @echo %%~p
   SET MYPATHCOPY=%%~q;%%~r
)

if "%MYPATHCOPY%"==";" goto done;
goto search;

:done

Пример вывода:

Z:\>path.bat
C:\Program Files\Microsoft DirectX SDK (November 2007)\Utilities\Bin\x86
c:\program files\imagemagick-6.3.4-q16
C:\WINDOWS\system32
C:\WINDOWS
C:\SFU\common\
c:\Program Files\Debugging Tools for Windows
C:\Program Files\Nmap
2 голосов
/ 23 июля 2011

Вы также можете использовать замену подстроки, чтобы проверить наличие подстроки. Здесь я удаляю кавычки для создания PATH_NQ, затем удаляю «c: \ mydir» из PATH_NQ и сравниваю его с оригиналом, чтобы увидеть, изменилось ли что-нибудь:

set PATH_NQ=%PATH:"=%
if not "%PATH_NQ%"=="%PATH_NQ:c:\mydir=%" goto already_in_path
set PATH=%PATH%;c:\mydir
:already_in_path
2 голосов
/ 11 октября 2011

Я скомбинировал некоторые из приведенных выше ответов, чтобы придумать это, чтобы гарантировать, что заданная запись пути существует в точности так, как задано , с максимально возможной краткостью и отсутствием ненужного эха в командной строке.

set myPath=<pathToEnsure | %1>
echo ;%PATH%; | find /C /I ";%myPath%;" >nul
if %ERRORLEVEL% NEQ 0 set PATH=%PATH%;%myPath%
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...