DICOM(8)GDCM:バッチファイルの環境変数の即時展開と遅延展開

前の関連記事:DICOM(7)GDCM:dcmファイルを一括して非圧縮に変換する方法


シリーズごとにサブフォルダに入っているファイルをいちいち各フォルダごとに変換するのは面倒です。サブフォルダの中もいっきに変換するバッチファイルを作ります。結構四苦八苦しました。この記事はバッチファイルに関する学習記録です。

サブフォルダの中のファイルを処理する方法を考える


まずはCドライブに_DicomSamplesフォルダを作りそのなかにSubfolder1とSubfolder2というサブフォルダを作ります。


これら3つのフォルダそれぞれに3つずつdcmファイルを入れます。

forに/rをつけるとサブフォルダのファイルも検索してくれます。

ドロップしたフォルダ以下の階層だけを検索してほしいのですがforの条件設定でどうやって指定するのかがわかりません。

ということで最初にpushd %1でドロップしたフォルダをカレントフォルダにしてしまうことにします。

まずechoでちゃんとすべてのファイルが表示されるのかを確認します。
pushd %1
for /r  %%i in (*.dcm) do (
  echo %%i
  pause
)
これに_DicomSamplesフォルダをドロップするとちゃんとすべてのdcmファイルがフルパスで表示されました。


変換後のファイル名%%~dpiDec_%%~nxi も表示されるかも確認します。
pushd %1
for /r  %%i in (*.dcm) do (
  echo %%i
  echo %%~dpiDec_%%~nxi 
  pause
)

問題ありません。
pushd %1
for /r  %%i in (*.dcm) do (
 gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
)
ということでこれで全部変換されるはずです。

やってみるとうまくいきました。

あとは使いやすくするためにいろいろ肉付けしていきます。

いろいろ機能を追加する


変換後に変換元ファイルを削除する
pushd %1
for /r  %%i in (*.dcm) do (
 gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
  del /q "%%i" 
)
変換元ファイルを削除するか事前に尋ねる
@echo off
pushd %1
echo フォルダ内のdcmファイルをサブフォルダ内も含めてすべて非圧縮形式に変換します。
echo.
echo はい(y^)/いいえ(n^)を選択後Enterキーを押してください。
echo.
set /p KEY="変換後に変換元ファイルを削除しますか?(y/n)"
if "%KEY%" == "y" (
  for /r  %%i in (*.dcm) do (
   gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
    del /q "%%i" 
  )
)
if "%KEY%" == "n" (
  for /r  %%i in (*.dcm) do (
   gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
  )
)

処理の表示をする
@echo off
pushd %1
echo フォルダ内のdcmファイルをサブフォルダ内も含めてすべて非圧縮形式に変換します。
echo.
echo はい(y^)/いいえ(n^)を選択後Enterキーを押してください。
echo.
set /p KEY="変換後に変換元ファイルを削除しますか?(y/n)"
if "%KEY%" == "y" (
for /r  %%i in (*.dcm) do (
  cls
  echo %%iを変換中
 gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
  del /q "%%i" 
)
cls
echo 変換を完了しました。
echo 変換元ファイルは削除しました。
)
if "%KEY%" == "n" (
  for /r  %%i in (*.dcm) do (
    cls
    echo %%iを変換中
   gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
  )
  cls
  echo 変換を完了しました。
  echo 変換元ファイルは残っています。
)
echo.
pause
これでもう十分かもしれませんが、さらに欲張って機能を追加します。

特定のファイル以外のファイルを削除する方法でつまずく


変換元ファイルや変換後ファイルを削除する機能はGDCMと全く関係ありませんが、何回もテストしているとほしくなったので作ることにしました。

変換後ファイルの削除は「Dec_から始まるファイル名」をワイルドカードで指定すればよいので簡単です。
@echo off
pushd %1
for /r  %%i in (Dec_*.dcm) do (
  cls
  echo %%iを削除中
 del /q "%%i"
)
頭を悩ましたのは変換元ファイルの指定、つまり「Dec_から始まるファイル名以外」の指定です。

ワイルドカードを使った簡単な方法を思いつかなかったのでifでファイル名の先頭4文字を判定することにしました。

%STR:~0,4%で0文字目から4文字を%STR%から切り出しています。
@echo off
pushd %1
for /r  %%i in (*.dcm) do (
  SET STR=%%~nxi
  if not "%STR:~0,4%"=="Dec_" (
    cls
    echo %%iを削除中
   del /q "%%i"
  )
)
ところがこれがうまく動きません。

Dec_から始まるファイル名のファイルも削除してしまいます。

ちょっとよくわからなかったのでこの機能のことは保留にしました。

