iPhone
Bash
MacOSX
iTunes
バックアップ

iPhone のバックアップを外付けドライブに移す方法 for macOS

背景

iPhone を何台も持っていると、iPhone の数だけバックアップが作成され、Mac のディスク容量が圧迫されます。

自分の MacBook Air では、iPhone のバックアップファイルは、33 GB の容量でした。写真やビデオを撮りまくる人は、もっとディスク容量が多いと思います。

MacBook-Air:~ username$ source_path="${HOME}/Library/Application Support/MobileSync"
MacBook-Air:~ username$ du -hs "${source_path}"
 33G    /Users/username/Library/Application Support/MobileSync

そのため、iPhone のバックアップを外付けドライブに移動させます。 ( お金がある人は、iCloudを契約してください。その方が安全で楽ちんです。)

そして、本記事で記載している行為は、Apple さん曰く、「厳禁」の行為です。絶対に実施しないでください。

「Backup」フォルダはコピーはできますが、別のフォルダ、外付けドライブ、またはネットワークドライブへの移動は厳禁です。

Apple 「iPhone、iPad、iPod touch のバックアップを探す」より (2018年11月18日)

iPhone のバックアップを外付けドライブに移す

iPhone のバックアップファイルを外付けドライブに移す作業は、以下の流れになります。iPhone のバックアップが消えると大変なので、バックアップファイルの移動は丁寧かつ慎重に確認します。

  1. iPhone のバックアップファイルを確認
  2. バックアップファイルの移動先を確認
  3. バックアップファイルの移動

1. iPhone のバックアップファイルを確認

iPhone のバックアップファイルは、以下のフォルダに格納されています。パスの途中にスペースがあるのが曲者で、コイツのせいでソースコードが少しずつ長くなります。

/Users/<username>/Library/Application Support/MobileSync/Backup"

バックアップファイルがちゃんと存在するか、ディレクトリのパス指定が間違っていないかを確認するため、lsdu でバックアップファイルを確認します。

MacBook-Air:~ username$ source_path="${HOME}/Library/Application Support/MobileSync"
MacBook-Air:~ username$ ls "${source_path}" 
Backup
MacBook-Air:~ username$ du -hs "${source_path}"
 33G    /Users/username/Library/Application Support/MobileSync

ちなみに、バックアップファイルのディレクトリ構造は、以下のようになっています。

MacBook-Air:~ username$ tree -L 2 "${source_path}" 
/Users/username/Library/Application Support/MobileSync
└── Backup
    ├── <guid1>
    ├── <guid2>
    └── <guid2>-<yymmdd>-<hhmmss>

3 directories, 0 files

補足1 : No such file or directory が出た場合

パスの指定が間違っていたら、以下のエラーが出力されます。また、変数をダブルコーテーション " で囲っていない場合も、同様のエラーが出ます。"${source_path}" を、目を皿のようにして確認しましょう。

MacBook-Air:~ username$ ls /hoge
ls: /hoge: No such file or directory

補足2 : Operation not permitted が出た場合

macOS 10.14 Mojave にアップデートしていた場合は、以下のエラーが出力されます。

MacBook-Air:~ username$ ls "/Users/username/Library/Application Support/MobileSync"
ls: MobileSync: Operation not permitted

このエラーは、コマンドの先頭に sudo を付けても解決しません。これは Mojave から導入された新しいプライバシー保護のせいです1

そのため、以下のようにターミナルにフルディスクアクセス権限を割り当てることで、このエラーを回避します2

screenshot01-1.png

2. バックアップファイルの移動先を確認

次はバックアップファイルの移動先を確認します。外付けドライブ (USB メモリや外付けHDD) を Mac に接続すると、以下のパスにマウントされます。

/Volumes/<外付けドライブのデバイス名>

そのため、iPhone のバックアップファイル移動先として、以下のディレクトリを作成します。

/Volumes/<外付けドライブのデバイス名>/MobileSync

ディレクトリの作成は、以下のように行います。

# 移動先のデバイス名を確認
MacBook-Air:~ username$ ls /Volumes/
Macintosh HD        <device_name>
Preboot
# デバイス名の確認
MacBook-Air:~ username$ device_name="/Volumes/<device_name>"
MacBook-Air:~ username$ ls "${device_name}"
MacBook-Air:~ username$ dest_path="${device_name}/MobileSync"
MacBook-Air:~ username$ mkdir -p "${dest_path}"
MacBook-Air:~ username$ ls "${dest_path}"

