1. ホーム
  2. windows

[解決済み] Windows コマンドインタプリタ(CMD.EXE)は、どのようにスクリプトを解析するのですか?

2022-04-24 12:25:25

質問

に遭遇しました。 ss64.com Windowsコマンドインタプリタが実行するバッチスクリプトの書き方について、良いヘルプを提供しています。

しかし 文法 バッチスクリプトの展開方法、展開しない方法、エスケープ方法などです。

私が解けなかった問題のサンプルです。

  • 見積もりシステムはどのように管理されていますか?を作りました。 TinyPerl スクリプト

    ( 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 . しかし、もし私が echo.exe を UnxUtils から取得すると、次のようになります。 a b . どうして %a% が違う形で展開されるのですか?

ライトをありがとうございました。

解決方法は?

バッチスクリプトの文法を調べるための実験を行った。また、バッチモードとコマンドラインモードの違いも調査した。

バッチライン・パーサー

ここでは、バッチファイルのラインパーサーにおける各フェーズについて簡単に説明します。

フェーズ 0) 行を読む。

フェーズ 1) パーセント拡張。

フェーズ2) 特殊文字の処理、トークン化、キャッシュコマンドブロックの構築。 これは、引用符、特殊文字、トークン区切り、キャレットエスケープなどの影響を受ける複雑な処理である。

フェーズ3) 解析されたコマンドのエコー送信 コマンドブロックが以下の文字で始まっていない場合のみ @ 直前のステップの開始時にECHOがONであった場合。

フェーズ 4) FOR %X 変数の拡張を行います。 FOR コマンドがアクティブで、DO 以降のコマンドが処理されている場合のみ。

フェーズ5)ディレイエクスパンション。 ディレイドエクスパンションが有効な場合のみ

フェーズ5.3) パイプの処理。 コマンドがパイプの両側にある場合のみ

フェーズ5.5) リダイレクトを実行する。

フェーズ6) CALL処理/キャレットダブリング。 コマンドトークンがCALLの場合のみ

フェーズ 7) 実行する。 コマンドが実行される


各フェーズの詳細はこちらです。

以下に説明するフェーズは、バッチパーサーがどのように動作するかのモデルでしかないことに注意してください。実際のcmd.exeの内部はこれらのフェーズを反映していないかもしれません。しかし、このモデルは、バッチスクリプトの動作を予測するのに有効です。

フェーズ 0) 行を読む。 入力の行を最初の <LF> .

  • コマンドとして解析される行を読み込む場合。 <Ctrl-Z> (0x1A)は次のように読みます。 <LF> (ラインフィード 0x0A)
  • GOTOまたはCALLが:labelをスキャンしながら行を読み取る場合。 <Ctrl-Z> はそれ自身として扱われ、それは ない に変換されます。 <LF>

フェーズ1)パーセンテージの拡大。

  • ダブル %% は、単一の %
  • 引数の展開 ( %* , %1 , %2 など)
  • の拡大 %var% var が存在しない場合、何もない状態に置き換えます。
  • 行は最初に切り捨てられる <LF> でなく %var% 拡張
  • 完全な説明は、dbenhamのこの記事の前半をお読みください。 同じスレッドです。パーセントフェーズ

フェーズ 2) 特殊文字を処理し、トークン化し、キャッシュされたコマンドブロックを構築します。 これは、引用符、特殊文字、トークン区切り、キャレットエスケープなどの影響を受ける複雑な処理である。以下は、この処理の概略である。

この段階を通じて重要な概念があります。

  • トークンとは、単純に文字列を1つの単位として扱うものです。
  • トークンは、トークン区切り文字で区切られます。標準的なトークン区切り記号は <space> <tab> ; , = <0x0B> <0x0C><0xFF>

    連続したトークン区切りは1つとして扱われ、トークン区切りの間に空のトークンは存在しません。
  • 引用文字列の中にトークン区切りはありません。引用文字列全体は常に1つのトークンの一部として扱われます。1つのトークンは、引用文字列と引用されていない文字の組み合わせで構成されている場合があります。

