Je vais d'abord vous montrer un certain nombre de questions qui font de ce problème difficile à résoudre parfaitement. Ensuite, je vais vous présenter le plus à l'épreuve des balles solution que j'ai été en mesure de venir avec.
Pour cette discussion, je vais utiliser les minuscules chemin pour représenter un seul chemin d'accès au dossier dans le système de fichiers, et en majuscules CHEMIN pour représenter la variable d'environnement PATH.
À partir d'un point de vue pratique, la plupart des gens veulent savoir si le CHEMIN contient l'équivalent logique d'un chemin donné, pas de savoir si le CHEMIN contient une chaîne de caractères exacte correspondance d'un chemin donné. Cela peut être problématique parce que:
-
Le trailing \
est facultatif dans un chemin d'accès
La plupart des chemins de travailler aussi bien à la fois avec et sans le dernier \
. Le chemin logiquement des points à l'emplacement même de toute façon. Le CHEMIN a souvent un mélange de chemins à la fois avec et sans le dernier \
. C'est probablement le plus commun de la pratique, lors de la recherche d'un CHEMIN pour un match.
- Il y a une exception: Le chemin d'accès relatif
C:
(ce qui signifie le répertoire de travail courant du lecteur C) est très différent de C:\
(ce qui signifie le répertoire racine du lecteur C)
Certains chemins ont alterné des noms courts
Tout chemin qui ne respectent pas les vieux 8.3 standard a une autre forme courte qui ne respectent pas la norme. C'est un autre problème de CHEMIN d'accès que j'ai vu avec une certaine fréquence, en particulier dans les milieux d'affaires.
Windows accepte, à l' /
et \
comme séparateurs de dossiers à l'intérieur d'un chemin.
Cela ne se voit pas très souvent, mais un chemin d'accès peut être spécifié à l'aide d' /
au lieu de \
et il fonctionnera bien dans le CHEMIN (ainsi que dans de nombreuses autres Fenêtres contextes)
-
Windows considère consécutives dossier des séparateurs d'une logique de séparation.
C:\FOLDER\\ et C:\FOLDER\ sont équivalentes. Cette aide réellement dans de nombreux contextes, lorsque vous traitez avec un chemin d'accès, car un développeur peut généralement ajouter \
d'un chemin sans se soucier de vérifier si la fuite \
existe déjà. Mais de toute évidence, cela peut causer des problèmes si vous essayez d'effectuer une exacte correspondance de chaînes.
- Exceptions: Non seulement est -
C:
, différente C:\
, mais C:\
(un chemin d'accès valide), est différent de celui d' C:\\
(un chemin d'accès non valide).
Windows garnitures de points de fuite et les espaces de noms de fichier et répertoire.
"C:\test. "
est équivalent à "C:\test"
.
Le courant .\
et parent ..\
le dossier de prescripteurs peuvent apparaître à l'intérieur d'un chemin
Peu de chances d'être vu dans la vraie vie, mais quelque chose comme C:\.\parent\child\..\.\child\
est équivalent à C:\parent\child
Un chemin d'accès peut éventuellement être entourés par des guillemets doubles.
Un chemin est souvent mis entre guillemets pour se protéger contre les caractères spéciaux comme <space>
,
;
^
&
=
. En fait n'importe quel nombre de citations peuvent apparaître avant, à l'intérieur, et/ou après le chemin d'accès. Ils sont ignorés par les Fenêtres, sauf pour les fins de la protection contre les caractères spéciaux. Les guillemets ne sont jamais tenus au sein de CHEMIN à moins d'un chemin d'accès contient un ;
, mais les citations peuvent être présents jamais-le-moins.
-
Un chemin peut être complet ou relatif.
Le chemin d'accès complet des points exactement à un emplacement spécifique dans le système de fichiers. Un chemin d'accès relatif modifications de l'emplacement en fonction de la valeur du courant de travail des volumes et des répertoires. Il existe trois principaux arômes des chemins relatifs:
-
D:
est relatif au répertoire de travail courant du volume D:
-
\myPath
est par rapport à l'actuel volume de travail (C:, D:, etc.)
-
myPath
est par rapport à l'actuel volume de travail et le répertoire
Il est parfaitement légal d'inclure un chemin d'accès relatif à l'intérieur de CHEMIN. Ceci est très fréquent dans le monde Unix, parce qu'Unix n'est pas de rechercher le répertoire courant par défaut, de sorte qu'un Unix CHEMIN contiennent souvent de l' .\
. Mais Windows n'rechercher le répertoire courant par défaut, de sorte que les chemins relatifs sont rares dans un CHEMIN d'accès Windows.
C'est dans le but de vérifier de manière fiable si le CHEMIN contient déjà un chemin, nous avons besoin d'un moyen de convertir n'importe quel chemin d'accès donné dans un canoniques (standard). L' ~s
modificateur utilisé POUR la variable et de l'argument de l'expansion est une méthode simple qui aborde les questions 1 à 6, et répond en partie à la question 7. L' ~s
modificateur supprime les entourant de guillemets, mais préserve l'intérieur des guillemets. Question 7 peut être complètement résolu par la suppression explicite des citations de tous les chemins avant la comparaison. Notez que si un chemin d'accès n'existe pas physiquement alors l' ~s
modificateur sera pas ajouter de l' \
pour le chemin, ni de convertir le chemin d'accès dans un valide au format 8.3.
Le problème avec ~s
est-il convertit les chemins relatifs dans les chemins d'accès complets. Cette situation est problématique pour la Question 8, car un chemin relatif ne devrait jamais correspondre à un chemin d'accès complet. Nous pouvons utiliser FINDSTR des expressions régulières pour classer un chemin que soit pleinement qualifié ou d'un proche. Une normale de chemin d'accès complet doit commencer par <letter>:<separator>
mais pas <letter>:<separator><separator>
, où <séparateur> est \
ou /
. Les chemins d'accès UNC sont toujours pleinement qualifié et doit commencer par \\
. Lorsque l'on compare les chemins d'accès complets, nous utilisons l' ~s
modificateur. Lorsque l'on compare les chemins relatifs, nous utilisons les premières chaînes. Enfin, nous n'avons jamais comparer un chemin d'accès complet à un chemin d'accès relatif. Cette stratégie fournit une bonne solution pratique pour le Numéro 8. La seule limitation est deux qui sont logiquement équivalentes les chemins d'accès relatifs pouvait être considéré comme ne correspondant pas, mais c'est une préoccupation mineure parce que les chemins relatifs sont rares dans un CHEMIN d'accès Windows.
Il y a quelques questions supplémentaires qui compliquent ce problème:
9) Normal extension n'est pas fiable lorsque vous traitez avec un CHEMIN d'accès contient des caractères spéciaux.
Les caractères spéciaux ne doivent pas être cité dans le CHEMIN, mais ils pourraient l'être. Donc un CHEMIN comme
C:\THIS & THAT;"C:\& THE OTHER THING"
est parfaitement valide, mais il ne peut pas être élargi de façon sécuritaire à l'aide de simples expansion parce que les deux "%PATH%"
et %PATH%
échouent.
10) Le délimiteur de chemin est aussi valable à l'intérieur d'un nom de chemin d'accès
Un ;
est utilisé pour délimiter les chemins de CHEMIN, mais ;
peut également être un caractère valide dans un chemin, auquel cas le chemin d'accès doit être cité. Cela provoque un problème d'analyse.
jeb a résolu ces deux questions 9 et 10 à "Pretty print" windows variable %PATH% - comment diviser le ';' CMD shell
On peut donc combiner l' ~s
modificateur et le chemin d'accès techniques de classification avec mon variation de jeb CHEMIN de l'analyseur pour obtenir ce près de preuve de balle solution pour vérifier si un chemin existe déjà au sein du CHEMIN. La fonction peut être inclus et appelé à partir d'un fichier de commandes, ou il peut rester seul et être appelé comme sa propre inPath.bat fichier de commandes. Il semble que beaucoup de code, mais plus de la moitié est des commentaires.
@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
La fonction peut être utilisée comme (en supposant que le fichier de commandes nommé inPath.chauve-souris):
set test=c:\mypath
call inPath test && (echo found) || (echo not found)
Typiquement, la raison pour laquelle vérifier si un chemin existe dans le CHEMIN d'accès est parce que vous voulez ajouter le chemin d'accès s'il n'est pas là. Normalement, c'est fait simplement en utilisant quelque chose comme path %path%;%newPath%
. Mais Question 9 montre comment ce n'est pas fiable.
Une autre question est de savoir comment retourner la dernière valeur du CHEMIN à travers le ENDLOCAL barrière à la fin de la fonction, en particulier si la fonction peut être appelée avec l'expansion retardée activé ou désactivé. Tout non échappés !
corrompre la valeur si l'expansion retardée est activé.
Ces problèmes sont résolus à l'aide d'un incroyable retour en toute sécurité technique que jeb inventé ici: 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