3. バックアップファイルの移動

iPhone のバックアップファイルを移動します。iPhone のバックアップを移動する前に $source_path$dest_path が、想定通りのディレクトリ構成か確認します。

# $source_path と $dest_path が、想定通りの構成か確認
function can_migrate_iphone_backup () {
    # $source_path が存在するか確認
    if [ ! -d "${source_path%/}" ]; then
        echo -n "\$source_path does NOT exists"
        echo " : \"${source_path}\""
        echo "Please set \$source_path to correct value."
        return 1
    fi

    # $source_path 内に Backup フォルダが存在するか確認
    if [ ! -d "${source_path%/}/Backup" ]; then
        echo -n "Backup folder does NOT exists in source folder"
        echo " : \"${source_path%/}/Backup\""
        echo "Please set \$source_path to correct value."
        return 1
    fi

    # $dest_path が存在するか確認
    if [ ! -d "${dest_path%/}" ]; then
        echo -n "\$dest_path does NOT exists"
        echo " : \"${dest_path}\""
        echo "Please set \$dest_path to correct value."
        return 1
    fi

    # $dest_path 内に Backup フォルダが存在しないことを確認
    if [ -d "${dest_path%/}/Backup" ]; then
        echo -n "Backup folder exists in destination folder."
        echo -n "Backup folder should NOT exist in destination folder."
        echo " : \"${dest_path%/}/Backup\""
        echo "Please set \$dest_path to correct value."
        return 1
    fi

    echo "\$source_path and \$dest_path is NO ploblem for migrate."
}

can_migrate_iphone_backup

上記の can_migrate_iphone_backup を実行し、実行結果が"$source_path and $dest_path is NO ploblem for migrate."と出力されたら、$source_path$dest_path に問題はありません。
問題がないことが確認できたら、 iPhone のバックアップファイルをコピーします。もちろん、コピー前に iTunes は終了させます。

# バックアップファイルをコピーする前の状態確認
function show_folder_size_and_file_count () {
    find "${1%/}" -type f -ls | awk 'BEGIN { sum=0;count=0 }; { sum+=$7;count++;printf "\rtotal %d Byte, %d files",sum,count }; END{ print "" }'
}
show_folder_size_and_file_count "${source_path%/}/Backup"
show_folder_size_and_file_count "${dest_path%/}/Backup"

# (テスト実行)バックアップファイルのコピー
rsync -ahvn --stats "${source_path%/}/Backup" "${dest_path}"

# (本番実行)バックアップファイルのコピー
rsync -ahv --stats "${source_path%/}/Backup" "${dest_path}"

# バックアップファイルをコピーした後の状態確認
show_folder_size_and_file_count "${source_path%/}/Backup"
show_folder_size_and_file_count "${dest_path%/}/Backup"

バックアップファイルをコピーしたら、 $source_path 側のバックアップファイルの名前を変更して一時的に退避します。そして、コピーした $dest_path 側のバックアップファイルへのシンボリックリンクを $source_path 内に作成します。

# バックアップフォルダ退避前の状態確認
show_folder_size_and_file_count "${source_path%/}/Backup"
show_folder_size_and_file_count "${source_path%/}/Backup_tmp"

# バックアップフォルダの退避
mv "${source_path%/}/Backup" "${source_path%/}/Backup_tmp"

# バックアップフォルダ退避後の状態確認
show_folder_size_and_file_count "${source_path%/}/Backup"
show_folder_size_and_file_count "${source_path%/}/Backup_tmp"

# シンボリックリンクの作成
ls "${source_path%/}"
ln -s "${dest_path%/}/Backup" "${source_path}"
ls "${source_path%/}"

# シンボリックリンクの削除 (想定外の挙動になったとき)
# unlink "${source_path}"

iTunes を起動し、 iPhone のバックアップファイルが正常に表示されていれば、移動は「ほぼ」完了です。

scrennshot02-2.png

最後に退避していたバックアップファイルを削除すれば、バックアップファイルを外付けドライブに移動する作業は「完了」です。お疲れ様でした。

ls "${source_path%/}/Backup_tmp"
rm -r "${source_path%/}/Backup_tmp"
ls "${source_path%/}/Backup_tmp"

