Windowsコマンドインタープリター(CMD.EXE)はスクリプトをどのように解析しますか?


142

私は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=aecho %a %b% c%%a a c%
    • set a =becho %a %b% c%bb c%
  • この変数に二重引用符が含まれている場合、変数が単一の引数としてコマンドに渡されるようにするにはどうすればよいですか?
  • setコマンドの使用時に変数はどのように保存されますか?たとえば、私がしset a=a" b、それからecho.%a%私が得るならa" b。ただしecho.exe、UnxUtilsから使用すると、が表示されますa b。どうやっ%a%て別の方法で拡張するのですか?

ライトありがとうございます。


回答:


200

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

バッチラインパーサー:

次に、バッチファイルの行パーサーのフェーズの概要を示します。

フェーズ0)読み取り行:

フェーズ1)拡張率:

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

フェーズ3)解析されたコマンドをエコーします。コマンドブロックがで始まらず@、前のステップの開始時にECHOがオンであった場合のみ。

フェーズ4)FOR %X変数の展開: FORコマンドがアクティブで、DOの後のコマンドが処理されている場合のみ。

フェーズ5)遅延拡張:遅延拡張が有効な場合のみ

フェーズ5.3)パイプ処理:コマンドがパイプのいずれかの側にある場合のみ

フェーズ5.5)リダイレクトの実行:

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

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


各フェーズの詳細は次のとおりです。

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

フェーズ0)ラインの読み取り:最初の入力ラインを読み取り<LF>ます。

  • コマンドとして解析される行を読み取る場合、<Ctrl-Z>(0x1A)は<LF>(LineFeed 0x0A)として読み取られます
  • :labelのスキャン中にGOTOまたはCALLが行を読み取るとき<Ctrl-Z>、はそれ自体として扱われます-に変換されません<LF>

フェーズ1)拡張率:

  • ダブル%%はシングルに置き換えられます%
  • 引数の拡張(%*%1%2、など)
  • の展開%var%。varが存在しない場合は、何も置き換えません
  • 行は展開<LF>内ではなく最初は切り捨てられます%var%
  • 完全な説明については、この前半のdbenham Same thread:Percent Phaseをお読みください。

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