選択メニューを使って一つのバッチファイルにまとめるところでさらにつまずく


変換元ファイル削除と変換後ファイル削除の機能もまとめて一つのバッチファイルにするために機能の選択ができる選択メニューを表示させるようにしました。
@echo off
pushd %1
echo ************ dcmファイル非圧縮変換バッチ ************
echo.
echo [1] : 非圧縮形式に変換する
echo.
echo [2] : 変換元ファイルを削除する。
echo        (ファイル名がDec_から始まるファイルのみを残してそれ以外を削除します。)
echo.
echo [3] : 変換後ファイルを削除する。
echo        (ファイル名がDec_から始まるファイルを削除します。)
echo.
echo ****************************************************
echo.
set /p NUM="機能を数字で選択してEnterキーを押してください。"
if "%NUM%"=="1" (
cls
echo フォルダ内のdcmファイルをサブフォルダ内も含めてすべて非圧縮形式に変換します。
echo.
echo はい(y^)/いいえ(n^)を選択後Enterキーを押してください。
echo.
set /p KEY="変換後に変換元ファイルを削除しますか?(y/n)"
if "%KEY%" == "y" (
  for /r  %%i in (*.dcm) do (
    cls
    echo %%iを変換中
   gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
    del /q "%%i" 
  )
  cls
  echo 変換を完了しました。
  echo 変換元ファイルは削除しました。
)
if "%KEY%" == "n" (
  for /r  %%i in (*.dcm) do (
    cls
    echo %%iを変換中
   gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
  )
  cls
  echo 変換を完了しました。
  echo 変換元ファイルは残っています。
  )
)
if "%NUM%"=="2" (
  for /r  %%i in (*.dcm) do (
    SET STR=%%~nxi
    if not "%STR:~0,4%"=="Dec_" (
      cls
      echo %%iを削除中
     del /q "%%i"
    )
  )
  cls
  echo ファイル名がDec_から始まるファイルのみを残してそれ以外は削除しました。
)
if "%NUM%"=="3" (
  for /r  %%i in (Dec_*.dcm) do (
    cls
    echo %%iを削除中
   del /q "%%i"
  )
  cls
  echo ファイル名がDec_から始まるファイルをすべて削除しました。
)
echo.
pause
ところがこれがまたうまく動きません。

Dec_以外のファイル削除機能がうまく動かないのは先に確かめた通りなのですが、単独ではうまく動いていたdcmファイルの変換機能も動きません。

_DicomSamplesフォルダをドロップすると機能選択メニューが表示されます。


3のファイル削除機能は狙ったとおりの動作をします。

2を選択するとDec_から始まるファイルもすべて削除されてしまうのはメニューに組み込む前と同じで変わっていません。

ところがメニューに組み込む前はちゃんと動いていた1の機能が動きません。

1を選択すると次の選択メニューが表示されるのですがその次の処理が実行されません。


なにかキーを入力してEnterキーを押すと「続行するには何かキーを押してください、、、」とでてきますがdcmファイルは変換されていません。


なぜでしょう?

@echo offを削除して実行してコマンドを見てみるとif "%NUM%"=="1" がTrueの場合の残りの部分がすべて飛ばされていることがわかりました。

ここまでなんとかわかったのですがそれ以上にはどうしてもわからずもう寝ました。

