【merge処理】
写真整理スクリプトを改良した
(dittoからmvへ)

関連記事

1. 移動スクリプトを書き直した

NASに写真を整理するスクリプトを書き直しました。
運用の流れはシンプルで、inboxフォルダに写真をドラッグして溜めておき、スクリプトを実行してarchiveフォルダに統合します。
溜まったら実行、また溜まったら実行、という繰り返しです。

2. 元のコードの問題点(ditto)

元のコードは、dittoでinboxをarchiveにコピーし、rmでinboxを空にする流れでした。

元のコード:問題点 #!/bin/sh SRC=/Volumes/nas/inbox DST=/Volumes/nas/archive ditto $SRC $DST -V rm -r $SRC/* ① コピー+削除 中断で両側に重複ファイル ② 同名ファイルを上書き 確認なしで消える ③ エラー処理なし 失敗しても rm が実行される ditto + rm の組み合わせが抱えるリスク
#!/bin/sh

SRC=/Volumes/nas/inbox
DST=/Volumes/nas/archive

ditto $SRC $DST -V
rm -r $SRC/*
Code language: PHP (php)

このコードには、問題があるとは思いながら、そのままにしていました。
というのも、dittoはコピーなので、rmの前に中断すると両フォルダに同じファイルが残ってしまうのです。

また、同名ファイルを黙って上書きするため、別の日に撮った同名ファイルが消えます1
エラー処理もないので、dittoが途中で失敗してもrmが走ってしまいます。

3. 改善したコード(mv)

そこで、生成AIを使って、コードを書き直しました。

改善したコード:4つの変更点 set -eu エラーで即座に停止 未定義変数もエラー扱い mv 参照書き換えで移動 中断しても重複しない photo(1).jpg 同名ファイルを連番保存 上書きなし・拡張子なしにも対応 -print0 スペース入り名前に対応 ヌル文字区切りで安全処理 ditto+rm → mv に置き換え、4点を同時に解消
#!/bin/sh
set -eu
SRC="/Volumes/nas/inbox"
DST="/Volumes/nas/archive"

find "$SRC" -type f -print0 | while IFS= read -r -d '' file; do
    rel="${file#${SRC}/}"
    dest="$DST/$rel"
    destdir="$(dirname "$dest")"
    mkdir -p "$destdir"
    if [ -e "$dest" ]; then
        base="${dest%.*}"
        ext="${dest##*.}"
        if [ "$base" = "$dest" ]; then
            ext=""
        else
            ext=".$ext"
        fi
        i=1
        while [ -e "${base}(${i})${ext}" ]; do
            i=$((i + 1))
        done
        dest="${base}(${i})${ext}"
    fi
    mv "$file" "$dest"
done
Code language: PHP (php)

まず、set -euを先頭に加えました。
-eはコマンドがゼロ以外の終了コードを返した時点でスクリプトを即座に終了させ、-uは未定義変数の参照をエラーとして扱います2
これで、エラー時には意図しない動作を中断できます。

また、dittormの組み合わせからmvに切り替えました。
NAS内の移動であればmvは実体をコピーせず参照を書き換えるだけです。
途中で止めても重複は発生せず、未処理のファイルはinboxに残るため再実行で続きから処理できます。

ただし、同名ファイルを上書きせず連番で保存するようにしました。
photo.jpgが既にある場合はphoto(1).jpgとして保存します。

find -print0read -d ''の組み合わせでスペースや改行を含むファイル名に対応しました3
macOSの写真ファイルはスペース入りの名前が珍しくないため、スクリプト処理で注意が必要な部分です。

4. NAS上での動作について

ネットワーク越しのmvはローカルと異なり、内部でコピーと削除に分解されることがあります4

NAS上での動作 ① Finderで確認 NASフォルダを開いて マウント状態を確認 ② スクリプト実行 mv でファイルを 1件ずつ移動 ③ 完了 inboxは空にせず残る 次回も使い回せる ⚠ 注意点 NASをまたぐ mv はコピー+削除に分解される場合がある 切断時は set -e でスクリプトが停止、未処理ファイルはinboxに残る スリープ・再起動後はマウントが解除される(Finderで再接続)

接続が切れた場合はset -eでスクリプトが止まりますが、移動の途中で切断されるとファイルが中途半端な状態になる可能性はゼロではありません。
また、スクリプト実行前にFinderでNASフォルダを開いてマウント状態を確認しておくと安心です5

  1. dittoは同名ファイルを確認なしで上書きします。manページには “ditto overwrites identically named files in the target directory without prompting for confirmation” と明記されています。 – ditto Man Page – SS64.com
  2. -e-uはシェルスクリプトの定番的な安全策で、まとめて set -eu と書くことが推奨されています。 – The Set Builtin – GNU Bash Reference Manual
  3. ヌル文字はUnixのファイル名に含むことができない唯一の文字です。find -print0でヌル文字区切りの出力を生成し、IFS= read -r -d ''でヌル文字を区切り文字として読み込むことで、スペースや改行を含むファイル名も1ファイルとして正しく処理できます。 – How to Properly Loop Through File Names Returned by the Find Command in Bash
  4. mvが同一ファイルシステム内であればカーネルのrename(2)システムコールで完結しますが、NFSなどのネットワークファイルシステムをまたぐ場合はコピーと削除に分解されます。 – Things UNIX can do atomically
  5. macOSはFinderでネットワークフォルダをクリックした時点でSMBまたはNFS接続を確立し、/Volumes/以下にマウントします。スリープや再起動、ネットワーク切断後は自動再接続されないため、スクリプト実行前に毎回確認する運用が現実的です。自動マウントが必要な場合はautofsや AutoMounter などのツールを利用する方法もあります。 – 5 options for auto-mounting network shares on MacOS