このフェーズ全体で重要な概念があります。

  • トークンは、1つの単位として扱われる文字列です。
  • トークンはトークン区切り文字で区切られます。標準のトークン区切り文字は次のとおりで<space> <tab> ; , = <0x0B> <0x0C>あり、<0xFF>
    連続するトークン区切り文字は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>はコマンドブロックの次の行の一部として扱われます。
      • 行バッファーの終わりにある場合、次の行が読み取られ、スペースに追加されます。
  • 特殊文字の1つ& | <またはの場合>、パイプ、コマンドの連結、およびリダイレクトを処理するために、この時点で行を分割します。
    • パイプ(|)の場合、各サイドは、フェーズ5.3で特別な処理を行う個別のコマンド(またはコマンドブロック)です。
    • 以下の場合&&&または||コマンド連結、連結の各側は、別のコマンドとして扱われます。
    • 以下の場合には<<<>、または>>リダイレクト、リダイレクト句は、解析された一時的に除去し、その後、現在のコマンドの最後に追加されます。リダイレクト句は、オプションのファイルハンドル番号、リダイレクト演算子、リダイレクト先トークンで構成されます。
      • リダイレクト演算子の前のトークンがエスケープされていない単一の数字である場合、その数字はリダイレクトされるファイルハンドルを指定します。ハンドルトークンが見つからない場合、出力リダイレクトはデフォルトで1(stdout)になり、入力リダイレクトはデフォルトで0(stdin)になります。
  • このコマンドの最初のトークン(リダイレクトを最後に移動する前)がで始まる@場合、に@は特別な意味があります。(@他のコンテキストでは特別ではありません)
    • スペシャル@は削除されました。
    • ECHOがONの場合、このコマンドは、この行の後続の連結コマンドとともに、フェーズ3エコーから除外されます。@が開始の前にある場合、(括弧で囲まれたブロック全体がフェーズ3エコーから除外されます。
  • 括弧の処理(複数行にわたる複合ステートメントを提供します):
    • パーサーがコマンドトークンを探していない場合、(特別ではありません。
    • パーサーがコマンドトークンを探してを見つけた(場合、新しい複合ステートメントを開始し、かっこカウンターをインクリメントします
    • 括弧カウンターが> 0の場合)、複合ステートメントを終了し、括弧カウンターをデクリメントします。
    • 行の終わりに達し、かっこカウンターが0より大きい場合、次の行が複合ステートメントに追加されます(フェーズ0から再び始まります)。
    • かっこカウンターが0で、パーサーがコマンドを探している場合、トークン区切り文字、特殊文字、改行、またはファイルの終わりが直後に続く限り)REMステートメントと同様に機能します。
      • すべての特殊文字は、その意味を失います^(行の連結が可能です)。
      • 論理行の終わりに達すると、「コマンド」全体が破棄されます。
  • 各コマンドは一連のトークンに解析されます。最初のトークンは常にコマンドトークンとして扱われます(特殊@が削除され、リダイレクトが最後に移動された後)。
    • コマンドトークンの前の先行トークン区切り文字が削除されます
    • コマンドトークンを解析する場合(、標準のトークン区切り文字に加えて、コマンドトークン区切り文字として機能します。
    • 後続のトークンの処理は、コマンドによって異なります。
  • ほとんどのコマンドは、コマンドトークンの後のすべての引数を単一の引数トークンに連結するだけです。すべての引数トークン区切り文字が保持されます。引数オプションは通常、フェーズ7まで解析されません。
  • IF、FOR、およびREMという3つのコマンドが特別な処理を行う
    • IFは、個別に処理される2つまたは3つの異なる部分に分割されます。IF構文の構文エラーは、致命的な構文エラーになります。
      • 比較演算は、フェーズ7に至るまで実際に流れる実際のコマンドです。
        • すべてのIFオプションは、フェーズ2で完全に解析されます。
        • 連続するトークン区切り文字は1つのスペースに折りたたまれます。
        • 比較演算子に応じて、1つまたは2つの値トークンが識別されます。
      • Trueコマンドブロックは、条件の後のコマンドのセットであり、他のコマンドブロックと同様に解析されます。ELSEを使用する場合は、Trueブロックを括弧で囲む必要があります。
      • オプションのFalseコマンドブロックは、ELSEの後のコマンドのセットです。繰り返しますが、このコマンドブロックは通常どおり解析されます。
      • TrueおよびFalseコマンドブロックは、後続のフェーズに自動的には流れません。その後の処理はフェーズ7で制御されます。
    • FORはDOの後に2つに分割されます。FOR構文の構文エラーは、致命的な構文エラーになります。
      • DOまでの部分は、フェーズ7までずっと流れる実際のFOR反復コマンドです。
        • すべてのFORオプションは、フェーズ2で完全に解析されます。
        • 括弧で囲まれたIN句は<LF>として扱われ<space>ます。IN句が解析された後、すべてのトークンが連結されて単一のトークンが形成されます。
        • エスケープされていない、引用符で囲まれていない連続するトークン区切り文字は、DOを介してFORコマンド全体で1つのスペースに折りたたまれます。
      • DOの後の部分は、通常解析されるコマンドブロックです。DOコマンドブロックの後続の処理は、フェーズ7の反復によって制御されます。
    • フェーズ2で検出されたREMは、他のすべてのコマンドとは劇的に異なります。
      • 解析される引数トークンは1つだけです-パーサーは最初の引数トークンの後の文字を無視します。
      • REMコマンドはフェーズ3の出力に表示される場合がありますが、コマンドは実行されず、元の引数テキストがエコーされます-エスケープキャレットは削除されませんが、...
        • ^行を終了するエスケープなしで終了する引数トークンが1つしかない場合、引数トークンは破棄され、次の行が解析されてREMに追加されます。これは、複数のトークンが存在するか、最後の文字が存在しなくなるまで繰り返されます^
  • コマンドトークンがで始まり:、これがフェーズ2の最初のラウンドである場合(フェーズ6のCALLによる再起動ではない場合)
    • トークンは通常、未実行ラベルとして扱われます。
      • 行の残りの部分は、しかし、解析され)<>&|、もはや特別な意味を持っていません。行の残り全体は、ラベル「コマンド」の一部と見なされます。
      • ^引き続き特殊です。つまり、行の継続を使用して、後続の行をラベルに追加できます。
      • 括弧で囲まれたブロック内の実行ラベルは、次の行にコマンドまたは実行ラベルが直後に続いていない限り、致命的な構文エラーになります。
        • (Unexecuted Labelに続く最初のコマンドには特別な意味はありません。
      • ラベルの解析が完了すると、コマンドは中止されます。ラベルの後続フェーズは発生しません
    • フェーズ2で見つかったラベルがフェーズ7まで解析を続行する実行済みラベルとして扱われる原因となる3つの例外があります。
      • ラベルトークンの前にリダイレクトがあり、そこにある|パイプや&&&あるいは||ライン上のコマンドの連結が。
      • ラベルトークンの前にリダイレクトがあり、コマンドは括弧で囲まれたブロック内にあります。
      • labelトークンは、括弧で囲まれたブロック内の行の最初のコマンドであり、上記の行はUnexecuted Labelで終わりました。
    • フェーズ2で実行済みラベルが検出さ れると、次のようになります。
      • ラベル、その引数、およびそのリダイレクトは、フェーズ3のエコー出力からすべて除外されます
      • その行の後続の連結コマンドはすべて完全に解析されて実行されます。
    • 詳細については実行されるラベル未実行ラベル、参照https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405を

フェーズ3)解析されたコマンドをエコーします。コマンドブロックがで始まらず@、前のステップの開始時にECHOがオンであった場合のみ。

フェーズ4)FOR %X変数の展開: FORコマンドがアクティブで、DOの後のコマンドが処理されている場合のみ。

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

