再帰的にファイル名やディレクトリ名にある半角スペースをアンダースコア ( _ )で置き換えるスクリプト: Linux & Mac 対応

シェルスクリプトで作業を自動化しようとするとき、ファイル名やディレクトリ名に半角スペースが入っていると、エラーとなります。
この半角スペースを全部アンダースコア( _ ) で置き換えてあげたいと思いました。
それも、カレントディレクトリだけではなく、再帰的にサブディレクトリにあるものもすべてです。
やっぱり楽したいですから…。特に画像解析をやる場合、DICOMのディレクトリ構造は、かなり奥深くにいくので、再帰的にいけると楽になります。

スマートでないかもしれませんが、以下のようなスクリプトを書いてみました。

replace-space-underscore.shのダウンロード(右クリックで名前をつけて保存)

思考過程を書いていこうと思います。

  • テスト環境の準備
  • 最初にテストのために空白が含まれているディレクトリと空白があるファイル名のファイルを作りました。

    以下のようになります。

    $ tree
    .
    └── grandparent 0
        ├── granpa and granma
        └── parent 1 2
            ├── child 1 2 3
            │   ├── Matthew Mark and Luke
            │   └── grandchild 1 2 3 4
            │       └── Ichiro and Jiro and Saburo and Shiro
            └── mom and dad
    
  • 半角スペースのあるファイル/ディレクトリの列挙
  • まず、findでスペースがあるファイル名/ディレクトリ名を探そうと思いました。

    これは、次で示せます。

    find . -name '* *'
    

    以下のような結果になりました。

    ./grandparent 0
    ./grandparent 0/granpa and granma
    ./grandparent 0/parent 1 2
    ./grandparent 0/parent 1 2/child 1 2 3
    ./grandparent 0/parent 1 2/child 1 2 3/Matthew Mark and Luke
    ./grandparent 0/parent 1 2/child 1 2 3/grandchild 1 2 3 4
    ./grandparent 0/parent 1 2/child 1 2 3/grandchild 1 2 3 4/Ichiro and Jiro and Saburo and Shiro
    ./grandparent 0/parent 1 2/mom and dad
    

    いい感じで空白のあるディレクトリやファイルがリストアップされています。

  • 半角スペースをアンダースコアに置換
  • 上記の結果を使って半角スペースをアンダースコアに置換します。これは簡単で、sedを使えばできます。
    先ほどのコマンドをパイプを使ってsedをつなげてみました。

    find . -name '* *' | sed 's/ /_/g'
    
    ./grandparent_0
    ./grandparent_0/granpa_and_granma
    ./grandparent_0/parent_1_2
    ./grandparent_0/parent_1_2/child_1_2_3
    ./grandparent_0/parent_1_2/child_1_2_3/Matthew_Mark_and_Luke
    ./grandparent_0/parent_1_2/child_1_2_3/grandchild_1_2_3_4
    ./grandparent_0/parent_1_2/child_1_2_3/grandchild_1_2_3_4/Ichiro_and_Jiro_and_Saburo_and_Shiro
    ./grandparent_0/parent_1_2/mom_and_dad
    

    いい感じで半角スペースがアンダースコアに置換されています。
    findの最初の結果と、今の結果をmvでつなげればいけそうじゃないかと思いました。

  • while read line を使って繰り返し
  • findの結果を一行ずつ読み込んで作業しようと思いました。

    具体的には

    • findを使って検索結果を表示
    • 1行ずつ読み込んで、その結果を変数 line に取り込む(read line)
    • $lineの内容をsedを使って半角スペースをアンダースコアに変換し、新たに newline という変数に代入
    • mv “$line” $newline として変更

    という内容ならば行けるかと思いました。

    ということで以下のように書いてみました。

    find . -name '* *' | \
    while read line
    do newline=$(echo $line | sed 's/ /_/g')
        echo $newline
        mv "$line" $newline
    done
    

    これを実行してみました。すると、次のようなエラーが出ました。

    ./grandparent_0
    ./grandparent_0/granpa_and_granma
    mv: `./grandparent 0/granpa and granma' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparent_0/parent_1_2
    mv: `./grandparent 0/parent 1 2' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparent_0/parent_1_2/child_1_2_3
    mv: `./grandparent 0/parent 1 2/child 1 2 3' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparent_0/parent_1_2/child_1_2_3/Matthew_Mark_and_Luke
    mv: `./grandparent 0/parent 1 2/child 1 2 3/Matthew Mark and Luke' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparent_0/parent_1_2/child_1_2_3/grandchild_1_2_3_4
    mv: `./grandparent 0/parent 1 2/child 1 2 3/grandchild 1 2 3 4' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparent_0/parent_1_2/child_1_2_3/grandchild_1_2_3_4/Ichiro_and_Jiro_and_Saburo_and_Shiro
    mv: `./grandparent 0/parent 1 2/child 1 2 3/grandchild 1 2 3 4/Ichiro and Jiro and Saburo and Shiro' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparent_0/parent_1_2/mom_and_dad
    mv: `./grandparent 0/parent 1 2/mom and dad' を stat できません: そのようなファイルやディレクトリはありません
    

    最初はうまくいっていますが、その後はうまくいっていません。
    そこで気づきました。親ディレクトリの半角スペースがアンダースコアに変わってしまっているので、mvで$lineを指定してもそのディレクトリがなくなってしまっているわけです。

    これを回避するにはどうしたらいいでしょうか?

  • find の -maxdepth オプションを使ってディレクトリの階層を制限する
  • ちょっと考えて、find の -maxdepth オプションを使えばいいのではないかと思いました。

    気を取り直して上記のスクリプトに、maxdepthオプションをつけてみました。ワンライナー化すると以下のようになります。

    find . -maxdepth 1 -name '* *' | while read line; do newline=$(echo $line | sed 's/ /_/g'); echo $newline; mv "$line" $newline; done
    

    これを実行します。

    ./grandparent_0
    

    そうするとエラーが出ずに、最初のディレクトリが無事に変換されました。

    次に、maxdepth 2 とします。

    find . -maxdepth 2 -name '* *' | while read line; do newline=$(echo $line | sed 's/ /_/g'); echo $newline; mv "$line" $newline; done
    
    ./grandparent_0/granpa_and_granma
    ./grandparent_0/parent_1_2
    

    無事に一段深いところで作業ができました。

  • forを使ってループ化
  • これを繰り返していけばいいということは、ループを使えばいいということになります。

    何段階まで奥に潜るかをどう決めたらいいかと考えた所、find -type d | wc -l を使えば、いいかなと思って試してみました。

    $ find . -type d | wc -l
    
    5
    

    無事にディレクトリの階層数を同定できました。for と seq を組み合わせれば以下のようにできます。

    for i in $(seq $(find . -type d | wc -l))
    do
        find . -maxdepth $i -name '* *' | \
        while read line
        do newline=$(echo $line | sed 's/ /_/g')
            echo $newline
            mv "$line" $newline
        done
    done
    

    これを走らせてみました。すると、

    ./grandparent_0
    ./grandparent_0/granpa_and_granma
    ./grandparent_0/parent_1_2
    ./grandparent_0/parent_1_2/child_1_2_3
    ./grandparent_0/parent_1_2/mom_and_dad
    ./grandparent_0/parent_1_2/child_1_2_3/Matthew_Mark_and_Luke
    ./grandparent_0/parent_1_2/child_1_2_3/grandchild_1_2_3_4
    ./grandparent_0/parent_1_2/child_1_2_3/grandchild_1_2_3_4/Ichiro_and_Jiro_and_Saburo_and_Shiro
    

    無事にできました!

これに若干の飾りをつけたのがスクリプトになります。

2 thoughts on “再帰的にファイル名やディレクトリ名にある半角スペースをアンダースコア ( _ )で置き換えるスクリプト: Linux & Mac 対応

  1. こんにちは。ubuntu と画像解析を検索していて、こちらのページにたどりつきました。
    16.04はremastersysは使えなかったのではないでしょうか?
    当方LinuxLiveUSB起動の専門です。
    Linux と”私の名前”で検索していただければ私のことはお調べいただけますので、メールいただけないでしょうか?
    ご協力できることがあると思います。

    • 金子様

      ご指摘の通り、Ubuntu 16.04に対してdefaultのremastersysは使うことができません。
      私はremastersysの作者に連絡をしてソースコードを入手し、Ubuntu 16.04でも使えるように改変して使用しております。
      なので、問題なく使うことができています。
      ということで、現在、特にご協力をいただく必要はなさそうです。どうもありがとうございます。

コメントを残す