Programming Serendipity

気まぐれに大まかに生きるブログ

一階層下のファイルを全て現在のフォルダに移動させるバッチファイル【ついでにコマンドプロンプト入門】(2021/4/26追記あり)

Windowsでは、下の階層にあるファイルを上に持っていくのって地味に面倒くさい作業になります。
これを手でやろうとすると、フォルダを開いて全選択して切り取ってフォルダを抜けて貼り付けて…というのが1回だけならまだしも、全てのフォルダの中身を上に持ってきたいときは手作業では骨が折れますよね。
そこで、これを自動的に行うバッチファイルを作ってみました。続きからどうぞ。

こちらがその中身です。

for /d %%i in (*) do (  
    pushd .  
    cd %%i  
    for %%j in (*) do ren "%%j" "%%i_%%j"  
    move * ../  
    popd  
    rd "%%i"  
)  

これをテキストエディタにコピーして.batの拡張子をつけて保存し、動作させたいフォルダにコピーしてダブルクリックで起動してください。
または毎回対象フォルダにコピーするのが面倒な場合、「パスを通す」という作業をすると面倒ごとから解放されます。
やりかたは、C:\cui_toolsのようなフォルダを用意してそこに今のバッチファイルを入れ、コントロールパネルで「環境変数」と検索して環境変数の編集を開き、変数PATHの編集を選び、その末尾に
;C:\cui_tools
を追加することです。これでパス通しの作業は完了です。
(追記:最新のWindows10であれば、Winキーを押してenvと入力すると候補に環境変数が出るのでそれでもOKで、またPathの追加も;で区切らずに直感的に追加できるようになっています)

あとは、対象のフォルダを開いた状態でWindowsエクスプローラのバーの何もないところをクリック(もしくはAlt+D)し、cmdと打ってエンターを押すとコマンドプロンプトが起動するので、
そこでさっきのバッチファイル名を打つだけで起動するようになります。
(追記:パスが通してあれば、cmdを起動しなくても直接バッチファイル名を打つだけでも起動できることに今気がつきました)

f:id:q7z:20160210013510p:plain

せっかくなので、コマンドプロンプトが初めての人でも分かるように1行1行全てに解説を付けてみました。この記事だけでコマンドプロンプトの概念の大半をカバーしてます。
コマンドプロンプト入門記事並みになりました。

詳細な解説

