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

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

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

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

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

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

    1
    2
    3
    4
    5
    mkdir -p 'spaces_test/grandparents dir/parents dir/children dir/grandchildren dir'
    touch spaces_test/grandparents\ dir/'grandpa and grandma'
    touch spaces_test/grandparents\ dir/parents\ dir/'dad and mom'
    touch spaces_test/grandparents\ dir/parents\ dir/children\ dir/'Matthew Mark and Luke'
    touch spaces_test/grandparents\ dir/parents\ dir/children\ dir/grandchildren\ dir/'Peter Jacob John Paul'

    treeで確認すると、以下のようになります。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    $ cd spaces_test
    $ tree
    .
    └── grandparents dir
        ├── grandpa and grandma
        └── parents dir
            ├── children dir
            │   ├── Matthew Mark and Luke
            │   └── grandchildren dir
            │       └── Peter Jacob John Paul
            └── dad and mom
  • 半角スペースのあるファイル/ディレクトリの列挙
  • まず、findでスペースがあるファイル名/ディレクトリ名を探そうと思いました。

    これは、次で示せます。

    1
    find . -name '* *'

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

    1
    2
    3
    4
    5
    6
    7
    8
    ./grandparents dir
    ./grandparents dir/parents dir
    ./grandparents dir/parents dir/dad and mom
    ./grandparents dir/parents dir/children dir
    ./grandparents dir/parents dir/children dir/grandchildren dir
    ./grandparents dir/parents dir/children dir/grandchildren dir/Peter Jacob John Paul
    ./grandparents dir/parents dir/children dir/Matthew Mark and Luke
    ./grandparents dir/grandpa and grandma

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

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

    1
    find . -name '* *' | sed 's/ /_/g'
    1
    2
    3
    4
    5
    6
    7
    8
    ./grandparents_dir
    ./grandparents_dir/parents_dir
    ./grandparents_dir/parents_dir/dad_and_mom
    ./grandparents_dir/parents_dir/children_dir
    ./grandparents_dir/parents_dir/children_dir/grandchildren_dir
    ./grandparents_dir/parents_dir/children_dir/grandchildren_dir/Peter_Jacob_John_Paul
    ./grandparents_dir/parents_dir/children_dir/Matthew_Mark_and_Luke
    ./grandparents_dir/grandpa_and_grandma

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

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

    具体的には

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

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

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

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

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

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    ./grandparents_dir
    ./grandparents_dir/parents_dir
    mv: './grandparents dir/parents dir' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparents_dir/parents_dir/dad_and_mom
    mv: './grandparents dir/parents dir/dad and mom' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparents_dir/parents_dir/children_dir
    mv: './grandparents dir/parents dir/children dir' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparents_dir/parents_dir/children_dir/grandchildren_dir
    mv: './grandparents dir/parents dir/children dir/grandchildren dir' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparents_dir/parents_dir/children_dir/grandchildren_dir/Peter_Jacob_John_Paul
    mv: './grandparents dir/parents dir/children dir/grandchildren dir/Peter Jacob John Paul' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparents_dir/parents_dir/children_dir/Matthew_Mark_and_Luke
    mv: './grandparents dir/parents dir/children dir/Matthew Mark and Luke' を stat できません: そのようなファイルやディレクトリはありません
    ./grandparents_dir/grandpa_and_grandma
    mv: './grandparents dir/grandpa and grandma' を stat できません: そのようなファイルやディレクトリはありません

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

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

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

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

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

    これを実行します。
    そうすると何も結果が出ずに終わりました。既に上記の試みの中で空白がアンダースコアに変換されていたからです。

    次に、maxdepth 2 とします。

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

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

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

    何段階まで奥に潜るかをどう決めたらいいかと考えた所、find -type d と awk を組み合わせればいいなと思いました。awkで区切り記号に / を入れることで、ディレクトリの個数を NF で確認できます。ポイントは、NFだけだと純粋な数字が表示されますが、$NFとすると、たとえば、NFが4の場合は$4と同じになるので、4つ目のフィールドの文字列が表示されます

    1
    2
    3
    4
    5
    6
    $ find . -type d | awk -F/ '{ print NF }'
    2
    2
    3
    4
    5

    今の場合は、最大5ということになります。sort と tail を使って、最後の数字だけ取得するようにしたいと思います。

    1
    2
    $ find . -type d | awk -F/ '{ print NF }' | sort | tail -1
    5

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for i in $(seq $(find . -type d | awk -F/ '{ print NF }' | sort | tail -1))
    do
        find . -maxdepth $i -name '* *' | \
        while read line
        do newline=$(echo $line | sed 's/ /_/g')
            echo $newline
            mv "$line" $newline
        done
    done

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

    1
    2
    3
    4
    5
    ./grandparents_dir/parents_dir/dad_and_mom
    ./grandparents_dir/parents_dir/children_dir
    ./grandparents_dir/parents_dir/children_dir/grandchildren_dir
    ./grandparents_dir/parents_dir/children_dir/Matthew_Mark_and_Luke
    ./grandparents_dir/parents_dir/children_dir/grandchildren_dir/Peter_Jacob_John_Paul

    無事にできました!

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

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

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

    • 金子様

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

コメントを残す

This site uses Akismet to reduce spam. Learn how your comment data is processed.