----この時点以降、フェーズ2で識別された各コマンドは個別に処理されます。
----フェーズ5から7は、次のコマンドに進む前に、1つのコマンドに対して完了しています。

フェーズ5)遅延拡張:遅延拡張がオンの場合にのみ、コマンドはパイプの両側の括弧で囲まれたブロック内になく、コマンドは「ネイキッド」バッチスクリプト(括弧なしのスクリプト名、CALL、コマンド連結、またはパイプ)。

  • コマンドの各トークンは、遅延拡張について個別に解析されます。
    • ほとんどのコマンドは、2つ以上のトークン(コマンドトークン、引数トークン、および各リダイレクト先トークン)を解析します。
    • FORコマンドは、IN句トークンのみを解析します。
    • IFコマンドは比較値のみを解析します-比較演算子に応じて1つまたは2つ。
  • 解析されたトークンごとに、まず、トークンが含まれているかどうかを確認します!。そうでない場合、トークンは解析されません- ^文字にとって重要です。トークンにが含まれている場合は、!各文字を左から右にスキャンします。
    • キャレット(^)の場合、次の文字に特別な意味がない場合は、キャレット自体が削除されます
    • 感嘆符の場合は、次の感嘆符を検索し(キャレットはもう観察されません)、変数の値に展開します。
      • 連続オープニング !は単一に折りたたまれます!
      • ペアリング!されていない残りは削除されます
    • この段階で変数を展開することは「安全」です。特殊文字が検出されなくなったためです(<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は続行しません
    • 二重にされたキャレットは、引用されない限り、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が呼び出される内容に影響を与える可能性があります。ただし、%0などのトークンはすでにセットアップされているため、CALLされたルーチンに渡される引数は変更されません。
      • GOTOラベルを実行して、ファイルポインターをサブルーチンの先頭に配置します(:labelに続く可能性のある他のトークンはすべて無視します)GOTOの動作規則については、フェーズ7を参照してください。
        • :labelトークンがない場合、または:labelが見つからない場合は、呼び出しスタックがすぐにポップされて、保存されたファイルの位置が復元され、CALLは中止されます。
        • :labelに/?が含まれている場合、:labelを検索する代わりにGOTOヘルプが出力されます。ファイルポインタは移動しません。CALLの後のコードは2回実行され、1回はCALLコンテキストで実行され、その後CALLが戻った後に再度実行されます。CALLがこのスクリプトでGOTOヘルプメッセージを出力する理由と、その後のコマンドが2回実行される理由を参照してください詳細については。
    • それ以外の場合は、指定されたバッチスクリプトに制御を渡します。
    • CALLed:labelまたはスクリプトの実行は、EXIT / Bまたはファイルの終わりに到達するまで続きます。その時点でCALLスタックがポップされ、保存されたファイルの位置から実行が再開されます。
      フェーズ7は、CALLされたスクリプトまたは:labelsに対しては実行されません。
  • それ以外の場合、フェーズ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 -> value = content
        最初の等号と最後の引用符の間のテキストがコンテンツとして使用されます(最初の等号と最後の引用符は除外されます)。最後の引用の後のテキストは無視されます。等号の後に引用符がない場合は、行の残りがコンテンツとして使用されます。
      • SETコマンドの名前の前に引用符がない場合
        set name="content" not ignored -> value = "content" not ignored
        存在する可能性のあるすべての引用符を含む、equalの後の行の残り全体がコンテンツとして使用されます。
    • IF比較が評価され、条件がtrueまたはfalseのどちらであるかに応じて、適切に解析済みの依存コマンドブロックがフェーズ5から処理されます。
    • FORコマンドのIN句は適切に繰り返されます。
      • これがコマンドブロックの出力を繰り返すFOR / Fの場合、次のようになります。
        • IN句は、CMD / Cを介して新しいcmd.exeプロセスで実行されます。
        • コマンドブロックは、もう一度、全体の解析プロセスを通過する必要がありますが、今回はコマンドラインコンテキストで
        • ECHOはONで始まり、遅延した拡張は通常は無効で始まります(レジストリ設定によって異なります)。
        • 子cmd.exeプロセスが終了すると、IN句コマンドブロックによって行われたすべての環境変更は失われます
      • 各反復について:
        • FOR変数の値が定義されています
        • 次に、既に解析されたDOコマンドブロックがフェーズ4から処理されます。
    • GOTOは次のロジックを使用して:labelを見つけます
      • ラベルは最初の引数トークンから解析されます
      • スクリプトは、ラベルが次に出現するかどうかスキャンされます
        • スキャンは現在のファイル位置から開始します
        • ファイルの終わりに達すると、スキャンはファイルの最初にループバックし、元の開始点まで続行します。
      • スキャンは、最初に見つかったラベルで停止し、ファイルポインターはラベルの直後の行に設定されます。スクリプトの実行はその時点から再開されます。真のGOTOが成功すると、解析されたコードのブロック(FORループを含む)が即座に中止されることに注意してください。
      • ラベルが見つからない場合、またはラベルトークンがない場合、GOTOは失敗し、エラーメッセージが出力され、呼び出しスタックがポップされます。これは事実上、EXIT / Bとして機能しますが、GOTOに続く現在のコマンドブロックで既に解析されているコマンドは実行されますが、CALLerのコンテキスト(EXIT / Bの後に存在するコンテキスト)で実行されます。
      • ラベルの解析に使用されるルールの詳細については、https://www.dostips.com/forum/viewtopic.php?f = 3&t = 3803を参照してください
    • RENAMEとCOPYはどちらも、ソースパスとターゲットパスにワイルドカードを使用できます。しかし、Microsoftは、特にターゲットパスに対して、ワイルドカードがどのように機能するかを文書化するひどい仕事をしています。ワイルドカードルールの便利なセットは、WindowsのRENAMEコマンドでワイルドカードがどのように解釈されるのですか?
  • 7.2-ボリュームの変更を実行 -コマンドトークンが引用符で始まらず、長さがちょうど2文字で、2番目の文字がコロンの場合は、ボリュームを変更します。
    • すべての引数トークンは無視されます
    • 最初の文字で指定されたボリュームが見つからない場合は、エラーで中止します
    • のコマンドトークンは、::SUBSTを使用してのボリュームを定義しない限り、常にエラーになります。SUBSTを使用してのボリュームを定義した::
      場合::、ボリュームは変更され、ラベルとして扱われません。
  • 7.3-外部コマンドの実行-そうでない場合、コマンドを外部コマンドとして扱います。
    • コマンドラインモードとコマンドに引用されておらず、ボリューム仕様、ホワイトスペースで始まらない場合は、,;=または+その後の最初の発生時にトークンコマンド破壊<space> , ;又は=および(S)トークン引数に残りを付加。
    • コマンドトークンの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の後には実行されません。FORDOコマンドブロックのフェーズ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)-多くの追加コンテンツと編集が役立つことを願って