以下の文字は、文脈によってこのフェーズで特別な意味を持つ場合があります。 <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

各文字を左から右へ見てください。

  • もし <CR> を削除すると、まるでそこに何もなかったかのようになります(奇妙な リダイレクトの動作 )
  • もしキャレット( ^ ) の場合、次の文字がエスケープされ、エスケープされたキャレットは削除されます。エスケープされた文字は,すべての特別な意味を失う(ただし <LF> ).
  • もし、引用文( " ) の場合、引用フラグをトグルします。引用フラグが有効の場合は、引用フラグが無効の場合のみ "<LF> は特殊です。他の文字は、次の引用で引用フラグがオフになるまで、特別な意味を失います。終了引用符をエスケープすることはできません。すべての引用文字は、常に同じトークン内にあります。
  • <LF> は常に引用フラグをオフにします。その他の動作は文脈によって変化しますが、引用符は決して <LF> .
    • エスケープされた <LF>
      • <LF> が剥がされる
      • 次の文字がエスケープされます。行バッファの終端にある場合、次の行を読み込んでフェーズ 1 とフェーズ 1.5 で処理し、現在の行に追加してから次の文字をエスケープします。もし次の文字が <LF> この処理は再帰的ではないことを意味します。
    • エスケープされていない <LF> 括弧内でない
      • <LF> は取り除かれ、現在の行の解析は終了する。
      • ラインバッファに残った文字は、単に無視されます。
    • アンエスケープ <LF> FOR IN括弧付きブロック内
      • <LF> が変換され <space>
      • 行バッファの終端にある場合、次の行が読み込まれ、現在の行に追加されます。
    • アンエスケープ <LF> 括弧で囲まれたコマンドブロック内
      • <LF> は、次のように変換されます。 <LF><space> であり、その <space> は、コマンドブロックの次の行の一部として扱われます。
      • ラインバッファの終端にある場合、次の行を読み込んでスペースに追加する。
  • 特殊文字 & | < または > の場合、パイプ、コマンド連結、リダイレクトを処理するために、この時点で行を分割します。
    • パイプの場合( | ) の場合、各サイドは、フェーズ 5.3 で特別な取り扱いを受ける個別のコマンド (またはコマンドブロック) になります。
    • の場合は & , && または || コマンドを連結した場合、連結の各辺は別々のコマンドとして扱われます。
    • の場合は < , << , > または >> リダイレクトを行う場合、リダイレクト節は解析され、一時的に削除された後、現在のコマンドの末尾に追加される。リダイレクション句は、オプションのファイルハンドル桁、リダイレクション演算子、リダイレクション先トークンから構成される。
      • リダイレクト演算子の前にあるトークンがエスケープされていない1桁の数字である場合、その数字はリダイレクトされるファイルハンドルを指定します。ハンドルトークンが見つからない場合、出力リダイレクションのデフォルトは1(標準出力)、入力リダイレクションのデフォルトは0(標準入力)である。
  • このコマンドの最初のトークンが(リダイレクトを最後に移す前の)次のように始まる場合 @ であれば、その @ は特別な意味を持ちます。( @ は他の文脈では特別なものではありません)
    • には、特別な @ が削除されます。
    • ECHO が ON の場合、このコマンドと、この行に続く連結されたコマンドは、フェーズ 3 のエコーから除外されます。もし @ の前にある ( の場合、括弧で囲まれたブロック全体がフェーズ3エコーから除外されます。
  • 処理括弧(複数行に渡る複合文に対応)。
    • パーサーがコマンド トークンを探していない場合は、次のようになります。 ( は特別なものではありません。
    • パーサーがコマンド トークンを探しているときに ( を指定すると、新しい複合文が開始され、括弧のカウンタがインクリメントされます。
    • もし、括弧カウンタが > 0 であれば、次のようになります。 ) は、複合ステートメントを終了し、括弧カウンタをデクリメントします。
    • 行末に到達し、括弧カウンタが > 0 であれば、次の行が複合文に追加されます(フェーズ 0 から再スタートします)。
    • 括弧カウンタが 0 で、パーサがコマンドを探している場合 ) と同じように機能します。 REM 文の直後にトークン区切り文字、特殊文字、改行、ファイル終端がある限りは
      • を除くすべての特殊文字は意味を失います。 ^ (行の連結が可能です)
      • 論理行の末尾に達すると、"command"全体が破棄されます。
  • 各コマンドは一連のトークンにパースされます。最初のトークンは常にコマンドトークンとして扱われます(特別な @ が取り除かれ、リダイレクトが最後に移動しています)。
    • コマンドトークンの前にあるトークン区切り記号は取り除かれます。
    • コマンドトークンのパース時 ( は、標準的なトークン区切り記号である
    • 後続のトークンの扱いはコマンドに依存します。
  • ほとんどのコマンドは、コマンドトークンの後のすべての引数を単純に1つの引数トークンに連結します。すべての引数トークンのデリミタは保存されます。引数オプションは、通常、フェーズ 7 まで解析されません。
  • IF、FOR、REMの3つのコマンドは、特別な扱いを受けます。
    • IFは2つまたは3つの部分に分割され、それぞれ独立に処理されます。IFの構文に誤りがあると、致命的なシンタックスエラーになります。
      • 比較演算は、フェーズ7まで流れる実際のコマンドである
        • すべてのIFオプションはフェーズ2で完全にパースされます。
        • 連続したトークン区切りは、1つのスペースに折り畳まれます。
        • 比較演算子によって、識別される値トークンは1つまたは2つになります。
      • Trueコマンドブロックは、条件以降のコマンドのセットであり、他のコマンドブロックと同様に解析されます。ELSE を使用する場合は、True ブロックを括弧で囲む必要があります。
      • オプションのFalseコマンドブロックは、ELSEの後のコマンドのセットです。この場合も、このコマンドブロックは通常通りパースされる。
      • TrueとFalseのコマンドブロックは、自動的に後続のフェーズに流れ込むことはありません。それらの後続処理は、フェーズ7で制御されます。
    • DOの後にFORが2つに分割されます。FOR 構文で構文エラーが発生すると、致命的な構文エラーになります。
      • DOまでの部分は、フェーズ7まで流れる実際のFOR反復コマンドです
        • すべてのFORオプションはフェーズ2で完全にパースされます。
        • IN括弧付きの節は、以下のように扱います。 <LF> として <space> . IN句が解析された後、すべてのトークンが連結され、1つのトークンとなります。
        • FORコマンドからDOまで、連続するエスケープ/引用符なしのトークン区切りは、1つのスペースに収まります。
      • DO以降の部分は、通常通りパースされるコマンドブロックです。DOコマンドブロックのその後の処理は、フェーズ7のイテレーションで制御されます。
    • フェーズ2で検出されたREMは、他のすべてのコマンドとは劇的に異なる扱いを受ける。
      • 引数トークンは1つだけ解析されます。パーサーは、最初の引数トークン以降の文字を無視します。
      • REM コマンドはフェーズ 3 出力に表示されることがありますが、コマンドは決して実行されず、元の引数テキストがエコーされます - エスケープ キャレットは削除されませんが、...
        • 引数トークンが1つだけで、その末尾がエスケープされていない ^ が行を終了する場合、引数トークンは捨てられ、後続の行が解析されてREMに追加されます。これは、トークンが1つ以上あるか、最後の文字が ^ .
  • コマンドトークンが : で、これがフェーズ2の最初のラウンド(フェーズ6のCALLによる再開ではない)である場合
    • このトークンは通常 未実行ラベル .
      • しかし、残りの行はパースされます。 ) , < , > , &| は特別な意味を持たなくなりました。残りの行全体がラベル "command"の一部であるとみなされます。
      • ^ は引き続き特殊で、行の継続を利用して後続の行をラベルに付加することができることを意味します。
      • について 未実行ラベル で囲まれている場合、その直後にコマンドまたは 実行済みラベル を次の行に追加します。
        • ( に続く最初のコマンドに特別な意味を持たなくなりました。 未実行ラベル .
      • ラベルの解析が完了した後、コマンドは中断されます。それ以降のフェーズは、そのラベルに対して行われません。
    • フェーズ2で見つかったラベルが、3つの例外によって 実行済みラベル であり、フェーズ7までパージングを継続する。
      • ラベルトークンの前にリダイレクトがあり、そこに | パイプまたは & , && または || コマンドを行に連結する。
      • ラベルトークンの前にリダイレクトがあり、コマンドが括弧で囲まれたブロックの中にある。
      • ラベルトークンは括弧で囲まれたブロック内の行の一番最初のコマンドであり、上の行は 未実行ラベル .
    • が発生した場合、以下のようになります。 実行済みラベル がフェーズ 2 で発見された場合
      • ラベル、その引数、およびリダイレクトはすべて、フェーズ 3 のエコー出力から除外されます。
      • その行に続く連結されたコマンドは、完全に解析され実行されます。
    • の詳細については、こちらをご覧ください。 実行済みラベル との比較。 未実行ラベル をご覧ください。 https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

フェーズ3) 解析されたコマンドをエコーする コマンドブロックが以下のように始まっていない場合のみ @ 直前のステップの開始時に ECHO が ON であった場合。

フェーズ 4) FOR %X 変数の拡張を行います。 FOR コマンドがアクティブで、DO 以降のコマンドが処理されている場合のみ。

  • この時点で、バッチ処理のフェーズ1では、すでにFOR変数が以下のように変換されています。 %%X%X . コマンドラインでは、フェーズ 1 のパーセント展開のルールが異なります。このため、コマンドラインでは %X が、バッチファイルでは %%X をFOR変数に使用します。
  • FOR変数名は大文字と小文字を区別しますが ~modifiers は大文字小文字を区別しない。
  • ~modifiers は変数名より優先される。に続く文字がある場合 ~ がモディファイアと有効なFOR変数名の両方であり、その後に有効なFOR変数名である文字が存在する場合、その文字はモディファイアとして解釈されます。
  • FOR変数名はグローバルですが、DO句のコンテキスト内のみです。ルーチンが FOR DO 節内から CALL された場合、FOR 変数は CALL されたルーチン内では展開されません。しかし、ルーチンに独自の FOR コマンドがある場合は、以下のようになります。 すべて 現在定義されている FOR 変数は、内側の DO コマンドからアクセスできます。
  • FOR 変数名はネストされた FOR 内で再利用することができます。内側の FOR の値が優先されますが、INNER FOR が閉じられると、外側の FOR の値が復元されます。
  • このフェーズの開始時に ECHO がオンになっていた場合、フェーズ 3) が繰り返され、FOR 変数が展開された後、解析された DO コマンドが表示されます。

