DOSバッチで、末尾から特定の文字を検索し、先頭からその文字までを抽出したい。

Yahoo知恵袋での質問。やはりYahooのIDがないのでこちらで勝手に回答します:-P


http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1421612734

DOSバッチで、末尾から特定の文字を検索し、先頭からその文字までを抽出したい。mpmyp813さん

DOSバッチで、末尾から特定の文字を検索し、先頭からその文字までを抽出したい。

テキストファイルの各行末尾から特定の文字を検索し、見つかった場所から末尾までを削除した行を他のテキストファイルの出力したいのですが、うまくできません。
下記のようなファイルから、各行の末尾から”}”までを除いた文字列を別ファイルresult.txtに出力したいです。


---検索対象ファイル:input.txt---
abc{12345}{de}abcdefg
ljklm{6789}{nop}vwxyz
-------------------------

---抽出後イメージ:result.txt---
abc{12345}{de}
ijklm{6789}{nop}
-------------------------

アルファベット部分および数字表記の文字数は可変なので”x文字目からy文字”という指定はできません。
また、先頭から複数個の”}”が存在するので、先頭からx”}”という検索ができないと思っています。
そのため、"}"が末尾から何文字目に出現するかを変数Sに入れ、”行の先頭からS文字目まで”というイメージになるのですが、どのように書いたらいいでしょうか?

エクセルの関数では以下のような式で実現できます。

セルA1(対象文字列): abc{12345}{de}fgh999uvw
セルB1(抽出関数): =LEFT(A1,LEN(A1)-LEN(RIGHT(A1,FIND("}",A1)-1)))


説明不足でしたら補足いたします。
よろしくお願い致します。

http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1421612734

質問されているmpmyp813さんは

そのため、"}"が末尾から何文字目に出現するかを変数Sに入れ、”行の先頭からS文字目まで”というイメージになるのですが、どのように書いたらいいでしょうか?

と書かれているますが、バッチには検索文字列の位置を返すようなVBのindex関数やexcelのfind関数のようなコマンドは見あたらないので、別の方法を考えるしかないと考えました。

まず抽出前と抽出後のイメージを見比べてみて、}を区切りとして文字列を分けることを考えます。そうすると、for /F "delims=}"が真っ先に思い付くのですが、区切られるtokenの数が固定の場合はこれでいいのだけれども、今回は数が不定("}"の数が固定と書かれていない)ためこの方法は使わないことにします。(質問で示されている例を見る限りはxxxx{yyyy}{zzz}ddddの4ブロック固定でddddの部分を削除すればいいようにも取れますが)

そこでまず、for /F "delims=" で1行分をそのままサブルーチンに渡すことにしました。

for /F "delims=" %%l in (input.txt) do call :sub "%%l"

受け取ったサブルーチンでは一旦環境変数lineに全てを入れて、環境変数の置換(set /?を参照のこと)使って、"}"を空白に置換したものを2つめのサブルーチンの引数にして呼び出します。

2つめのサブルーチンでは、"}"で区切られた引数がそれぞれ %1 %2 %3 ... に入ることになります。そしてこの引数の一番最後が今回削除したい文字列になるわけです。

あとは、2つめのサブルーチンで %1 の引数が空になるまで、shift コマンドを使って引数をシフトしながらループします。その際 %1 の内容を一時的に環境変数xに待避しておきます。こうすることで、%1が空になったとき、直前の引数が環境変数xに残るわけです。
今回の例だと環境変数xにはabcdefgが入ります。

:sub2
  if "%1"=="" goto exit_loop
  set x=%1
  shift
goto sub2
:exit_loop

最後の仕上げは、1行の文字列(環境変数line)から削除したい文字列(環境変数x)を削除するところです。これは先ほども使った環境変数の置換を使います。

call set ans=%%line:%x%=%%

この call set 〜 というのが一時的な遅延展開をしているところで、まず call set ans=%%line:%x%=%% という行がバッチファイルとして実行されると、%%→% と %x% の環境変数の展開が行われます。

call set ans=%line:abcdefg=%

ここでcall文が実行され、set ans=%line:abcdefg=% が子プロセスのようにして実行されるようです(call /? ではcall :ラベルで「指定された引数で新しいバッチ ファイル コンテキストが作成され」と書いてありますが内部コマンドでも同じのようです)。
環境変数グローバル変数のように扱われますから、環境変数ansの内容はcall文実行後も保持されてめでたくabcdefgを空に置換(=削除)してくれた結果が環境変数ansに入ります。
最後に結果をresult.txtに出力して終わり。

ということで最終的にできたバッチはこれです。

:----------------------------------------------------- sample.bat
echo off
for /F "delims=" %%l in (input.txt) do call :sub "%%l"
type result.txt
exit /b

:sub
set line=%~1
call :sub2 %line:}= %
goto :EOF

:sub2
  if "%1"=="" goto exit_loop
  set x=%1
  shift
goto sub2
:exit_loop
call set ans=%%line:%x%=%%
echo %ans%>>result.txt
goto :EOF

このページ見てくれればいいんですが、やっぱり無理かなぁ。