Windows バッチでの文字列置換

Windows バッチでの文字列置換

Windows バッチでの文字列置換が実際にどのように機能するかを理解しようとしていますが、問題があります。

@echo off
set var=wild
set varnew=%var:l=n%
echo var is: %var%
echo varnew is: %varnew%

動作します。期待通りの出力が生成されます。

var is: wild
varnew is: wind

しかし、これは当てはまりません (例: ディレクトリ「Main」):

@echo off
for /D %%G IN (*) do (
    setlocal
    echo G is: %%G
    set _srcp=%%G
    echo _srcp is %_srcp%
    rem set _newp=%_newp:ai=_01_% <-- confused variable
    set _newp=%_srcp:ai=_01_%
    echo._newp is: %_newp%
    endlocal
                     )

次の出力が生成されます:

G is: Main
_srcp is Main
_newp is: %_srcp:ai=_01_

最後の行としてコードが生成されることを期待しています_newp is: M_01_n。ここでは本当にアイデアが尽きています。誰か私に正しい方向を指し示してもらえませんか?

BB

答え1

いくつか問題があります:

  • %var%展開はステートメントが解析されるときに行われ、括弧で囲まれたコード ブロック全体がコマンドが実行される前に 1 回のパスで解析されます。したがって、値はループが開始される前に存在していた値です。解決策は遅延展開であり、これはループ内の各コマンドが実行されているときに行われます。

  • あなたの論理は間違っています - _newp の割り当ては _srcp の値に基づく必要があります

CMDプロセッサは複雑なものです(また、ドキュメントも不十分です)。さまざまな種類の変数が展開されるポイントが複数あり、バッチ プログラミングを最大限に活用したい場合は、それらを完全に理解する必要があります。すべてリンクで説明されていますが、要約すると、展開の順序は次のとおりです。

1) % 拡張 - パラメータ:echo %1 または環境変数: echo %var%
---- ほとんどの解析は既に完了しています ----
2) FOR 変数の展開: for %%A in (*) do echo %%A
3) 遅延環境変数の展開: echo !var!
4) CALL % 展開 - パラメータ:call echo %%1 または環境変数: call echo %%var%%
5) SET /A 環境変数の展開: `set /a "value=var+1"

遅延拡張を有効にするには、SETLOCAL EnableDelayedExpansion

遅延拡張を使用する次のコードは、求めている結果をもたらします。

@echo off
for /D %%G in (*) do (
  setlocal enableDelayedExpansion
  echo G is: %%G
  set "_srcp=%%G"
  echo _srcp is !_srcp!
  set "_newp=!_srcp:ai=_01_!"
  echo _newp is: !_newp!
  endlocal
)

%%G遅延拡張は FOR 変数拡張の後に発生するため、 const が含まれる場合は結果が破損することに注意してください!。これは、 SETLOCAL を追加することで回避できます。

for /D %%G in (*) do (
  setlocal disableDelayedExpansion
  echo G is: %%G
  set "_srcp=%%G"
  setlocal enableDelayedExpansion
  echo _srcp is !_srcp!
  set "_newp=!_srcp:ai=_01_!"
  echo _newp is: !_newp!
  endlocal
  endlocal
)

二重パーセントの CALL を使用しても目的の結果を得ることができますが、これははるかに遅くなります。数回実行する場合は速度は重要ではありませんが、ループで何千回も実行される場合は速度が非常に重要になります。

@echo off
for /D %%G in (*) do (
  setlocal
  echo G is: %%G
  set "_srcp=%%G"
  call echo _srcp is %%_srcp%%
  call set "_newp=%%_srcp:ai=_01_%%"
  call echo _newp is: %%_newp%%
  endlocal
)

答え2

davebenhamの回答への追加

FOR や IF などのブロック内で echo %var% を実行しても、正しく動作しません。%var% 変数は更新されません。!var! を使用する必要があります。!var! を動作させるには、setlocal EnableDelayedExpansion を設定する必要があります。

これについてはcmdヘルプに説明がありますが、どのコマンドのヘルプで説明されているかは明らかではありません。set /?

set /?

最後に、遅延環境変数拡張のサポートが追加されました。このサポートはデフォルトで常に無効になっていますが、CMD.EXE の /V コマンドライン スイッチを使用して有効/無効にすることができます。CMD /? を参照してください。

遅延環境変数の展開は、実行時ではなくテキスト行の読み取り時に発生する現在の展開の制限を回避するのに役立ちます。次の例は、即時変数展開の問題を示しています。

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "%VAR%" == "after" @echo If you see this, it worked
)

両方の IF ステートメントの %VAR% は、最初の IF ステートメントが読み取られるときに置換されるため、メッセージは表示されません。これは、論理的に複合ステートメントである IF の本体を含むためです。したがって、複合ステートメント内の IF は実際には「前」と「後」を比較しており、それらは決して等しくなりません。同様に、次の例は期待どおりに動作しません。

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

現在のディレクトリ内のファイルのリストは作成されず、代わりに LIST 変数が最後に見つかったファイルに設定されます。これも、FOR ステートメントが読み取られるときに %LIST% が 1 回だけ展開され、その時点では LIST 変数が空であるためです。したがって、実際に実行する FOR ループは次のようになります。

for %i in (*) do set LIST= %i

これは、LIST を最後に見つかったファイルに設定し続けます。

遅延環境変数拡張を使用すると、実行時に別の文字 (感嘆符) を使用して環境変数を拡張できます。遅延変数拡張が有効になっている場合、上記の例は次のように記述して意図したとおりに動作させることができます。

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

コマンドラインから表記法をテストすることもできます。

cmd /e:on

デフォルトの cmd /v:off から始めて、cmd/v:on に移行します。ほとんどの人は、専門家でも cmd .v:off を使用します (おそらく、他のものがこれと異なって解釈される可能性があるため)。そのため、これを使用して、cmd 内で !var! 表記を試すことができることを示すだけです。

C:\>set a=5

C:\>echo %a%
5

C:\>echo !a!
!a!


C:\>cmd /v:on
Microsoft Wind
Copyright (c)

C:\>echo !a!
5

C:\>

ちなみに、cmd /v:on またはバッチ ファイルで EnableDelayedExpansion をオンにした場合、Dave が示すように、! は特殊文字なので、! が %var% 内にあると問題が発生することに注意する必要があります。これが、ユーザーがモードを常時オンにしない理由ですが、他の理由があるかもしれません。

関連情報