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/*
Code language: PHP (php)
このコードには、問題があるとは思いながら、そのままにしていました。
というのも、dittoはコピーなので、rmの前に中断すると両フォルダに同じファイルが残ってしまうのです。
また、同名ファイルを黙って上書きするため、別の日に撮った同名ファイルが消えます1。
エラー処理もないので、dittoが途中で失敗してもrmが走ってしまいます。
3. 改善したコード(mv)
そこで、生成AIを使って、コードを書き直しました。
#!/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。
これで、エラー時には意図しない動作を中断できます。
また、dittoとrmの組み合わせからmvに切り替えました。
NAS内の移動であればmvは実体をコピーせず参照を書き換えるだけです。
途中で止めても重複は発生せず、未処理のファイルはinboxに残るため再実行で続きから処理できます。
ただし、同名ファイルを上書きせず連番で保存するようにしました。photo.jpgが既にある場合はphoto(1).jpgとして保存します。
find -print0とread -d ''の組み合わせでスペースや改行を含むファイル名に対応しました3。
macOSの写真ファイルはスペース入りの名前が珍しくないため、スクリプト処理で注意が必要な部分です。
4. NAS上での動作について
ネットワーク越しのmvはローカルと異なり、内部でコピーと削除に分解されることがあります4。
接続が切れた場合はset -eでスクリプトが止まりますが、移動の途中で切断されるとファイルが中途半端な状態になる可能性はゼロではありません。
また、スクリプト実行前にFinderでNASフォルダを開いてマウント状態を確認しておくと安心です5。
dittoは同名ファイルを確認なしで上書きします。manページには “ditto overwrites identically named files in the target directory without prompting for confirmation” と明記されています。 – ditto Man Page – SS64.com-eと-uはシェルスクリプトの定番的な安全策で、まとめてset -euと書くことが推奨されています。 – The Set Builtin – GNU Bash Reference Manual- ヌル文字はUnixのファイル名に含むことができない唯一の文字です。
find -print0でヌル文字区切りの出力を生成し、IFS= read -r -d ''でヌル文字を区切り文字として読み込むことで、スペースや改行を含むファイル名も1ファイルとして正しく処理できます。 – How to Properly Loop Through File Names Returned by the Find Command in Bash mvが同一ファイルシステム内であればカーネルのrename(2)システムコールで完結しますが、NFSなどのネットワークファイルシステムをまたぐ場合はコピーと削除に分解されます。 – Things UNIX can do atomically- macOSはFinderでネットワークフォルダをクリックした時点でSMBまたはNFS接続を確立し、
/Volumes/以下にマウントします。スリープや再起動、ネットワーク切断後は自動再接続されないため、スクリプト実行前に毎回確認する運用が現実的です。自動マウントが必要な場合はautofsや AutoMounter などのツールを利用する方法もあります。 – 5 options for auto-mounting network shares on MacOS