for /d %%i in (*) do (

1行目にあるfor は、現在のディレクトリの全てのファイル・フォルダに対して処理を行うコマンドです。
/d というスイッチを指定することで対象をディレクトリ(フォルダ)に限定します。
そのディレクトリ名をひとつずつ処理するのですが、そのときに受け取る個別の名前を%%iとしています。
例えば、a,b,cという3つのフォルダがあった場合、最初のループでは%%iにはaが入り、次のループでb、次にcが入ります。
これは、コマンドプロプト上で直接打つときは%iと、%はひとつでいいのですが、バッチファイルの場合は%は2つです。
また、文字は1文字限定です。大文字小文字のアルファベットと数字の合計52種類から自由に選べます。便宜的なものなのでとくに選ぶアルファベットによって違いはありません。
そして、(*)の部分はフィルタをあらわし、(*)だと全て、(a*)だとファイルフォルダ名がaで始まるもの、(*.xls)だと.xlsで終わるものに限定します。
do 以降に実際の処理が書かれますが、複数行にわたるときは()で囲みます。

  pushd .

次の行のpushd .は現在のディレクトリの情報を保存します。これはあとでpopdすることでその保存したディレクトリに移動できるワンセットの機能です。
DOSコマンドでは、ピリオドひとつは現在のディレクトリを表します。

  cd %%i

cd(Change Directory)は、今いるディレクトリを変更します。例えば、a,b,cという3つのディレクトリがある場所でこのバッチファイルを実行した場合、
最初はここはaというディレクトリに移動し(cd a)、2回目のループではbというディレクトリに移動し(cd b)、最後の3回目のループではcというディレクトリに移動する(cd c)という意味になります。
ディレクトリ名は日本語でもかまいません。
つまり、ここで一旦中のフォルダに入るわけです。

  for %%j in (*) do ren "%%j" "%%i_%%j"

次にもforが現れています。今度は全てのファイルをリネームする処理です。
ren は rename の簡略形です。 ren a.txt b.txt としたら、a.txtというファイルをb.txtという名前に変更します。
ただ、空白でファイルを区別する都合上、ファイル名自体に空白があると2つのファイルとみなされるため、""で囲むことでひとつのファイルであることを伝える必要があります。
これは、ファイルを移動する際に、ファイル名がかぶると処理に不便なので、ディレクトリ名をファイル名の先頭にくっつけています。
たとえば、aというフォルダの中にp.xlsというファイルがあったとすると、a_p.xlsという名前に変更されます。これでファイルをまとめて統合しても安心です。
もし名前が衝突しないことが分かっている場合はこれはしなくても構いません。その場合、この行のはじめに::(コロン2つ)、またはremと書いてください。その行はコメントになり、コマンドプロンプトからは無視されます。
もちろん、行ごと削除してもかまいません。

  move * ../

moveというのが、ファイルを移動するコマンドです。
これは例えば、今のディレクトリにa.txtがあるときに、 move a.txt b とすると、「今のディレクトリの下の」bというフォルダの中にa.txtを移動させる処理になります。
しかし、いまやりたいのはファイルを「1階層上に」移動させることです。
そこで、ひとつ上のディレクトリを表す../を使用しています。../../とすると、2階層上です。
ここでの*も、forの(*)と同じで、「全ての」を意味します。

  popd

popd は、pushdで一時保存したディレクトリに戻ります。

  rd "%%i"

rd(Remove Directory)は、rmdirの省略形で、空のディレクトリを削除するコマンドです。もう全てのファイルは移動し終わっているので、削除してしまいます。
もし、ディレクトリに何かが残っていても削除したい場合は、 rd /s のように、サブディレクトリも全て削除するスイッチを指定します。
さらに、その際に聞かれる確認も無効にしたい場合、 rd /s /q のように /qスイッチを追加します。
ただし、このスイッチは注意して使用してください。GUI操作に慣れていると忘れがちですが、コマンドプロンプトで削除したファイルはゴミ箱には行かず直接削除されます。

これが全てのフォルダに対して行われるので、ガバっと一気に1階層下のファイルを上に上げることができます。
Done!おめでとう!

ちなみに、コマンドプロンプトは実行中のコマンドを自動的に表示する仕様(echo)で、forコマンドの変数の中身がどう展開されたかが見れるのでおすすめですが、
このメッセージが不要な場合はバッチファイルの先頭に@echo offを追加してください。
echo offというのがechoの機能をオフにするもので、ただこのecho offというコマンド自体は表示されてしまうので、それも表示しないために先頭に@をつけています。
@ではじまる行は、一時的にechoの機能がオフになります。
echo コマンドは、プログラミング言語によくあるprint関数のようなものです。

コマンドプロンプトのコマンドを調べたいとき

コマンドプロンプトでhelpと打つとコマンドの一覧が表示されます。その中で、たとえばhelp forと打つとforコマンドに関する解説が表示されます。
Webで調べたい場合は、command prompt, dos command, command lineなど、何種類か検索方法があります。
マイクロソフトの公式リファレンスは、コマンド自体は網羅されていますが、(毎度のことながら)サンプルが少ないためわかりにくいです。
調べていて見つけたSS64というサイトが良さそうです。こちらはサンプルが豊富で、理解しやすい良サイトでした。
追記:このページもよさそう

smdn.jp

まとめ

コピー・削除・移動・リネームくらいであればプログラムを書かなくてもバッチで処理したほうが楽だし早いですね。

追記:おまけ

ファイルではなくフォルダをザップしたい場合、手抜きですが以下のようにするとできます。

for /d %%i in (*) do (  
    pushd .  
    cd %%i  
    for /d %%j in (*) do ren "%%j" "%%i_%%j"  
    for /d %%k in (*) do ren "%%k" "../%%k"  
    popd  
    rd "%%i"  
)  

バッチ的には引数で処理分けたりするのがいいのでしょうが、動けば正義なり。

追記2:全部のサブディレクトリから一括でザップする

もうこの記事を書いてから5年ほどたちましたが、ありがたいことに今でも多くの人に読まれているようで、内容をアップデートしたほうがよさそうと感じたのでさらに追記しようと思います。
この記事で紹介した内容は直近の子ディレクトリからのみザップする処理なので、孫ディレクトリなどさらに階層が深い場合は効果を発揮しない弱点がありました。
というより、自分自身も使ってて不便だなと思ってたのに放置して運用でカバーしてました(いけませんね)
正直この辺になってくるともうバッチでは対処できなくなってくるので、無理やりバッチでやるよりは普通にコーディングしたほうがシンプルに書けます。
ということで、もはやバッチは関係ありませんが、これを素直に普通のプログラムでやる方法を紹介します。

gist.github.com

これをVisual StudioC#コンソールアプリケーションとしてビルドしてできたexeファイルを、batと同様の場所に置いてファイル名を実行すると動きます。
C#で書きましたが、他の言語でも同じことはできるはずです。
これで、指定フォルダの中にあるファイルを一括で同じディレクトリにまとめることができました。スッキリ!(コメントをくれたこうじろうさんありがとうございます)