----これ以降、フェーズ2で特定された各コマンドは個別に処理される。

---- フェーズ5~7は、1つのコマンドを完了させてから次のコマンドに移る。

フェーズ5)拡張の遅れ。 遅延展開がオンの場合のみ、コマンドが パイプの両側にある括弧付きのブロック でなく、かつコマンドが 裸のバッチスクリプト (スクリプト名から括弧、CALL、コマンド連結、パイプを除いたもの)。

  • コマンドの各トークンは、独立して遅延展開のために解析されます。
    • ほとんどのコマンドは、コマンド・トークン、引数トークン、および各リダイレクト先トークンの2つ以上のトークンを解析します。
    • FOR コマンドは、IN 節トークンのみを解析します。
    • IF コマンドは、比較演算子によって 1 つまたは 2 つの比較値のみを解析します。
  • 解析された各トークンについて、まず、その中に ! . もしそうでなければ、そのトークンはパースされません。 ^ 文字が含まれます。 トークンに ! の場合、各文字を左から右へスキャンします。
    • キャレットである場合 ( ^ ) 次の文字は特別な意味を持たず、キャレットそのものが削除されます。
    • エクスクラメーションマークであれば、次のエクスクラメーションマークを検索し(キャレットはもう観測されません)、変数の値まで展開します。
      • 連続したオープニング ! は1つに折りたたまれます。 !
      • ペアリングされていない残りの ! が削除されます。
    • この段階でのバーの展開は、特殊文字はもう検出されないので、quot;safe"です(たとえ <CR> または <LF> )
    • より完全な説明は、dbenhamのこれの後半を読んでください。 同スレッド - 感嘆符フェーズ

