165

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%違う方法で拡大するのですか?

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

4

8 に答える 8

244

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

バッチラインパーサー:

バッチファイルラインパーサーのフェーズの概要は次のとおりです。

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

フェーズ1)拡張率:

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

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

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

フェーズ5)遅延拡張:遅延拡張が有効になっている場合のみ

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

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

フェーズ6)CALL処理/ Caretダブリング:コマンドトークンが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からこの前半を読んでください。同じスレッド:パーセントフェーズ

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

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

  • トークンは、1つの単位として扱われる単なる文字列です。
  • トークンはトークン区切り文字で区切られます。標準のトークン区切り文字は<space> <tab> ; , = <0x0B> <0x0C>であり、<0xFF>
    連続するトークン区切り文字は1つとして扱われます-トークン区切り文字の間に空のトークンはありません
  • 引用符で囲まれた文字列内にトークン区切り文字はありません。引用符で囲まれた文字列全体は、常に単一のトークンの一部として扱われます。単一のトークンは、引用符で囲まれた文字列と引用符で囲まれていない文字の組み合わせで構成されている場合があります。

次の文字は、コンテキストに応じて、このフェーズで特別な意味を持つ場合があります。<CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

各キャラクターを左から右に見てください。

  • その後、それ<CR>が存在しなかったかのように削除します(奇妙なリダイレクト動作を除く)
  • カレット(^)の場合、次の文字がエスケープされ、エスケープしているカレットが削除されます。エスケープ文字はすべての特別な意味を失います(を除く<LF>)。
  • 引用符(")の場合は、引用符フラグを切り替えます。引用フラグがアクティブな場合は、"とのみ<LF>が特別です。他のすべての文字は、次の引用符が引用符フラグをオフに切り替えるまで、特別な意味を失います。終了引用符をエスケープすることはできません。引用符で囲まれた文字はすべて、常に同じトークン内にあります。
  • <LF>常に見積もりフラグをオフにします。他の動作はコンテキストによって異なりますが、引用符によって。の動作が変わることはありません<LF>
    • エスケープ<LF>
      • <LF>剥ぎ取られる
      • 次のキャラクターはエスケープされます。行バッファの終わりにある場合、次の行はフェーズ1と1.5によって読み取られて処理され、次の文字をエスケープする前に現在の行に追加されます。次の文字が<LF>、の場合、それはリテラルとして扱われます。つまり、このプロセスは再帰的ではありません。
    • <LF>括弧内に エスケープされていない
      • <LF>が削除され、現在の行の解析が終了します。
      • 行バッファに残っている文字はすべて無視されます。
    • FORIN<LF>括弧で囲まれたブロック内で エスケープ解除
      • <LF>に変換されます<space>
      • 行バッファの最後にある場合は、次の行が読み取られ、現在の行に追加されます。
    • <LF>括弧で囲まれたコマンドブロック内で エスケープ解除
      • <LF>はに変換され<LF><space><space>はコマンドブロックの次の行の一部として扱われます。
      • 行バッファーの終わりにある場合は、次の行が読み取られ、スペースに追加されます。
  • 特殊文字& | <またはのいずれかである場合は>、パイプ、コマンドの連結、およびリダイレクトを処理するために、この時点で行を分割します。
    • パイプ(|)の場合、各側は個別のコマンド(またはコマンドブロック)であり、フェーズ5.3で特別な処理が行われます。
    • 、、、またはコマンド連結の場合、&連結の各側は個別のコマンドとして扱われます。&&||
    • 、、、、またはリダイレクトの場合<、リダイレクト句が解析され、一時的に削除されてから、現在のコマンドの最後に追加されます。リダイレクト句は、オプションのファイルハンドル桁、リダイレクト演算子、およびリダイレクト先トークンで構成されます。 <<>>>
      • リダイレクト演算子の前にあるトークンが1つのエスケープされていない数字である場合、その数字はリダイレクトされるファイルハンドルを指定します。ハンドルトークンが見つからない場合、出力リダイレクトはデフォルトで1(stdout)になり、入力リダイレクトはデフォルトで0(stdin)になります。
  • このコマンドの最初のトークン(リダイレクトを最後に移動する前)がで始まる@場合、@は特別な意味を持ちます。(@他のコンテキストでは特別ではありません)
    • スペシャル@は削除されます。
    • ECHOがオンの場合、このコマンドは、この行の後続の連結コマンドとともに、フェーズ3エコーから除外されます。@が開始の前にある場合、(括弧で囲まれたブロック全体がフェーズ3エコーから除外されます。
  • プロセス括弧(複数行にわたる複合ステートメントを提供します):
    • パーサーがコマンドトークンを探していない場合、それ(は特別なことではありません。
    • パーサーがコマンドトークンを探していて、を見つけた(場合は、新しい複合ステートメントを開始し、括弧カウンターをインクリメントします。
    • 括弧カウンターが>0の場合)、複合ステートメントを終了し、括弧カウンターをデクリメントします。
    • 行の終わりに到達し、括弧カウンターが> 0の場合、次の行が複合ステートメントに追加されます(フェーズ0から再開します)
    • 括弧カウンターが0で、パーサーがコマンドを探している場合、トークン区切り文字、特殊文字、改行、またはファイルの終わりが直後に続く限り )、ステートメントと同様に機能します。REM
      • ^(行の連結が可能)を除いて、すべての特殊文字は意味を失います。
      • 論理行の終わりに達すると、「コマンド」全体が破棄されます。
  • 各コマンドは一連のトークンに解析されます。最初のトークンは常にコマンドトークンとして扱われます(特別な@ものが削除され、リダイレクトが最後に移動された後)。
    • コマンドトークンの前の先頭のトークン区切り文字が削除されます
    • コマンドトークンを解析する場合(、標準のトークン区切り文字に加えて、コマンドトークン区切り文字として機能します
    • 後続のトークンの処理は、コマンドによって異なります。
  • ほとんどのコマンドは、コマンドトークンの後のすべての引数を単一の引数トークンに連結するだけです。すべての引数トークンの区切り文字は保持されます。引数オプションは通常、フェーズ7まで解析されません。
  • 3つのコマンド(IF、FOR、およびREM)が特別に処理されます
    • IFは、独立して処理される2つまたは3つの別個の部分に分割されます。IF構文の構文エラーは、致命的な構文エラーになります。
      • 比較操作は、フェーズ7まで流れる実際のコマンドです。
        • すべてのIFオプションは、フェーズ2で完全に解析されます。
        • 連続するトークン区切り文字は、単一のスペースに折りたたまれます。
        • 比較演算子に応じて、識別される1つまたは2つの値トークンがあります。
      • Trueコマンドブロックは、条件の後のコマンドのセットであり、他のコマンドブロックと同様に解析されます。ELSEを使用する場合は、Trueブロックを括弧で囲む必要があります。
      • オプションのFalseコマンドブロックは、ELSEの後のコマンドのセットです。この場合も、このコマンドブロックは通常どおり解析されます。
      • TrueおよびFalseコマンドブロックは、後続のフェーズに自動的に流れません。その後の処理はフェーズ7によって制御されます。
    • FORは、DOの後に2つに分割されます。FOR構文の構文エラーは、致命的な構文エラーになります。
      • DOを通る部分は、フェーズ7まで流れる実際のFOR反復コマンドです。
        • すべてのFORオプションは、フェーズ2で完全に解析されます。
        • IN括弧で囲まれた句は<LF>、として扱います<space>。IN句が解析された後、すべてのトークンが連結されて1つのトークンが形成されます。
        • 連続するエスケープされていない/引用符で囲まれていないトークン区切り文字は、DOを介したFORコマンド全体で単一のスペースに折りたたまれます。
      • DOの後の部分は、通常どおりに解析されるコマンドブロックです。その後のDOコマンドブロックの処理は、フェーズ7の反復によって制御されます。
    • フェーズ2で検出されたREMは、他のすべてのコマンドとは大幅に異なる方法で処理されます。
      • 解析される引数トークンは1つだけです。パーサーは、最初の引数トークンの後の文字を無視します。
      • REMコマンドはフェーズ3の出力に表示される場合がありますが、コマンドが実行されることはなく、元の引数テキストがエコーされます。エスケープするキャレットは削除されません。
        • 行を終了するエスケープされていないもので終わる引数トークンが1つしかない場合^、引数トークンは破棄され、後続の行が解析されてREMに追加されます。これは、トークンが複数あるか、最後の文字がなくなるまで繰り返されます^
  • コマンドトークンが。で始まり:、これがフェーズ2の最初のラウンドである場合(フェーズ6のCALLによる再起動ではない)、
    • トークンは通常、未実行のラベルとして扱われます。
      • 行の残りの部分は解析されますが、、、、)およびは特別な意味<を持ちません。行の残り全体は、ラベル「コマンド」の一部と見なされます。>&|
      • ^引き続き特別です。つまり、行の継続を使用して、後続の行をラベルに追加できます。
      • 括弧で囲まれたブロック内の実行されていないラベルは、次の行でコマンドまたは実行されたラベルがすぐに続く場合を除いて、致命的な構文エラーになります
        • (UnexecutedLabelに続く最初のコマンドに対して特別な意味を持たなくなりました。
      • ラベルの解析が完了すると、コマンドは中止されます。後続のフェーズはラベルに対して実行されません
    • フェーズ2で見つかったラベルが、フェーズ7まで解析を継続 する実行済みラベルとして扱われる原因となる可能性のある3つの例外があります。
      • ラベルトークンの前にリダイレクトがあり、その行に|パイプ、、、、またはコマンドの連結があります。&&&||
      • ラベルトークンの前にリダイレクトがあり、コマンドは括弧で囲まれたブロック内にあります。
      • ラベルトークンは、括弧で囲まれたブロック内の行の最初のコマンドであり、上の行は未実行のラベルで終了します。
    • フェーズ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変数を使用する理由です。
  • FOR変数名では大文字と小文字が区別されますが、大文字と小文字は区別~modifiersされません。
  • ~modifiers変数名よりも優先されます。次の文字~が修飾子と有効なFOR変数名の両方であり、アクティブなFOR変数名である後続の文字が存在する場合、その文字は修飾子として解釈されます。
  • FOR変数名はグローバルですが、DO句のコンテキスト内でのみ使用できます。ルーチンがFORDO句内からCALLされた場合、FOR変数はCALLedルーチン内で展開されません。ただし、ルーチンに独自の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つに折りたたまれます!
      • 残っているペアリングされていない!ものはすべて削除されます
    • 特殊文字が検出されなくなったため(<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のヘルプが出力されます。
  • 最初のを削除して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が呼び出される内容に影響を与える可能性があります。ただし、%0などのトークンはすでに設定されているため、CALLedルーチンに渡される引数は変更されません。
      • 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> , ;それ以外の場合は、またはの最初の出現前にコマンドトークンを解除します。前の=
        テキストが内部コマンドの場合は、そのコマンドを覚えておいてください。
        • コマンドラインモードの場合、またはコマンドが括弧で囲まれたブロックからのものである場合、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
        の場合、等しい後の行の残りの部分全体がコンテンツとして使用されます。これには、存在する可能性のあるすべての引用符が含まれます。
    • IF比較が評価され、条件がtrueかfalseかに応じて、フェーズ5から開始して、適切な解析済みの依存コマンドブロックが処理されます。
    • FORコマンドのIN句は適切に繰り返されます。
      • これがコマンドブロックの出力を繰り返すFOR/Fの場合、次のようになります。
        • IN句は、CMD/Cを介して新しいcmd.exeプロセスで実行されます。
        • コマンドブロックは、解析プロセス全体を2回実行する必要がありますが、今回はコマンドラインコンテキストで実行します。
        • ECHOはオンで開始し、遅延拡張は通常無効で開始します(レジストリ設定によって異なります)
        • 子cmd.exeプロセスが終了すると、IN句のコマンドブロックによって行われたすべての環境変更は失われます。
      • 反復ごとに:
        • FOR変数値が定義されています
        • 次に、フェーズ4から開始して、すでに解析されたDOコマンドブロックが処理されます。
    • GOTOは、次のロジックを使用して:labelを検索します
      • 最初の引数トークンからラベルを解析します
      • ラベルの次の出現をスキャンします
        • 現在のファイル位置から開始します
        • ファイルの終わりに達した場合は、ファイルの先頭にループバックして、元の開始点に進みます。
      • スキャンは、最初に見つかったラベルで停止し、ファイルポインタはラベルの直後の行に設定されます。その時点からスクリプトの実行が再開されます。真のGOTOが成功すると、FORループを含む、解析されたコードブロックがすぐに中止されることに注意してください。
      • ラベルが見つからない場合、またはラベルトークンがない場合、GOTOは失敗し、エラーメッセージが出力され、コールスタックがポップされます。これは、EXIT / Bとして効果的に機能します。ただし、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-外部コマンドを実行する-それ以外の場合は、コマンドを外部コマンドとして処理しようとします。
    • ,コマンドラインモードで、コマンドが引用符で囲まれておらず、ボリューム指定で始まらない場合は、空白;、、、、=または+の最初の出現時にコマンドトークンを分割し<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の後で実行されません。これは、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
)-多くの追加コンテンツと編集

于 2010-11-04T09:04:30.513 に答える
63

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

それが無秩序のように聞こえるなら、それは一種です。ただし、多数のWindowsプログラムMicrosoft C / C ++ランタイムを利用しているため、MSVCRTが引数をトークン化する方法argvを理解しておくと一般的に役立つ場合があります。抜粋は次のとおりです。

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

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]は常に実行可能ファイルの名前であり、簡潔にするために以下では省略されています。WindowsXPSP3でテストされています。VisualStudio2005でコンパイルされています。)

> 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]
于 2010-11-04T08:26:46.733 に答える
51

