最初に、この問題を完全に解決するのを難しくしているいくつかの問題を指摘します。次に、私が思いついた中で最も防弾のソリューションを紹介します。
この説明では、小文字のパスを使用してファイル システム内の単一のフォルダー パスを表し、大文字の PATH を使用して PATH 環境変数を表します。
実用的な観点から、ほとんどの人は、PATH に特定のパスと完全に一致する文字列が含まれているかどうかではなく、PATH に特定のパスと論理的に同等のものが含まれているかどうかを知りたいと考えています。これは、次の理由で問題になる可能性があります。
パスの末尾\
はオプションです
ほとんどのパスは、末尾の があってもなくても同じように機能し\
ます。パスは、どちらの方法でも同じ場所を論理的に指しています。PATH には、多くの場合、末尾に\
. これは、一致する PATH を検索する際のおそらく最も一般的な実際の問題です。
- 例外が 1 つあります。相対パス(ドライブ C の現在の作業ディレクトリを意味する) は、 (ドライブ C のルート ディレクトリを意味する
C:
) とは大きく異なります。C:\
一部のパスには代替短縮名
があります 古い 8.3 標準に準拠していないパスには、標準に適合する代替短縮名があります。これは、特にビジネス環境で頻繁に見られる PATH の問題です。
Windows は、パス内のフォルダー セパレーターとしてと の両方/
を受け入れます。\
これはあまり見られませんが、/
代わりに を使用してパスを指定することができ\
、PATH 内で (および他の多くの Windows コンテキストと同様に) 正常に機能します。
Windows は、連続するフォルダー区切りを 1 つの論理区切りとして扱います。
C:\FOLDER\\ と C:\FOLDER\ は同等です。これは実際、パスを処理する際に多くのコンテキストで役立ちます。これは、開発者は一般に、末尾が既に存在する\
かどうかをわざわざ確認することなくパスに追加できるためです。\
しかし、正確な文字列一致を実行しようとすると、明らかに問題が発生する可能性があります。
C:
例外: がと異なるだけでなくC:\
、C:\
(有効なパス)が (C:\\
無効なパス) と異なります。
Windows は、ファイル名とディレクトリ名から末尾のドットとスペースを削除します。
"C:\test. "
と同等"C:\test"
です。
現在.\
および親の..\
フォルダー指定子は、パス内に表示される場合があり
ますC:\.\parent\child\..\.\child\
C:\parent\child
オプションで、パスを二重引用符で囲むことができます。
のような特殊文字から保護するために、パスはしばしば引用符で囲まれます<space>
,
;
^
&
=
。実際には、パスの前、中、および/または後に任意の数の引用符を表示できます。特殊文字から保護する目的を除いて、これらは Windows によって無視されます。パスに が含まれていない限り、PATH 内で引用符が必要になることはあり;
ません。
パスは、完全修飾パスまたは相対パスにすることができます。
完全修飾パスは、ファイル システム内の特定の 1 つの場所を指します。相対パスの場所は、現在の作業ボリュームとディレクトリの値によって異なります。相対パスには主に 3 つの種類があります。
D:
ボリューム D の現在の作業ディレクトリに相対的です。
\myPath
現在の作業ボリュームに相対的です (C:、D: など)。
myPath
現在の作業ボリュームとディレクトリに相対的です
PATH 内に相対パスを含めることは完全に合法です。これは、Unix の世界では非常に一般的です。Unix はデフォルトで現在のディレクトリを検索しないため、Unix PATH には多くの場合.\
. ただし、Windows は既定で現在のディレクトリを検索するため、Windows PATH で相対パスが使用されることはほとんどありません。
したがって、PATH に既にパスが含まれているかどうかを確実に確認するには、任意のパスを正規 (標準) 形式に変換する方法が必要です。FOR 変数と引数の~s
展開で使用される修飾子は、問題 1 ~ 6 に対処し、問題 7 に部分的に対処する単純な方法です。~s
修飾子は、囲み引用符を削除しますが、内部引用符は保持します。問題 7 は、比較の前にすべてのパスから引用符を明示的に削除することで完全に解決できます。パスが物理的に存在しない場合、~s
修飾子は をパスに追加せず\
、パスを有効な 8.3 形式に変換しないことに注意してください。
問題~s
は、相対パスを完全修飾パスに変換することです。相対パスは完全修飾パスと一致してはならないため、これは問題 8 の問題です。FINDSTR 正規表現を使用して、パスを完全修飾パスまたは相対パスとして分類できます。通常の完全修飾パスは で始まる必要<letter>:<separator>
があり<letter>:<separator><separator>
ますが、<separator> は または のいずれ\
か/
です。UNC パスは常に完全修飾パスであり、で始まる必要があり\\
ます。完全修飾パスを比較するときは、~s
修飾子。相対パスを比較するときは、生の文字列を使用します。最後に、完全修飾パスを相対パスと比較することはありません。この戦略は、問題 8 の優れた実用的な解決策を提供します。唯一の制限は、2 つの論理的に同等の相対パスが一致しないものとして扱われる可能性があることですが、相対パスは Windows PATH ではまれであるため、これは小さな懸念事項です。
この問題を複雑にする追加の問題がいくつかあります。
9)特殊文字を含む PATH を扱う場合、通常の展開は信頼できません。
PATH 内で特殊文字を引用する必要はありませんが、引用することはできます。したがって、 PATH like
は完全に有効ですが、とC:\THIS & THAT;"C:\& THE OTHER THING"
の両方が失敗するため、単純な展開を使用して安全に展開することはできません。"%PATH%"
%PATH%
10)パス区切り文字はパス名内でも有効です
A;
は PATH 内のパスを区切るために使用されますが、パス内;
で有効な文字にすることもできます。その場合、パスを引用符で囲む必要があります。これにより、解析の問題が発生します。
jeb は、 'Pretty print' windows %PATH% variable - how to split on ';' の問題 9 と 10 の両方を解決しました。CMD シェルで
そのため、修飾子とパス分類の手法を jeb の PATH パーサーの私のバリエーションと組み合わせて~s
、特定のパスが 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 は、これがいかに信頼できないかを示しています。
もう 1 つの問題は、関数の最後にある ENDLOCAL バリアを越えて最終的な PATH 値を返す方法です。特に、遅延展開を有効または無効にして関数を呼び出すことができる場合です。!
遅延拡張が有効になっている場合、エスケープされていない値はすべて破損します。
これらの問題は、jeb がここで発明した驚くべきセーフ リターン テクニックを使用して解決されます。
@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