4
こんにちはジェブ、あなたの洞察をありがとう...理解するのは難しいかもしれませんが、私はそれをじっくりと考えてみます!あなたは多くのテストを実行したようです!(翻訳ありがとうございadministrator.de/...を
ブノワ

2
バッチフェーズ5)-%% aはフェーズ1ですでに%aに変更されているため、forループ展開は実際に%aを展開します。また、バッチフェーズ1の詳細な説明を以下の回答に追加しました(編集権限を持っていません)
dbenham

3
Jeb-フェーズ0を移動してフェーズ6と組み合わせることができますか?それは私にはもっと理にかなっています、またはそれらがそのように分離されている理由はありますか?
dbenham 2012年

1
@aschipfl-そのセクションを更新しました。)本当にほとんどのような機能をしてREMカッコカウンタは、コマンドラインからこれらの両方を試してみてください0のときのコマンド:) Ignore this、およびecho OK & ) Ignore this
dbenham

1
@aschipflはい、それは正しいので、時々 'set "var =%expr%"が表示されます!「感嘆符が削除されますが、最後の力フェーズ5
ジェブ

62

コマンドウィンドウからコマンドを呼び出す場合、コマンドライン引数のトークン化はcmd.exe(別名 "シェル")では行われません。ほとんどの場合、トークン化は新しく形成されたプロセスのC / C ++ランタイムによって行われますが、必ずしもそうであるとは限りません。たとえば、新しいプロセスがC / C ++で記述されていない場合、または新しいプロセスが無視argvして処理することを選択した場合自身の生のコマンドライン(例:GetCommandLine()を使用))。OSレベルでは、Windowsはトークン化されていないコマンドラインを単一の文字列として新しいプロセスに渡します。これは、ほとんどの* nixシェルとは対照的です。シェルは、引数を新しく形成されたプロセスに渡す前に、一貫した予測可能な方法で引数をトークン化します。つまり、個々のプログラムは引数のトークン化を自分の手に持っていることが多いため、Windowsのさまざまなプログラム間で引数のトークン化動作が大きく異なる可能性があります。