#Percent Expansion Rulesこれは、jebの回答のフェーズ1の拡張された説明です(バッチモードとコマンドラインモードの両方で有効)。

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

  • 1.05(で行を切り捨てる<LF>
  • キャラクター<LF>
    • <LF>行の残りの部分を先からドロップ(無視)します
    • フェーズ2.0に進む
  • それ以外の場合、文字はでなければならないので%、1.1に進みます
  • 1.1(エスケープ% コマンドラインモードの場合はスキップ
  • バッチモードで別のモードが続く場合は、シングル%
    置き換えてスキャンを続行します%%%
  • コマンドラインモードの場合、 1.2(引数の展開) はスキップされます
  • それ以外の場合、バッチモードの場合
    • 続いて*コマンド拡張が有効になっている場合は、すべてのコマンドライン引数のテキストに
      置き換え%*(引数がない場合は何も置き換えないでください)、スキャンを続行します。
    • それ以外の場合は、引数値に置き換え(未定義の場合は何も置き換えないでください)、スキャンを続行し<digit>ます
      %<digit>
    • それ以外の場合~、コマンド拡張が有効になっている場合は
      • オプションの有効な引数修飾子のリストが続き、その後に必須が続く場合は<digit>、変更された引数値に
        置き換え%~[modifiers]<digit>(定義されていない場合、または指定されている場合は$ PATH:修飾子が定義されていない場合は何も置き換えない)、スキャンを続行します。
        注:修飾子は大文字と小文字を区別せず、$ PATHを除いて任意の順序で複数回表示できます:修飾子は1回だけ表示でき、前の最後の修飾子である必要があります<digit>
      • そうでなければ、無効な変更された引数構文は致命的なエラーを引き起こします:すべての解析されたコマンドは中止され、バッチモードの場合はバッチ処理が中止されます!
  • 1.3(拡張変数)
  • それ以外の場合、コマンド拡張機能が無効になっている場合は
    、次の文字列を調べて%、バッファの前または最後でブレークし、それらをVARと呼びます(空のリストの場合があります)
    • 次のキャラクター%
      • VARが定義されている場合は、VARの値に
        置き換え%VAR%て、スキャンを続行します
      • それ以外の場合、バッチモードの場合は、
        削除%VAR%してスキャンを続行します
      • それ以外の場合は1.4
    • それ以外の場合は1.4
  • それ以外の場合、コマンド拡張機能が有効になっている場合は
    、次の文字列を調べて% :、バッファの前または最後でブレークし、それらをVAR(空のリストの場合があります)と呼びます。VARが前にブレーク:し、後続の文字がVARの最後の文字として%含まれ、前にブレークする場合。 :%
    • 次のキャラクター%
      • VARが定義されている場合は、VARの値に
        置き換え%VAR%て、スキャンを続行します
      • それ以外の場合、バッチモードの場合は、
        削除%VAR%してスキャンを続行します
      • それ以外の場合は1.4
    • それ以外の場合、次のキャラクター:
      • VARが未定義の場合
        • バッチモードの場合は、
          削除%VAR:してスキャンを続行します。
        • それ以外の場合は1.4
      • それ以外の場合、次のキャラクター~
        • 次の文字列がのパターンと一致する場合は、VARの値のサブ文字列[integer][,[integer]]%
          置き換え%VAR:~[integer][,[integer]]%て(おそらく空の文字列になります)、スキャンを続行します。
        • それ以外の場合は1.4
      • それ以外の場合、=または*=その後に
        無効な変数の検索と置換の構文を使用すると、致命的なエラーが発生します。解析されたすべてのコマンドが中止され、バッチモードの場合はバッチ処理が中止されます。
      • それ以外の場合、次の文字列がのパターンに一致[*]search=[replace]%する場合、検索には、以外の任意の文字=セットが含まれ、置換には、以外の任意の文字セットが含まれる可能性があります%。次に、検索と
        置換%VAR:[*]search=[replace]%を実行した後、VARの値に置き換えて(おそらく空の文字列になります)、続行します。スキャン
      • それ以外の場合は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つあります。コマンド拡張機能が有効になっているときに、末尾に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!"

#Delayed Expansion Rulesこれは、jebの回答のフェーズ5の拡張された、より正確な説明です(バッチモードとコマンドラインモードの両方で有効)

フェーズ5)遅延拡張

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

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

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

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

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

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

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

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

クイズ:環境変数の値を単一の引数として(つまり、として%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。)文字を追加してみてください。'&

于 2011-01-17T15:58:14.297 に答える
5

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

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

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

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

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

于 2014-08-11T21:01:13.023 に答える
3

FOR-ループメタ変数の拡張

これは、受け入れられた回答のフェーズ4)の詳細な説明です(バッチファイルモードとコマンドラインモードの両方に適用可能)。もちろん、コマンドはアクティブでなければなりません。以下に、句の後のコマンドライン部分の処理について説明します。バッチファイルモードでは、前述の即時拡張フェーズ(フェーズ1)のために、はすでに変換されていることに注意してください。fordo%%%%

  • %左から行の終わりまで、-signをスキャンします。見つかった場合は、次のようにします。
    • コマンド拡張機能が有効になっている場合(デフォルト)、次の文字が~;であるかどうかを確認します。はいの場合:
      • 大文字と小文字を区別しないセットで、変数参照または記号fdpnxsatzを定義する文字の前にある次の文字をできるだけ多く取ります(それぞれ複数回でも)。そのような記号が見つかった場合、次のようになります。 for$$
        • :1をスキャンします; 見つかった場合:
          • の後に文字がある場合は、:それを変数参照として使用し、for定義されていない限り、期待どおりに展開します。その後、展開せず、その文字位置でスキャンを続行します。
          • :が最後の文字の場合、cmd.exeクラッシュします!
        • それ以外の場合(:見つかりません)は何も展開しません。
      • それ以外の場合($-signが検出forされない場合)、定義されていない限り、すべての修飾子を使用して変数を展開します。展開せず、その文字位置でスキャンを続行します。
    • それ以外の場合(~見つからない場合、またはコマンド拡張機能が無効になっている場合)、次の文字を確認します。
      • 使用可能な文字がこれ以上ない場合は、何も展開しないでください。
      • 次の文字が、の場合は、何も展開せず、この文字位置2%でスキャンの最初に戻ります。
      • それ以外の場合は、次の文字をfor変数参照として使用し、展開します。ただし、定義されていない場合は、展開しません。
  • 次の文字位置でスキャンの最初に戻ります(使用可能な文字がまだある場合)。

1)との間の文字列は環境変数の名前$:見なされます。環境変数は空の場合もあります。環境変数に空の名前を付けることはできないため、動作は未定義の環境変数の場合とまったく同じです。
2)これは、for名前付きのメタ変数%は-modifierなしでは展開できないことを意味し~ます。


元のソース:FOR変数%%〜pの後に文字列リテラルを安全にエコーする方法

于 2020-12-10T22:19:52.970 に答える
0

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


引用については、次のような振る舞いをしている気がします。

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

要するに:

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

"a"""a"""および"a""""行の終わりにある場合はすべて同じ文字列です

于 2010-11-04T08:06:42.327 に答える
-2

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

ソースコードへのリンク。

于 2020-06-08T18:00:52.043 に答える