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

# 背景

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

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

```sh
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 のバックアップを探す](https://support.apple.com/ja-jp/HT204215)」より (2018年11月18日)

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

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

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

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

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

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

```
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
```

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

```sh
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 にアップデートしていた場合は、以下のエラーが出力されます。

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

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

[^1]: [macOS 10.14 Mojaveでは新しいプライバシー保護機能のため、ホームディレクトリ以下でもターミナルからアクセスするさいには「フルディスクアクセス」設定が必要に。](https://applech2.com/archives/macos-mojave-new-privacy-full-disk-access.html)
そのため、以下のようにターミナルにフルディスクアクセス権限を割り当てることで、このエラーを回避します[^2]。

[^2]: [ターミナルでコマンド実行時に「Operation not permitted」と表示される場合の対処法](https://gori.me/mac/mac-tips/112082)


![screenshot01-1.png](https://qiita-image-store.s3.amazonaws.com/0/233927/0772ff71-85d6-212d-58e8-37acd27666a2.png)

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

```sh
/Volumes/<外付けドライブのデバイス名>
```
そのため、iPhone のバックアップファイル移動先として、以下のディレクトリを作成します。

```sh
/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` が、想定通りのディレクトリ構成か確認します。

```sh
# $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 は終了させます。

```sh
# バックアップファイルをコピーする前の状態確認
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` 内に作成します。

```sh
# バックアップフォルダ退避前の状態確認
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](https://qiita-image-store.s3.amazonaws.com/0/233927/d828da9e-7437-6e33-0635-7e0eedb8d973.png)

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

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


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

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

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

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

`$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
```

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

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

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

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

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

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

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

```sh
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]<sup>,</sup>[^4]<sup>,</sup>[^5]はそのまま動かず、地味に困った。また、コマンド作成直後は、  `find` コマンドの `-ls` オプションや `-exec` オプションを知らず、`ls` や `xargs` コマンドをパイプして実装する、という回り道をしてしまった。 


[^3]: [ディレクトリ中のファイルサイズ合計値を バイト表示](http://sonic64.com/2004-05-26.html)

[^4]:  [あるディレクトリの合計サイズ容量を知りたいときのコマンド](http://lab.geo.jp/blog/linux/dir-size-command.html)

[^5]: [指定ディレクトリ以下のファイルサイズの合計を取得するワンライナー](https://memo.sugyan.com/entry/20090421/1240302386#20090421f1)



## 3. man を テキストファイルに保存する方法
`col` を使う。
`find`, `du`, `ls` のオプションは、GNU 版と macOS 版でそこそこ違っていた。そのため、マニュアルをしっかり読み込むんだ。やはり、`man` コマンドは偉大だった。

```
man find | col -bx > man_find.txt
```

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

- [FreeBSD 日本語マニュアル検索](http://www.koganemaru.co.jp/search.html)
    - ちゃんと動いている (2018年11月18日時点)
- [FreeBSD 用の日本語マニュアルを作るプロジェクト
](http://www.jp.freebsd.org/man-jp/)
    - `Internal Server Error` が出る。(2018年11月18日時点)

なぜ macOS なのに、 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]。

[^6]: [Linuxでファイルコピー時に進捗状況を確認する6つの方法](https://orebibou.com/2015/07/linuxでファイルコピー時に進捗状況を確認する6つの/)

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

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

 -  `\` の入力方法 : `option` + `¥`

[^7]: [Macにおけるバックスラッシュ（\）の入力方法](https://qiita.com/miyohide/items/6cb8967282d4b2db0f61)