フェーズ5.3) パイプの加工。 コマンドがパイプの両側にある場合のみ

パイプの各側は独立して非同期で処理されます。

  • コマンドが cmd.exe の内部であるか、バッチ ファイルであるか、または括弧付きのコマンド ブロックである場合、新しい cmd.exe のスレッドで、以下を経由して実行されます。 %comspec% /S /D /c" commandBlock" そのため、コマンドブロックはフェーズを再起動しますが、今回はコマンドラインモードで実行されます。
    • 括弧付きのコマンドブロックの場合、すべての <LF> に変換され、その前後にコマンドがある場合は <space>& . その他 <LF> は取り除かれます。
  • 以上で、パイプコマンドの処理は終了です。
  • 参照 パイプでつながれたコードブロックの中で、遅延展開に失敗するのはなぜですか? パイプのパースと処理の詳細はこちら

フェーズ5.5) リダイレクトを実行する。 フェーズ2で発見されたあらゆるリダイレクトが実行されるようになりました。

フェーズ6)CALL処理/キャレットダブリング。 コマンドトークンがCALLである場合、または最初に発生する標準トークンデリミタの前のテキストがCALLである場合のみ。CALLがより大きなコマンドトークンから解析された場合、未使用の部分は、処理を進める前に引数トークンの前に追加されます。

  • 引数トークンをスキャンして、引用符で囲まれていない /? . トークン内のどこかに見つかった場合は、フェーズ6を中止してフェーズ7に進み、CALLのHELPが出力されます。
  • 最初の CALL 複数のCALLを重ねることができます。
  • すべてのキャレットを2倍にする
  • フェーズ1、1.5、2を再開し、フェーズ3には進みません。
    • 2重になっているキャレットは、引用されていない限り、1つのキャレットに戻されます。しかし、残念ながら、引用されたキャレットは2重のままです。
    • フェーズ1が少し変わる - ステップ1.2または1.3での拡張エラーはCALLを中断しますが、致命的なエラーにはならず、バッチ処理は続行されます。
    • フェーズ2のタスクが少し変更される
      • フェーズ2の最初のラウンドで検出されなかった、引用符で囲まれていない、エスケープされていないリダイレクトが新たに現れた場合、それは検出されますが、実際にリダイレクトを実行せずに削除されます(ファイル名を含む)。
      • 行末に新たに出現した引用符で囲まれていないキャレットは、行の継続を行わずに削除されます。
      • 次のいずれかを検出した場合、エラーにならずにCALLを中止します。
        • 新たに現れた引用符で囲まれていない、エスケープされていないもの & または |
        • 結果のコマンドトークンは、引用符で囲まれず、エスケープされない状態で始まります。 (
        • 削除されたCALLの後の最初のトークンは、次のように始まっています。 @
      • 結果コマンドが一見有効なIFまたはFORの場合、その後、次のようなエラーで実行に失敗します。 IF または FOR は内部コマンド、外部コマンドとして認識されません。
      • もちろん、このフェーズ2の第2ラウンドでは、結果としてのコマンドトークンが以下のようなラベルであれば、CALLは中止されない。 : .
  • 結果のコマンドトークンがCALLの場合、フェーズ6を再開する(CALLがなくなるまで繰り返す)。
  • 結果のコマンドトークンがバッチスクリプトまたは:labelの場合、CALLの実行はフェーズ6の残りの部分によって完全に処理されます。
    • CALLが完了したときに、正しい位置から実行を再開できるように、現在のバッチスクリプトファイルの位置をコールスタックにプッシュする。
    • CALL の %0、%1、%2、...%N、および %* 引数トークンを設定し、すべての結果トークンを使用します。
    • で始まるラベルがコマンドトークンの場合 : であれば
      • フェーズ5を再スタートします。これは、どの :label が CALL されるかに影響を与える可能性があります。しかし、%0 などのトークンはすでに設定されているため、CALL されるルーチンに渡される引数は変更されません。
      • GOTO ラベルを実行して、ファイルポインタをサブルーチンの先頭に配置します (:label の後に続く他のトークンは無視します。) GOTO の動作に関する規則については、フェーズ 7 を参照してください。
        • label トークンがない場合、または :label が見つからない場合は、直ちにコールスタックをポップして保存されたファイル位置を復元し、CALL を中止します。
        • もし :label に /? が含まれていたら、 :label を探す代わりに GOTO ヘルプが表示されます。ファイル・ポインタは移動しないので、CALL後のコードは2回実行されます(1回はCALLコンテキストで、もう1回はCALLのリターン後に実行されます)。参照 このスクリプトでは、なぜCALLはGOTOヘルプメッセージを表示するのでしょうか? をご覧ください。
    • Else 指定されたバッチスクリプトに制御を移します。
    • CALLされた :label またはスクリプトの実行は、EXIT /B または end-of-file に達するまで続けられ、その時点で CALL スタックがポップされ、保存されたファイル位置から実行が再開されます。

      CALLされたスクリプトや:labelについては、Phase 7は実行されません。
  • そうでない場合は、フェーズ6の結果は、実行のためにフェーズ7にフォールスルーされます。

フェーズ 7) 実行する。 コマンドが実行される

  • 7.1 - 内部コマンドを実行する - コマンドトークンが引用符で囲まれている場合は、このステップをスキップします。そうでない場合は、内部コマンドの解析と実行を試みます。
    • 引用符で囲まれていないコマンド トークンが内部コマンドを表しているかどうかを判断するために、次のテストが行われます。
      • コマンド・トークンが内部コマンドに完全に一致する場合、それを実行します。
      • の前にコマンドトークンをブレークします。 + / [ ] <space> <tab> , ; または =

        前のテキストが内部コマンドの場合、そのコマンドを覚えておくとよい
        • コマンドラインモード、または括弧付きブロック、IF true または false コマンドブロック、FOR DO コマンドブロック、またはコマンド連結に関わるコマンドの場合、内部コマンドを実行します。
        • それ以外の場合 (バッチ モードのスタンドアロン コマンドである必要があります)、現在のフォルダと PATH をスキャンして、ベース名が元のコマンド トークンに一致する .COM、.EXE、.BAT、または .CMD ファイルを探します。
          • 最初にマッチしたファイルが.BATまたは.CMDであれば、7.3.execに進み、そのスクリプトを実行します。
          • それ以外の場合 (一致するものが見つからないか、最初の一致が .EXE または .COM の場合) は、記憶している内部コマンドを実行します。
      • の前にコマンドトークンをブレークします。 . \ または :

        前のテキストが内部コマンドでない場合は、7.2 に進みます。

        それ以外の場合、前のテキストは内部コマンドである可能性があります。このコマンドを覚えておいてください。
      • の前にコマンドトークンを改行する。 + / [ ] <space> <tab> , ; または =

        直前のテキストが既存のファイルへのパスの場合、7.2 に進む

        それ以外の場合は、覚えている内部コマンドを実行します。
    • 内部コマンドがより大きなコマンドトークンから解析される場合、コマンドトークンの未使用部分が引数リストに含まれます。
    • コマンド トークンが内部コマンドとして解析されたからといって、それが正常に実行されるとは限りません。各内部コマンドには、引数やオプションの解析方法、および許可される構文について、独自のルールがあります。
    • すべての内部コマンドは、次のような場合、その機能を実行する代わりにヘルプを表示します。 /? が検出されました。ほとんどの場合 /? が引数のどこにでも現れる場合。しかし、ECHO や SET のようないくつかのコマンドは、最初の引数トークンが次のように始まっている場合にのみヘルプを表示します。 /? .
    • SETには興味深いセマンティクスがあります。
      • SETコマンドの変数名の前に引用符があり、拡張機能が有効になっている場合

        set "name=content" ignored --> 値= content

        の場合、最初の等号と最後の引用符の間のテキストがコンテンツとして使用されます(最初の等号と最後の引用符は除く)。最後の引用符の後のテキストは無視されます。等号の後に引用符がない場合は、その行の残りの部分が内容として使用されます。
      • SETコマンドの名前の前に引用符がない場合

        set name="content" not ignored --> 値= "content" not ignored

        を指定すると、イコールの後の残りの行全体がコンテンツとして使用され、引用符が存在する場合もすべて含まれます。
    • IF比較が評価され、条件が真か偽かに応じて、既に解析された適切な従属コマンドブロックが処理され、フェーズ5から開始されます。
    • FOR コマンドの IN 節は適切に反復処理されます。
      • コマンドブロックの出力を反復するFOR /Fである場合。
        • IN 節は、CMD /C によって新しい cmd.exe プロセスで実行されます。
        • コマンド ブロックは、2 回目に全体の解析プロセスを実行する必要がありますが、今度はコマンド ライン コンテキストで実行されます。
        • ECHOは最初からオンで、遅延展開は通常無効です(レジストリ設定に依存します)。
        • IN 節コマンド ブロックによって行われたすべての環境の変更は、子プロセスである cmd.exe が終了すると失われます。
      • 各反復の場合。
        • FOR変数の値が定義されている
        • そして、すでにパースされたDOコマンドブロックは、フェーズ4から処理されます。
    • GOTOは、以下のロジックで:labelを探します。
      • 第一引数のトークンからラベルをパースする
      • ラベルの次の出現をスキャンする
        • 現在のファイル位置から開始
        • ファイルの終端に達した場合は、ファイルの先頭にループバックし、元の開始点まで継続します。
      • スキャンは最初に見つかったラベルの位置で停止し、ファイルポインタはそのラベルの直後の行に設定されます。スクリプトの実行はその時点から再開される。真のGOTOが成功すると、FORループを含む解析されたコードブロックが直ちに中止されることに注意してください。
      • ラベルが見つからない場合、またはラベルトークンが見つからない場合、GOTOは失敗し、エラーメッセージが表示され、コールスタックがポップされる。ただし、GOTO に続く現在のコマンドブロック内のすでに解析されたコマンドは、CALLER のコンテキスト (EXIT /B の後に存在するコンテキスト) で実行されます。
      • 参照 https://www.dostips.com/forum/viewtopic.php?t=3803 は、ラベルの解析規則についてのより正確な説明、および https://www.dostips.com/forum/viewtopic.php?t=8988 は、ラベルのスキャンルールについてです。
    • RENAMEとCOPYは、どちらもソースとターゲットのパスにワイルドカードを使用できます。しかし、Microsoftは、特にターゲットパスに対するワイルドカードの動作について、ひどい文書化をしています。有用なワイルドカードの規則が以下にあります。 WindowsのRENAMEコマンドはどのようにワイルドカードを解釈するのですか?
  • 7.2 - ボリューム変更の実行 - コマンドトークンが引用符で始まらず、ちょうど2文字で、2文字目がコロンである場合、ボリュームを変更します。
    • すべての引数トークンは無視されます
    • 最初の文字で指定されたボリュームが見つからない場合、エラーで中断する
    • のコマンドトークンは :: のボリュームを定義するためにSUBSTを使用しない限り、常にエラーとなります。 ::

      SUBSTを使用してボリュームを定義した場合、そのボリュームは :: の場合、ボリュームは変更され、ラベルとしては扱われません。
  • 7.3 - 外部コマンドの実行 - Elseは外部コマンドとして扱おうとする。
    • コマンドラインモードで、コマンドが引用されておらず、ボリューム指定で始まっていない場合、ホワイトスペース。 , , ; , = または + の最初の出現箇所でコマンドトークンを解除します。 <space> , ; または = を返し、残りを引数トークンの前に追加します。
    • コマンドトークンの2文字目がコロンの場合、1文字目で指定されたボリュームが見つかるかどうかを確認します。

      ボリュームが見つからない場合は、エラーで中止します。
    • バッチモードの場合、コマンドトークンが : 7.4 に進みます。

      なお、ラベルトークンが :: のボリュームを定義するためにSUBSTを使用しない限り、前のステップはエラーで中止されるため、このステップに到達することはありません。 :: .
    • 実行する外部コマンドを特定する。
      • これは、カレントボリューム、カレントディレクトリ、PATH変数、PATHEXT変数、またはファイルの関連付けに関わる複雑な処理である可能性があります。
      • 有効な外部コマンドを特定できない場合は、エラーで中止します。
    • コマンドラインモードで、コマンドトークンが : 7.4 に進みます。

      なぜなら、コマンドトークンの先頭が :: のボリュームを定義するためにSUBSTを使用します。 :: で、コマンドトークン全体が外部コマンドへの有効なパスである。
    • 7.3.exec - 外部コマンドを実行する。
  • 7.4 - ラベルを無視する - で始まるコマンドトークンの場合、コマンドとそのすべての引数を無視します。 : .

    7.2および7.3の規則により、ラベルがこのポイントに到達するのを防ぐことができます。