bash のプログラミングで注意すること、つまづいたこと、落とし穴

1. ファイルやディレクトリのパスを格納する変数は、"${value}"と書く

この様な書き方にする理由は、以下の2つである。
1. スペースが入った変数を関数の引数にすると、変数が 2 つの引数と認識される
2. ダブルコーテーション内で変数と文字列を結合するとき、どこまでが変数がわかりにくい

1. スペースが入った変数を関数の引数にすると、変数が 2 つの引数と認識される

$value にスペースを含むパスを設定した場合、<コマンド> $value が意図しない挙動をする。コマンドの引数として $value を 1 つ渡したつもりが、 スペースで区切られた複数の引数をコマンドに渡したことになる。そのため、変数をダブルコーテーションで囲む。

MacBook-Air:tmp username$ tree
.
└── hoge\ hoge
    └── piyo

2 directories, 0 files
MacBook-Air:tmp username$ path="hoge hoge/piyo"
MacBook-Air:tmp username$ ls "$path"
MacBook-Air:tmp username$ ls $path
ls: hoge: No such file or directory
ls: hoge/piyo: No such file or directory

2. ダブルコーテーション内で変数と文字列を結合するとき、どこまでが変数がわかりにくい

$value$valuet の 2 つの変数が存在する場合、"$valuetmp"は、どっちの変数か少し悩む。そのため、どこまでが変数名か迷わないようにするために "${value}tmp" と、括弧で囲む。

2. du のブロックサイズ表示、ディレクトリのファイルサイズ表示

バックアップファイルのコピー後、コピー元とコピー先のディレクトリサイズを比較しようとした。しかし、ls -l では、フォルダ内のファイルサイズの合計を表示していなかった。

  • ls -l コマンドの補足
    • ls -l コマンドを実行して、1行目に表示される total は、ディレクトリ内のファイル (ディレクトリは含まない) のブロックサイズ
    • ls -l コマンドを実行して、ディレクトリの右に表示されるサイズは i-node のブロックサイズ

そのため、du でディレクトリのサイズを比較したが、du で表示されたサイズがコピー元とコピー先で異なっていた。これは du が、ファイルサイズではなくブロックサイズを表示しているためだった。

そのため、findawk を使って、地道にディレクトリのサイズを集計するコマンドを作成した。

function show_folder_size_and_file_count () {
    find "${dest_path}" -type f -ls | awk 'BEGIN { sum=0;count=0 }; { sum+=$7;count++;printf "\rtotal %d Byte, %d files",sum,count }; END{ print "" }'
}

macOS の find コマンドには、-b オプションが存在しないため、ネット上のコマンド例3,4,5はそのまま動かず、地味に困った。また、コマンド作成直後は、 find コマンドの -ls オプションや -exec オプションを知らず、lsxargs コマンドをパイプして実装する、という回り道をしてしまった。

3. man を テキストファイルに保存する方法

col を使う。
find, du, ls のオプションは、GNU 版と macOS 版でそこそこ違っていた。そのため、マニュアルをしっかり読み込むんだ。やはり、man コマンドは偉大だった。

man find | col -bx > man_find.txt

あと、インターネットでから利用可能な FreeBSD の日本語マニュアルを以下に列挙する。

なぜ macOS なのに、 FreeBSD のマニュアルを見るのかと言うと、 macOS は、Darwin (Mach Kernel + FreeBSD) を利用した OS だからである。実際、macOS の man ls コマンドを実行すると、最初の行や末尾に BSD の文字が表示される。

MacBook-Air:tmp username$ man ls | sed -n 2p
LS(1)                     BSD General Commands Manual                    LS(1)
MacBook-Air:tmp username$ man ls | tail -n 1
BSD                              May 19, 2002                              BSD

4. ファイルのコピー状況を把握する方法

大量 (数万個程度) のファイルをコピーする際、ファイルのコピーが、数万個中の何個完了したのか知りたかった。しかし、初期搭載のコマンドで把握する方法はなかった6

5. bash のエスケープ文字 \ を入力する方法

bash の エスケープ文字は \ と知っていたが、入力方法が分からず非常に困った。まさか option + ¥ の同時押しとは...7。知らなかった。

  • \ の入力方法 : option + ¥