それが無政府状態のように聞こえるなら、それは一種のことです。ただし、多数のWindowsプログラム Microsoft C / C ++ランタイムのを利用しargvているため、MSVCRTが引数をトークン化する方法を理解することは、一般的に役立ちます。以下は抜粋です。

  • 引数は、スペースまたはタブである空白で区切られます。
  • 二重引用符で囲まれた文字列は、その中に含まれる空白に関係なく、1つの引数として解釈されます。引用符で囲まれた文字列を引数に埋め込むことができます。キャレット(^)はエスケープ文字または区切り文字として認識されないことに注意してください。
  • バックスラッシュ\ "が前に付いた二重引用符は、リテラルの二重引用符(")として解釈されます。
  • 円記号は、二重引用符の直前にない限り、文字どおりに解釈されます。
  • 偶数個のバックスラッシュの後に二重引用符が続く場合、バックスラッシュ(\)のペアごとに1つのバックスラッシュ()がargv配列に配置され、二重引用符( ")は文字列区切り文字として解釈されます。
  • 奇数のバックスラッシュの後に二重引用符が続く場合、バックスラッシュ(\)のペアごとに1つのバックスラッシュ()がargv配列に配置され、二重引用符は残りのバックスラッシュによってエスケープシーケンスとして解釈され、 argvに配置されるリテラルの二重引用符( ")。

マイクロソフトの「バッチ言語」(.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]は常に実行可能ファイルの名前であり、簡潔にするために以下では省略されています。WindowsXP SP3でテストされています。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]

お返事ありがとうございます。TinyPerlがプログラムの出力を出力しないことを理解する[a "b" c]ことは、私をさらに困惑させます。私は、後処理を[a "b] [c]行う方法を理解するのが困難です。
Benoit

今考えてみると、このコマンドラインのトークン化はおそらくCランタイムによって完全に行われています。実行可能ファイルは、Cランタイムを使用しないように作成することもできます。その場合、コマンドラインをそのまま処理し、独自のトークン化を実行する必要があります(必要な場合)。またはアプリケーションがCランタイムを使用する場合、argcとargvを無視して、Win32などを介して生のコマンドラインを取得することを選択できますGetCommandLine。おそらくTinyPerlはargvを無視し、独自のルールによって生のコマンドラインを単純にトークン化しています。
Mike Clark、

4
「Win32の観点から見ると、コマンドラインは新しいプロセスのアドレススペースにコピーされる文字列にすぎないことに注意してください。起動プロセスと新しいプロセスがこの文字列を解釈する方法は、規則ではなく規則によって制御されます。」-Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
本当にいい答えをありがとう。それは私の意見の多くを説明しています。そしてそれはまた、なぜ私が時々Windowsで動作することが本当にくだらないと思うのかを説明しています...
Benoit

Win32 C ++プログラムの場合、コマンドラインからargvへの変換中にバックスラッシュと引用符に関してこれを見つけました。バックスラッシュの数は、最後のバックスラッシュの後にdblquoteが続く場合にのみ2で除算され、前に偶数のバックスラッシュがある場合、dblquoteは文字列を終了します。
Benoit

47

パーセント拡張ルール

jebの回答のフェーズ1の詳細な説明を次に示します(バッチモードとコマンドラインモードの両方で有効)。

フェーズ1)パーセント拡張 左から、各文字をスキャンして、%またはを探し<LF>ます。見つかった場合

  • 1.05(で行を切り捨て<LF>
    • キャラクターが<LF>その後 なら
      • ドロップ(無視する)から行の残りの部分<LF>以降
      • Goto Phase 1.5(Strip <CR>
    • %そうでない場合、文字はでなければならないため、1.1に進みます。
  • % コマンドラインモードの場合、1.1(エスケープ)はスキップされます
    • バッチモードならば、別のに続いて%、その後
      元に戻し%%、単一で%、スキャン継続
  • 1.2(引数を展開) コマンドラインモードの場合はスキップ
    • そうでなければ、バッチモードの場合
      • 続く場合は*、コマンド拡張機能が、その後有効になっている
        置き換え%*、すべてのコマンドライン引数のテキストに(引数がない場合は何も交換)し、スキャンを続行。
      • そうでない場合は、<digit>その後に引数値で
        置き換え%<digit>(未定義の場合は何も置き換えない)、スキャンを続行します。
      • そうでなければ、その後に続く~コマンド拡張機能が有効になっている場合
        • 引数修飾子のオプションの有効なリストに続いて必須の<digit>場合、変更された引数値で
          置換%~[modifiers]<digit>し(定義されていない場合、または指定された$ PATH:修飾子が定義されていない場合は何も置換しない)、スキャンを続行します。
          注:修飾子は大文字と小文字を区別せず、任意の順序で複数回出現できます。ただし、$ PATH:修飾子は一度だけ出現でき、前の最後の修飾子でなければなりません。<digit>
        • そうしないと、無効な変更された引数構文により致命的なエラーが発生します。解析されたすべてのコマンドが中止され、バッチモードの場合はバッチ処理が中止されます。
  • 1.3(変数を展開)
    • それ以外の場合、コマンド拡張機能が無効になっている場合は
      、次の文字列を調べて%、バッファの前または最後を区切り、VAR(空のリストである可能性があります)と呼びます。
      • 次の文字がある場合は%、その後
        • VARが定義されている場合は、VARの値に
          置き換え%VAR%てスキャンを続行します
        • それ以外の場合、バッチモードの場合は、スキャンを
          削除%VAR%して続行し ます
        • その他goto 1.4
      • その他goto 1.4
    • それ以外の場合、コマンド拡張機能が有効になっている場合は
      、次の文字列を調べて% :、バッファの前または最後を壊し、それらをVAR(空のリストである場合があります)と呼びます。VARが前に壊れた場合:し、後続の文字がVARの最後の文字として%含ま:れ、前にブレークする場合%
      • 次の文字がある場合は%、その後
        • VARが定義されている場合
          %VAR%ている場合は、VARの値に 置き換えてスキャンを続行します
        • そうでなければ、バッチモードの場合
          %VAR%は、スキャンを 削除して続行し
        • その他goto 1.4
      • そうでなければ、次の文字が :、その後
        • VARが未定義の場合
          • バッチモードの場合は
            削除%VAR:してスキャンを続行します。
          • その他goto 1.4
        • そうでなければ、次の文字が ~、その後
          • 文字の次の文字列がパターンに一致した場合[integer][,[integer]]%、その後
            に置き換え%VAR:~[integer][,[integer]]%(おそらく空の文字列になる)と、スキャン継続VARの値のサブと。
          • その他goto 1.4
        • そうし=ないと*=
          無効な変数の検索と置換の構文が続く場合、または致命的なエラーが発生します。解析されたすべてのコマンドが中止され、バッチモードの場合はバッチ処理が中止されます。
        • それ以外の場合、次の文字列がのパターンに一致[*]search=[replace]%する場合、検索には以外の=任意の文字セットが含まれ%
          置換には以外の任意の文字セットが含まれ、次に置換%VAR:[*]search=[replace]%検索を実行した後、VARの値とし、(おそらく空の文字列が得られる)を交換して続行スキャン
        • その他goto 1.4
  • 1.4(ストリップ%)
    • それ以外の場合、バッチモードの場合は、
      削除して%、スキャンの後の次の文字からスキャンを続行します。%
    • または、リーディング%を保持し、保持されたリーディングの後の次の文字からスキャンを続行します。%

上記は、このバッチがなぜ

@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!"

遅延拡張ルール

以下は、ジェブの答えのフェーズ5のより正確な説明です。(バッチモードとコマンドラインモードの両方で有効)。

フェーズ5)拡張の遅延

次の条件のいずれかに該当する場合、このフェーズはスキップされます。

  • 遅延拡張は無効になっています。
  • コマンドは、パイプの両側の括弧で囲まれたブロック内にあります。
  • 受信コマンドトークンは「ネイキッド」バッチスクリプトです。つまりCALL、、括弧で囲まれたブロック、コマンド連結(&&&または||)、パイプのいずれにも関連付けられていません|

遅延拡張プロセスは、トークンに個別に適用されます。コマンドには複数のトークンがある場合があります。

  • コマンドトークン。ほとんどのコマンドでは、コマンド名自体がトークンです。ただし、いくつかのコマンドには、フェーズ5のトークンと見なされる特殊な領域があります。
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKENここで、比較の一つである==equneqlssleqgtr、又はgeq
  • 引数トークン
  • リダイレクトの宛先トークン(リダイレクトごとに1つ)

を含まないトークンは変更されません!

少なくとも1つを含むトークンごとに!、各文字を左から右にスキャンして^orを!探し、見つかった場合は

  • 5.1(キャレットエスケープ)!またはに必要^リテラル
    • 文字がキャレットの場合 ^場合
      • を削除します ^
      • 次の文字をスキャンして、リテラルとして保持します
      • スキャンを続行
  • 5.2(展開変数)
    • キャラクターが !場合、
      • コマンド拡張機能が無効になっている場合は
        、次の文字列を調べて、前に改行する!か、<LF>調べて、区切り、VAR(空のリストである可能性があります)
        • 次の文字が !、その後
          • VARが定義されている場合は、
            置換!VAR!ている場合は、VARの値にてスキャンを続行します
          • バッチモードの場合は
            削除!VAR!は、スキャンをして続行し
          • その他goto 5.2.1
        • その他goto 5.2.1
      • それ以外の場合、コマンド拡張機能が有効になっている場合は
        、次の文字列を調べて、、、、またはの前!に区切り、VAR(空のリストの場合があります)と呼びます。VARが前にブレークし、その後に続く文字がVARの最後の文字として含まれ、前にブレークする場合:<LF>:!:!
        • 次の文字が !、その後
          • VARが存在する場合、
            置換!VAR!値にてスキャンを続行します
          • バッチモードの場合は
            削除!VAR!は、スキャンをして続行し
          • その他goto 5.2.1
        • そうでなければ、次の文字が :、その後
          • VARが未定義の場合
            • バッチモードの場合は
              削除!VAR:してスキャンを続行し
            • その他goto 5.2.1
          • そうでなければ、次の文字が ~、その後
            • 次の文字列がのパターンに一致する場合、VARの値の部分文字列で[integer][,[integer]]!置換!VAR:~[integer][,[integer]]!し(空の文字列になる可能性があります)、スキャンを続行します。
            • その他goto 5.2.1
          • それ以外の場合、次の文字列がのパターンに一致[*]search=[replace]!する場合、検索には以外の=任意の文字セットが含まれ!
            置換には以外の任意の文字セットが含まれ、次に置換!VAR:[*]search=[replace]!検索と置換を実行した後、VARの値でします(空の文字列になる可能性があります)。スキャンを続ける
          • その他goto 5.2.1
        • その他goto 5.2.1
      • 5.2.1
        • バッチモードの場合、先頭の!
          Elseを削除し、先頭を保持します。!
        • 保存された先頭の次の文字からスキャンを続行します !

3
+ 1、%definedVar:a=b%vs %undefinedVar:a=b%%var:~0x17,-010%フォームのコロン構文とルールのみがここにありません
jeb

2
良い点-私はあなたの懸念に対処するために変数展開セクションを拡張しました。また、不足している詳細を埋めるために、引数の展開セクションを拡張しました。
dbenham

2
jebからいくつかのプライベートフィードバックを受け取った後、コロンで終わる変数名のルールを追加し、メモ2を追加しました。メモ3も、面白くて重要だと思ったので追加しました。
dbenham

1
@aschipfl-ええ、私はそれについてより詳細に検討することを検討しましたが、そのうさぎの穴に行きたくありませんでした。[整数]という用語を使用したとき、私は故意にコミットしませんでした。CMD.EXEが数値を解析する方法についてはルールに詳細があります
dbenham 2016

1
私のような変数名の最初の文字のための予約文字が存在しないことのように、CMDコンテキスト用の拡張ルールを欠けている%<digit>%*または%~。また、未定義の変数の動作が変わります。おそらく、2つ目の回答を開く必要があります
2017年

7

指摘したように、コマンドにはμ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"""]

クイズ:環境変数の値を単一の引数(つまりas %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)の文字が。


%t%を単一の引数として渡すには、 "%t:" = \ "%"を使用できます。つまり、変数の展開には%VAR:str = replacement%構文を使用します。|のようなシェルのメタ文字 そして、&あなたは再びそれらをエスケープしない限り、変数の内容はまだ、けれどもシェルまで露出して混乱することができますで...
Toughy

@Toughyつまり、私の例でta "b cです。あなたがそれらの6つの文字を取得するためのレシピを持っていますか(a2×スペースは、"b、およびc)として登場する%1内部.cmd?私はあなたの考えも好きです。args "%t:"=""%"かなり近いです:-)
bobbogo 2018年

5

あなたはすでにいくつかの素晴らしい答えを持っていますが、あなたの質問の一部に答えるには:

set a =b, echo %a %b% c% → bb c%

何が起こっているのかというと、=の前にスペースがあるため、として正しく評価され%a<space>% たときに呼び出される変数が作成されます。echo %a %b

残りの部分b% c%は、プレーンテキスト+未定義の変数として評価されます% c%。これは、入力したとおりにエコーする必要がありecho %a %b% c%ます。bb% c%

変数名にスペースを含める機能は、計画された「機能」よりも見落としが多いと思います


0

編集:受け入れられた回答を参照してください、以下は間違っており、コマンドラインをTinyPerlに渡す方法のみを説明しています。


引用について、私は行動が次のようであると感じています:

  • a "が見つかると、文字列のグロビングが始まります
  • 文字列のグロビングが発生した場合:
    • でないすべての文字"がグロブされる
    • a "が見つかった場合:
      • ""(したがってtriple ")が後に続く場合、二重引用符が文字列に追加されます
      • その後に"(したがってdouble ")が続く場合、二重引用符が文字列に追加され、文字列のグロビングが終了します
      • 次の文字がでない場合"、文字列のグロビングは終了します
    • 行が終了すると、文字列のグロビングが終了します。

要するに:

"a """ b "" c"""2つの文字列で構成されます:a " b "およびc"

"a"""a"""そして"a""""すべて同じ文字列であるかの行の末尾


トークナイザーと文字列のグロビングはコマンドに依存します!「セット」は「コール」または「イフ」とは異なる働きをします
jeb

はい、しかし外部コマンドはどうですか?cmd.exeは常に同じ引数をそれらに渡すと思いますか?
Benoit

1
cmd.exeは常に拡張結果をトークンではなく文字列として外部コマンドに渡します。それは外部コマンドに依存してそれをエスケープしてトークン化する方法に依存します、findstrはバックスラッシュを使用し、次は別のものを使用できます
jeb

0

マイクロソフトがターミナルのソースコードを公開していることに注意してください。構文解析に関しては、コマンドラインと同様に機能します。たぶん誰かが端末の解析ルールに従ってリバースエンジニアリングされた解析ルールをテストすることに興味があります。

ソースコードへのリンク

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.