ss64.com に遭遇しました。これは、Windowsコマンドインタープリターが実行するバッチスクリプトの作成方法に関する優れたヘルプを提供します。
ただし、バッチスクリプトの文法、物事がどのように展開するか、または展開しないか、および物事をエスケープする方法についての良い説明を見つけることができませんでした。
ここに、私が解決できなかった質問の例を示します:
foreach $i (@ARGV) { print '*' . $i ; }
)、コンパイルしてこの方法で呼び出しました:my_script.exe "a ""b"" c"
→出力は*a "b*c
ですmy_script.exe """a b c"""
→出力*"a*b*c"
echo
コマンドはどのように機能しますか?そのコマンド内で何が展開されますか?for [...] %%I
を使用する必要がありますが、インタラクティブセッションではfor [...] %I
を使用する必要があるのはなぜですか?%PROCESSOR_ARCHITECTURE%
を文字通りエコーするにはどうすればよいですか? echo.exe %""PROCESSOR_ARCHITECTURE%
が機能することがわかりましたが、より良い解決策はありますか?%
のペアはどのように一致しますか?例:set b=a
、echo %a %b% c%
→%a a c%
set a =b
、echo %a %b% c%
→bb c%
set
コマンドを使用すると、変数はどのように保存されますか?たとえば、set a=a" b
を実行してからecho.%a%
を実行すると、a" b
を取得します。ただし、UnxUtilsからecho.exe
を使用すると、a b
が返されます。 %a%
が別の方法で拡張するのはなぜですか?あなたのライトをありがとう。
コマンドウィンドウからコマンドを呼び出す場合、コマンドライン引数のトークン化はcmd.exe
(別名「シェル」)によって行われません。ほとんどの場合、トークン化は新しく形成されたプロセスのC/C++ランタイムによって行われますが、これは必ずしもそうではありません-たとえば、新しいプロセスがC/C++で記述されていない場合、または新しいプロセスがargv
そして、それ自体の生のコマンドラインを処理します(例: GetCommandLine() )。 OSレベルでは、Windowsはトークン化されていないコマンドラインを単一の文字列として新しいプロセスに渡します。これは、シェルが引数を新しく形成されたプロセスに渡す前に一貫性のある予測可能な方法でトークン化するほとんどの* nixシェルとは対照的です。これは、個々のプログラムが引数のトークン化を自分の手に委ねることが多いため、Windows上の異なるプログラム間で引数のトークン化の動作が大きく異なることを意味します。
それが無秩序のように聞こえるなら、それは一種です。ただし、多数のWindowsプログラムdoはMicrosoft C/C++ランタイムのargv
を利用するため、一般的に理解するのに役立つ場合があります- MSVCRTがトークン化する方法 引数。以下は抜粋です。
Microsoftの「バッチ言語」(.bat
)はこの無秩序な環境の例外ではなく、トークン化とエスケープのための独自の独自のルールを開発しました。また、cmd.exeのコマンドプロンプトは、コマンドライン引数の前処理(主に変数の置換とエスケープ)を行ってから、新しく実行するプロセスに引数を渡すように見えます。このページでは、jebとdbenhamによる優れた回答で、バッチ言語とcmdエスケープの低レベルの詳細について詳しく読むことができます。
Cで簡単なコマンドラインユーティリティを作成し、テストケースについての説明を見てみましょう。
int main(int argc, char* argv[]) {
int i;
for (i = 0; i < argc; i++) {
printf("argv[%d][%s]\n", i, argv[i]);
}
return 0;
}
(注:argv [0]は常に実行可能ファイルの名前であり、簡潔にするために以下では省略されています。Windowsでテスト済みXPSP3。VisualStudio 2005でコンパイルされています。)
> test.exe "a ""b"" c"
argv[1][a "b" c]
> test.exe """a b c"""
argv[1]["a b c"]
> test.exe "a"" b c
argv[1][a" b c]
そして、私自身のテストのいくつか:
> test.exe a "b" c
argv[1][a]
argv[2][b]
argv[3][c]
> test.exe a "b c" "d e
argv[1][a]
argv[2][b c]
argv[3][d e]
> test.exe a \"b\" c
argv[1][a]
argv[2]["b"]
argv[3][c]
jeb's answer (バッチモードとコマンドラインモードの両方で有効)のフェーズ1の詳細な説明を次に示します。
フェーズ1)拡張率左から順に、各文字をスキャンして%
または<LF>
を探します。見つかったら
<LF>
の行を切り捨てます)<LF>
の場合、<LF>
以降の行の残りをドロップ(無視)します<CR>
)%
でなければならないため、1.1に進んでください%
)コマンドラインモードの場合はスキップ%
が続く場合%%
を単一の%
に置き換えて、スキャンを続行します*
が続き、コマンド拡張が有効になっている場合%*
をすべてのコマンドライン引数のテキストで置き換え(引数がない場合は何も置き換えない)、スキャンを続行します。<digit>
が続く場合%<digit>
を引数値に置き換え(未定義の場合は何も置き換えない)、スキャンを続行します。~
が続き、コマンド拡張が有効になっている場合は<digit>
が続く場合、%~[modifiers]<digit>
を変更された引数値に置き換え(定義されていない場合、または指定された$ PATH:修飾子が定義されていない場合は何も置き換えない)、スキャンを続行します。<digit>
の前の最後の修飾子でなければなりません%
またはバッファーの終わりの前で区切り、それらをVAR(空のリストの場合があります)と呼びます。%
の場合、%VAR%
をVARの値に置き換えてスキャンを続行します%VAR%
を削除してスキャンを続行します%
:
またはバッファーの終わりの前で中断し、それらをVAR(空のリストの場合があります)と呼びます。 VARが:
の前で中断し、後続の文字が%
である場合、VARの最後の文字として:
を含め、%
。の前で中断します。%
の場合、%VAR%
をVARの値に置き換えてスキャンを続行します%VAR%
を削除してスキャンを続行します:
である場合は%VAR:
を削除して、スキャンを続行します。~
である場合は[integer][,[integer]]%
のパターンに一致する場合%VAR:~[integer][,[integer]]%
をVARの値のサブストリングに置き換え(空のストリングになる可能性がある)、スキャンを続行します。=
または*=
が続く場合[*]search=[replace]%
のパターンと一致する場合。検索では=
を除く任意の文字セットを含めることができ、replaceでは%
を除く任意の文字セットを含めることができます。%VAR:[*]search=[replace]%
は、検索と置換(空の文字列になる可能性がある)を実行した後、VARの値でスキャンを続行します%
を削除し、%
の後の次の文字からスキャンを続行します%
を保持し、%
の後の次の文字からスキャンを続行します上記は、このバッチの理由を説明するのに役立ちます
@echo off
setlocal enableDelayedExpansion
set "1var=varA"
set "~f1var=varB"
call :test "arg1"
exit /b
::
:test "arg1"
echo %%1var%% = %1var%
echo ^^^!1var^^^! = !1var!
echo --------
echo %%~f1var%% = %~f1var%
echo ^^^!~f1var^^^! = !~f1var!
exit /b
次の結果が得られます。
%1var% = "arg1"var
!1var! = varA
--------
%~f1var% = P:\arg1var
!~f1var! = varB
注1-フェーズ1は、REMステートメントの認識前に発生します。これは、発言でさえ致命的なエラーを生成できるため、非常に重要です。無効な引数展開構文または無効な変数検索と置換構文があります!
@echo off
rem %~x This generates a fatal argument expansion error
echo this line is never reached
注2-%解析ルールの別の興味深い結果:名前に:を含む変数は定義できますが、コマンド拡張機能が無効になっていない限り展開できません。例外が1つあります。最後に1つのコロンを含む変数名は、コマンド拡張が有効になっているときに展開できます。ただし、コロンで終わる変数名に対して部分文字列または検索および置換操作を実行することはできません。以下のバッチファイル(jeb提供)は、この動作を示しています
@echo off
setlocal
set var=content
set var:=Special
set var::=double colon
set var:~0,2=tricky
set var::~0,2=unfortunate
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
echo Now with DisableExtensions
setlocal DisableExtensions
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
注3-投稿でjebがレイアウトする構文解析ルールの順序の興味深い結果:検索を実行して通常の展開に置き換える場合、特殊文字はエスケープしないでください(ただし、引用される)。ただし、検索を実行して遅延展開に置き換える場合、特殊文字はエスケープする必要があります(引用符で囲まれていない場合)。
@echo off
setlocal enableDelayedExpansion
set "var=this & that"
echo %var:&=and%
echo "%var:&=and%"
echo !var:^&=and!
echo "!var:&=and!"
jeb's answer (バッチモードとコマンドラインモードの両方で有効)のフェーズ5の拡張されたより正確な説明を次に示します。
フェーズ5)拡張の遅延
次の条件のいずれかが当てはまる場合、このフェーズはスキップされます。
CALL
、括弧付きブロック、コマンド連結(&
、&&
または||
)、またはパイプ|
に関連付けられていません。遅延拡張プロセスは、トークンに個別に適用されます。コマンドには複数のトークンが含まれる場合があります。
for ... in(TOKEN) do
if defined TOKEN
if exists TOKEN
if errorlevel TOKEN
if cmdextversion TOKEN
if TOKEN comparison TOKEN
、比較は==
、equ
、neq
、lss
、leq
、gtr
、またはgeq
のいずれか!
を含まないトークンは変更されません。
少なくとも1つの!
を含む各トークンについて、各文字を左から右に^
または!
をスキャンし、見つかった場合は、
!
または^
リテラルに必要です^
then ^
を削除します!
の場合、!
または<LF>
の前で区切り、それらをVAR(空のリストである可能性があります)と呼びます!
の場合、!VAR!
をVARの値に置き換えてスキャンを続行します!VAR!
を削除してスキャンを続行します!
、:
、または<LF>
の前で区切り、それらをVAR(空のリストの場合があります)と呼びます。 VARが:
の前で中断し、後続の文字が!
の場合、VARの最後の文字として:
を含め、!
の前で中断します。!
の場合、!VAR!
をVARの値に置き換えてスキャンを続行します!VAR!
を削除してスキャンを続行します:
である場合は!VAR:
を削除してスキャンを続行します~[integer][,[integer]]!
その後!VAR:~[integer][,[integer]]!
をVARの値のサブストリングに置き換え(おそらく空のストリングになる)、スキャンを続行します[*]search=[replace]!
のパターンと一致する場合。検索では=
を除く任意の文字セットを含むことができ、replaceは!
を除く任意の文字セットを含むことができます。!VAR:[*]search=[replace]!
をVARの値に置き換え(場合によっては空の文字列になる)、スキャンを続行!
を削除します!
を保存します!
の後の次の文字からスキャンを続行します指摘したように、コマンドはμSoftランドの引数文字列全体に渡され、これを独自の使用のために個別の引数に解析するのは彼らの責任です。異なるプログラム間でこれに一貫性はないため、このプロセスを記述するためのルールのセットはありません。プログラムで使用するCライブラリが存在するかどうか、各コーナーケースを確認する必要があります。
システム.bat
ファイルに関する限り、ここにそのテストがあります。
c> type args.cmd
@echo off
echo cmdcmdline:[%cmdcmdline%]
echo 0:[%0]
echo *:[%*]
set allargs=%*
if not defined allargs goto :eof
setlocal
@rem Wot about a Nice for loop?
@rem Then we are in the land of delayedexpansion, !n!, call, etc.
@rem Plays havoc with args like %t%, a"b etc. ugh!
set n=1
:loop
echo %n%:[%1]
set /a n+=1
shift
set param=%1
if defined param goto :loop
endlocal
これで、いくつかのテストを実行できます。 μSoftが何をしようとしているかを把握できるかどうかを確認します。
C>args a b c
cmdcmdline:[cmd.exe ]
0:[args]
*:[a b c]
1:[a]
2:[b]
3:[c]
今のところ結構です。 (これからは面白くない%cmdcmdline%
と%0
を省きます。)
C>args *.*
*:[*.*]
1:[*.*]
ファイル名の展開はありません。
C>args "a b" c
*:["a b" c]
1:["a b"]
2:[c]
引用符は引数の分割を防ぎますが、引用符の除去はありません。
c>args ""a b" c
*:[""a b" c]
1:[""a]
2:[b" c]
連続した二重引用符があると、特殊な解析機能が失われます。 @Beniotの例:
C>args "a """ b "" c"""
*:["a """ b "" c"""]
1:["a """]
2:[b]
3:[""]
4:[c"""]
クイズ:環境変数の値をsingle引数(つまり、%1
)としてbatファイルに渡すにはどうすればよいですか?
c>set t=a "b c
c>set t
t=a "b c
c>args %t%
1:[a]
2:["b c]
c>args "%t%"
1:["a "b]
2:[c"]
c>Aaaaaargh!
正常な解析は永遠に壊れているようです。
娯楽のために、これらの例にその他の^
、\
、'
、&
(&c。)文字を追加してみてください。
編集:受け入れられた答えを参照してください。以下は間違っており、コマンドラインをTinyPerlに渡す方法のみを説明しています。
引用に関して、私はその振る舞いは次のように感じています:
"
が見つかると、文字列のグロビングが始まります"
以外の文字はすべてグロブされます"
が見つかった場合:""
が続く場合(したがって、トリプル"
)、文字列に二重引用符が追加されます。"
が続く場合(したがって、二重"
)、二重引用符が文字列に追加され、文字列のグロビングが終了します"
でない場合、文字列のグロビングは終了します要するに:
"a """ b "" c"""
は2つの文字列で構成されます:a " b "
およびc"
"a""
、"a"""
および"a""""
は、行末にある場合はすべて同じ文字列です