コマンドライン・パーサー。

BatchLine-Parserと同様に動作しますが、以下の点が異なります。

フェーズ 1) パーセント展開。

  • いいえ %* , %1 などの引数展開
  • もしvarが未定義であれば %var% は変更されないままです。
  • を特別に扱うことはありません。 %% . もし var=content ならば %%var%% に展開されます。 %content% .

フェーズ3) 解析されたコマンドをエコーする

  • フェーズ2の後には実行されません。FOR DO コマンド ブロックのフェーズ 4 の後にのみ実行されます。

フェーズ5)ディレイエクスパンション。 DelayedExpansionが有効な場合のみ。

  • var が未定義の場合 !var! は変更されないままです。

フェーズ 7) コマンドの実行

  • labelをCALLまたはGOTOしようとすると、エラーになります。
  • フェーズ7で既に文書化されているように、実行されたラベルは異なるシナリオの下でエラーになる可能性があります。
    • 一括実行されたラベルがエラーを起こすのは、そのラベルが ::
    • コマンドラインで実行されるラベルは、ほとんどの場合、エラーになる

整数値のパース

cmd.exeが文字列から整数値を解析するコンテキストは数多く存在し、そのルールは一貫していません。

  • SET /A
  • IF
  • %var:~n,m% (可変部分文字列展開)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

これらのルールの詳細は、以下をご参照ください。 CMD.EXE が数字を解析するためのルール


cmd.exeの解析ルールを改善したい人のために DosTipsフォーラムにあるディスカッショントピック ここで問題を報告し、提案をすることができます。

お役に立てれば幸いです。

Jan Erik (jeb) - 原著者、位相の発見者

Dave Benham (dbenham) - 多くの追加コンテンツや編集を行いました。