丸カッコに囲まれた中にある変数はカッコ「(」が読み込まれた時点の値が使用されてしまう


さて翌日気を取り直していろいろバッチファイルをいじってみました。

set /p KEY="変換後に変換元ファイルを削除しますか?(y/n)"

のあとに以下のコマンドをいれて%KEY%のなかみを見てみました。

echo "%KEY%" 

するとyキーを入力したはずなのに""と表示されます。

%KEY%に何も代入されていないのが原因のようです。

選択メニューに組み込む前はちゃんと動いていたのに奇妙な現象です。

バッチファイル。IF文やFOR文の中で複数コマンドを書く時の注意点 - Windowsのコマンドプロンプト(bat,cmd) - to_dk notebook

いろいろ調べた結果このページに書いてあることが原因のような気がしましたが、ちょっとよく理解できませんでした。

さらに調べた結果CMD.EXEの遅延環境変数の展開 - ふなWikiの解説でようやく理解できました。

さらにFOR文やIF文で変数の中身が消える? (DOSの変数の代入) - バッチもん研究所 blogを読んで%KEY%が空っぽの理由がはっきり理解できました。

要は、カッコ()に囲まれた中にある変数はカッコ(が始まった時点の値が使用されてしまう、ということです。

ということでif "%NUM%"=="1" ()の()に入っている%KEY%は「set /p KEY="変換後に変換元ファイルを削除しますか?(y/n)"」で値がSETされる前に「if "%NUM%"=="1" (」が読み込まれた瞬間の%KEY%の値、つまりnullが使用されてしまっているのです。

「遅延環境変数の展開」を使って解決


(が読みこまれた瞬間に()内の変数がその時点の値が使用されてしまうことを「環境変数の即時展開」というそうです。

それに対して普通の言語のように動作を期待する方法として「遅延展開」が用意されています。

setlocal enabledelayedexpansionとendlocalで挟んだ間で%の替わりに!を使って変数を展開します。
@echo off
pushd %1
echo ************ dcmファイル非圧縮変換バッチ ************
echo.
echo [1] : 非圧縮形式に変換する
echo.
echo [2] : 変換元ファイルを削除する。
echo        (ファイル名がDec_から始まるファイルのみを残してそれ以外を削除します。)
echo.
echo [3] : 変換後ファイルを削除する。
echo        (ファイル名がDec_から始まるファイルを削除します。)
echo.
echo ****************************************************
echo.
set /p NUM="機能を数字で選択してEnterキーを押してください。"
setlocal enabledelayedexpansion
if "%NUM%"=="1" (
  cls
  echo フォルダ内のdcmファイルをサブフォルダ内も含めてすべて非圧縮形式に変換します。
  echo.
  echo はい(y^)/いいえ(n^)を選択後Enterキーを押してください。
  echo.
  set /p KEY="変換後に変換元ファイルを削除しますか?(y/n)"
  if "!KEY!" == "y" (
    for /r  %%i in (*.dcm) do (
      cls
      echo %%iを変換中
     gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
      del /q "%%i" 
    )
  cls
  echo 変換を完了しました。
  echo 変換元ファイルは削除しました。
)
if "!KEY!" == "n" (
  for /r  %%i in (*.dcm) do (
    cls
    echo %%iを変換中
   gdcmconv -w "%%i" "%%~dpiDec_%%~nxi"
  )
  cls
  echo 変換を完了しました。
  echo 変換元ファイルは残っています。
)
endlocal
if "%NUM%"=="2" (
rem for /r  %%i in (*.dcm) do (
rem SET STR=%%~nxi
rem if not "%STR:~0,4%"=="Dec_" (
rem  cls
rem  echo %%iを削除中
rem del /q "%%i"
rem )
rem )
rem cls
rem echo ファイル名がDec_から始まるファイルのみを残してそれ以外は削除しました。
)
if "%NUM%"=="3" (
  for /r  %%i in (Dec_*.dcm) do (
    cls
    echo %%iを削除中
   del /q "%%i"
  )
  cls
  echo ファイル名がDec_から始まるファイルをすべて削除しました。
)
echo.
pause
これはdcmファイルの変換機能はちゃんと動きました。

まだちゃんと動かないDec_ファイル以外の削除機能はremでコメントアウトしています。

環境変数の即時展開とか遅延展開とか初めて知りました。

参考にしたサイト


ファイルの拡張子をまとめて変更する(コマンド・プロンプト編) - @IT
forを使ってサブフォルダのファイルも処理します。

For - DOS コマンド一覧
Forの解説。コマンドは大文字小文字は区別しないようです。

コマンドプロンプト del - [ファイルを削除する]
ファイル削除コマンド

炎のコマンドプロンプト入門/バッチファイル/条件分岐
選択メニューを表示させる方法

メッセージの表示(ECHO) - バッチファイルの作成 - コマンドプロンプトの使い方
空白行を出力する方法、特殊文字をechoする方法が参考になりました。

開発に役立つ,BATファイルの書き方・パターン集 (コマンドプロンプトの定石を体系的に学び,バッチ中級者になろう) - 主に言語とシステム開発に関して
サブルーチン化などいろいろ載っています。

BATファイルで文字列の切り出し
左端の文字は0字目と数えることに注意。そのあとに取り出す文字数を指定します。

バッチファイル。IF文やFOR文の中で複数コマンドを書く時の注意点 - Windowsのコマンドプロンプト(bat,cmd) - to_dk notebook
このページがきっかけで解決策が見つかりました。

CMD.EXEの遅延環境変数の展開 - ふなWiki
環境変数の即時展開と遅延展開の違いがよくわかりました。

FOR文やIF文で変数の中身が消える? (DOSの変数の代入) - バッチもん研究所 blog
()の中は(が読み込まれた時点で変数が展開されるということがわかりました。

次の関連記事:DICOM(9)GDCM:バッチファイルで特定のファイル以外のファイルを削除する

PR

0 件のコメント:

コメントを投稿