2019-06-19

面倒なファイル名は付けずに8文字IDで管理しよう

みなさんはこんな感じのファイル管理を見たことはないでしょうか?

  • レイアウト_トップ画面.xlsx
  • レイアウト_登録画面.xlsx
  • レイアウト_トップ画面_20190101提出版.xlsx
  • レイアウト_登録画面_20190101提出版.xlsx

バージョン管理システムが導入されていないオフィスにいたとき、作業用PCの作業ディレクトリ内がこんな感じでした(笑)。 ちなみにいまはバージョン管理システムが導入されており、少し作業効率が改善されたのを覚えています。 とはいっても、バージョン管理システムもその人の使い方次第で↑のような状況になってしまうのですが……

Web サイトやプレゼンテーション資料を作るときなど、画像素材をいろんなところから収集してくるときがあります。 すると、ファイル名は「猫の画像1.jpg」、「ScreenShot20190101-1.png」、「12de944eff….jpg」など、ファイル名の規則性がバラバラで意味を成さなくなります。 そんなときに、ファイル名を8文字IDに一括で置き換えて統一する方法を紹介します。

    mvhash コマンドを作ってみた

    Linux や Mac をお使いであれば、以下のシェルスクリプトを導入すれば8文字IDに変換できるようになります。

    上記シェルスクリプトを mvhash というファイル名で /usr/localbin などのパスが通っているところに配置します。 そして、以下のようにすれば8文字IDに変換されます。

    $ ls
      82f231898694e893389f7fc7f0d4b2ae1ddfb69ed2d59295603593c8529db673.jpg
      ScreenShot20190101-1.png
      猫の画像1.jpg
    
    $ mvhash *.jpg *.png
      82f231898694e893389f7fc7f0d4b2ae1ddfb69ed2d59295603593c8529db673.jpg -> fff32890.jpg
      猫の画像1.jpg -> 9018ac4f.jpg
      ScreenShot20190101-1.png -> 9ee84598.png
    
    $ ls
      9018ac4f.jpg  9ee84598.png  fff32890.jpg
    

    せっかくなので、mvhash コマンドの解説をしていきたいと思います!💪

    コマンド引数で while ループ

    while [ $# -ne 0 ]
    do
      # $1 を使った処理
      shift
    done
    

    $ mvhash *.jpg *.png のようにワイルドカードを使ったコマンドを実行するとき、bash がワイルドカードの展開をして $ mvhash 猫の画像1.jpg ScreenShot20190101-1.png 82f23189….jpg というコマンドを入力したとみなされます。 よって、mvhash シェルスクリプトでは、与えられたコマンドライン引数ごとにループ処理をすることになります。 while 文のループ条件は「引数が0でないとき」とし、shift コマンドによって引数のシフトというものをします。 これによって第一引数 $1 が削除され、そのあとに続く引数が $1$2、…となります。

    $ mvhash 猫の画像1.jpg ScreenShot20190101-1.png 82f23189….jpg  (引数3つ)
    ↓shift
    $ mvhash ScreenShot20190101-1.png 82f23189….jpg  (引数2つ)
    ↓shift
    $ mvhash 82f23189….jpg  (引数1つ)
    ↓shift
    $ mvhash  (引数0つのためループ終了)
    

    よって、while ループの中で $1 を使った処理を書けばよいということになります。

    すでに8文字ID化されている場合はスキップ

    if [[ $1 =~ ^[0-9a-f]{8}\. ]];  then
      shift
      continue
    fi
    

    ファイル名を8文字ID化するために crc32 というハッシュアルゴリズムを使用するため(後述)、ID は 0~9とa~f の文字を使った8文字となります。 そして、8文字IDをさらに8文字ID化することができてしまうため、これを防ぐためにスキップ処理を入れています。 if [[ $1 =~ ^[0-9a-f]{8}\. ]] によって、与えられたファイル名がすでに8文字IDであるかを 正規表現マッチして条件分岐します。

    crc32 で8文字IDを生成

    hashed=`crc32 <(echo $1)`.${1##*.}
    echo $1' -> '${hashed}
    mv -i "$1" "${hashed}"
    shift
    

    crc32 コマンドは、指定したファイルの内容をもとに crc32 ハッシュを出力するコマンドです。 しかし、今回は指定したファイルの名前をもとに crc32 ハッシュを取得したいと思います。 そこで、bash の <() という構文を使います。 これは、() 内に書いたコマンドによる標準出力を一時的なファイルとして展開する……というものです。

    なかなか説明が難しいのでデバッグ出力をしてみました。 下記の /dev/fd/63 に注目してください。 /dev/fd/63 という仮想的なファイルには、echo test.png による標準出力、つまり「test.png」という文字列が書き込まれています。 これを crc32 コマンドに送ることで、test.png の中身ではなくファイル名をもとに crc32 ハッシュを生成しています。

    $ echo crc32 <(echo test.png)
      crc32 /dev/fd/63
    

    (余談)「8文字」IDにした理由

    今回はファイル名を8文字のIDに置き換えましたが、当方ファイル名は8文字!という謎のこだわりを持っています😤  というのは、だいぶ前にファイル名が8文字までしか使えない OS があったからです。 その名残からか、Linux のコマンド名も伝統的なものは8文字以内に収まっているような気がします。 とはいえさすがにもう 2019 年、docker-compose のように長いコマンド名も普通だし、 無理やり8文字に収めようとすると直感的でなくなってしまう問題点もあります。

    この記事の執筆者

    hato6502

    hata  

    中学生の頃に 6502 という CPU からプログラミングの世界に入りました。 C/C++ で1から作ることも好きですが、最近は変化の激しい IT 業界をみて Web 系にシフトしつつあります。