Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2014-02-08

iOS7のユーザ辞書をリセットするスクリプト

前回までに自分のiPhoneMacBookのユーザ辞書は同期するようになったのだけど、その手順はちょっと複雑だった。再び同期できなくなった時のことを考えて、素早く、安全に、iPhoneのユーザ辞書をリセットするスクリプトにしておこうと、思い立った。

開発&実行環境

  • MacBook OSX 10.9.1
  • iPhone iOS7

mbdbを編集するRubyコード(reset_keyboard.rb)

  • いちいちテキストに書き出さずとも、mbdbの内容を取捨選択できるように改良した。
  • 「mbdb.reject!(/HomeDomain::Library\/Keyboard/)」によって、キーボードに関連する設定ファイルを一括削除している。
# encoding: ASCII-8BIT

class Mbdb
  def initialize(mbdb_filename, verbose = false)
    @verbose = verbose
    process_mbdb_file(mbdb_filename)
  end

  def to_text_file(filename)
    File.open(filename, 'w') do |f|
      @mbdb.each do |r|
        f.puts r.values.inspect
      end
    end
  end

  def to_mbdb_file(filename)
    File.open(filename, 'wb') do |f|
      f.write(binary_data)
    end
  end

  def reject!(regexp)
    @mbdb.reject! {|r| "#{r[:domain]}::#{r[:filename]}" =~ regexp}
  end

  def fill!(regexp)
    @mbdb.reject! {|r| !("#{r[:domain]}::#{r[:filename]}" =~ regexp)}
  end

private

  # Return an integer (big-endian) and new offset from the current offset
  def get_int(data, offset, intsize)
    value = 0
    while intsize > 0
      value = (value<<8) + data[offset].ord
      offset += 1
      intsize -= 1
    end
    return value, offset
  end

  # Return a string and new offset from the current offset into the data
  def get_string(data, offset)
    return 'ffff', offset + 2 if data[offset] == 0xFF.chr and data[offset + 1] == 0xFF.chr # Blank string
    length, offset = get_int(data, offset, 2) # 2-byte length
    value = data[offset...(offset + length)]
    value = '0000' if value == ""
    return value, (offset + length)
  end

  def put_int(data_10, intsize)
    data_16 = "%0#{intsize*2}x" % data_10
    [data_16].pack('H*')
  end

  def put_string(str)
    return "\xff\xff" if str == 'ffff'
    return "\x00\x00" if str == '0000'
    return [str.length].pack('n') + str
  end

  def process_mbdb_file(filename)
    @mbdb = Array.new
    data = File.open(filename, 'rb') { |f| f.read }
    puts "MBDB file read. Size: #{data.size}"
    raise 'This does not look like an MBDB file' if data[0...4] != 'mbdb'
    offset = 4
    offset += 2 # value x05 x00, not sure what this is
    while offset < data.size
      fileinfo = {}
      fileinfo[:domain], offset = get_string(data, offset)
      fileinfo[:filename], offset = get_string(data, offset)
      fileinfo[:linktarget], offset = get_string(data, offset)
      fileinfo[:datahash], offset = get_string(data, offset)
      fileinfo[:unknown1], offset = get_string(data, offset)
      fileinfo[:mode], offset = get_int(data, offset, 2)
      fileinfo[:unknown2], offset = get_int(data, offset, 4)
      fileinfo[:unknown3], offset = get_int(data, offset, 4)
      fileinfo[:userid], offset = get_int(data, offset, 4)
      fileinfo[:groupid], offset = get_int(data, offset, 4)
      fileinfo[:mtime], offset = get_int(data, offset, 4)
      fileinfo[:atime], offset = get_int(data, offset, 4)
      fileinfo[:ctime], offset = get_int(data, offset, 4)
      fileinfo[:filelen], offset = get_int(data, offset, 8)
      fileinfo[:flag], offset = get_int(data, offset, 1)
      fileinfo[:propertynum], offset = get_int(data, offset, 1)
      fileinfo[:properties] = {}
      (0...(fileinfo[:propertynum])).each do |i|
        propname, offset = get_string(data, offset)
        propval, offset = get_string(data, offset)
        fileinfo[:properties][propname] = propval
      end
      @mbdb << fileinfo
    end
    @mbdb
  end

  def binary_data
    bin = "mbdb\x05\x00"
    @mbdb.each do |h|
      bin << put_string(h[:domain])
      bin << put_string(h[:filename])
      bin << put_string(h[:linktarget])
      bin << put_string(h[:datahash])
      bin << put_string(h[:unknown1])
      bin << put_int(h[:mode], 2)
      bin << put_int(h[:unknown2], 4)
      bin << put_int(h[:unknown3], 4)
      bin << put_int(h[:userid], 4)
      bin << put_int(h[:groupid], 4)
      bin << put_int(h[:mtime], 4)
      bin << put_int(h[:atime], 4)
      bin << put_int(h[:ctime], 4)
      bin << put_int(h[:filelen], 8)
      bin << put_int(h[:flag], 1)
      bin << put_int(h[:propertynum], 1)
      h[:properties].each do |k, v|
        bin << put_string(k)
        bin << put_string(v)
      end
    end
    bin
  end

end

if RUBY_VERSION >= "1.9" then
  `mv '#{ARGV[0]}' '#{ARGV[0]}.back'`
  mbdb = Mbdb.new("#{ARGV[0]}.back", true)
  mbdb.reject!(/HomeDomain::Library\/Keyboard/) # Reset UserDictionary
  mbdb.to_text_file("#{ARGV[0]}.txt")
  mbdb.to_mbdb_file(ARGV[0])
else
  puts 'Needs Ruby version 1.9 or later'
end

バックアップを保護するシェルスクリプト(reset_keyboard.sh)

  • 上記Rubyコードがmbdbを修正する前に、バックアップをコピーして、安全に作業する環境を整える。
#!/bin/sh
current_dir=`dirname $0`
target_dir="$1"
reset_dir="${target_dir}-reset-user-dictionary"

rm -fr "$reset_dir";
cp -r "$target_dir" "$reset_dir"

display_name=`defaults read "${reset_dir}/Info.plist" "Display Name"`
defaults write "${reset_dir}/Info.plist" "Display Name" -string "${display_name}-reset-user-dictionary"

ruby "${current_dir}/reset_keyboard.rb" "${reset_dir}/Manifest.mbdb"

バックアップリストを返すシェルスクリプト(current_backup_list.sh)

  • iPhoneのバックアップは、端末固有のUniqueDeviceIDという英数字が羅列したフォルダに存在する。
  • リセットする端末を指定する時、英数字の羅列では扱いにくいので、端末名を関連づけたリストを返す。
#!/bin/sh
backup_folders=`find "$HOME/Library/Application Support/MobileSync/Backup" -name Info.plist -exec dirname {} \;`
IFS=$'\n'
for f in $backup_folders
do
  deviceID=`defaults read "$f/Manifest.plist" 'Lockdown'|grep UniqueDeviceID|awk '{print $3}'|tr -d ';'`
  folder_name=`basename "$f"`
  if [ "$deviceID" = "$folder_name" ]; then
    echo "`defaults read "$f/Info.plist" 'Display Name'` :: $folder_name"
  fi
done

AppleScriptアプリケーションによるGUI(reset_iOS7_UserDictionary.app)

  • 以上のスクリプトを、AppleScriptアプリケーションとしてまとめてみた。
  • iOS端末を指定すると、上記スクリプトが連携して、ユーザ辞書をリセットしたバックアップを生成するのだ。

 activate
 set current_backup_list_sh to (path to resource "current_backup_list.sh")'s POSIX path
 set reset_keyboard_sh to (path to resource "reset_keyboard.sh")'s POSIX path
 
 set device_list to (do shell script current_backup_list_sh)'s paragraphs
 set selected_item to choose from list device_list with prompt "ユーザ辞書をリセットする端末を選択してください。" with title my name
 if selected_item is false then error number -128 --キャンセル
 set selected_folder to split(selected_item as text, " :: ")'s item 2
 
 display notification "ユーザ辞書をリセット中です..."
 delay 1
 
 set mobilesync_backup to (((path to application support folder from user domain) as text) & "MobileSync:Backup:")'s POSIX path
 do shell script (reset_keyboard_sh & space & quoted form of (mobilesync_backup & selected_folder)) -- & " >& /dev/null &"
 
 display notification "リセットが完了しました。"
 delay 1
 
 
 
 
 on split(src_text, delimiter)
   set last_delimiter to AppleScript's text item delimiters
   set AppleScript's text item delimiters to delimiter
   set res to src_text's text items
   set AppleScript's text item delimiters to last_delimiter
   res
 end split

ダウンロードと使い方


例:MacBookとiPhoneのユーザ辞書をリセットする場合

  • すべての端末(MacBook、iPhone)で、iCloudの「書類とデータ」の同期をオフにする。
iCloudのユーザ辞書のリセット
  • WebブラウザでiCloudにアクセスして、「書類とデータのリセット」を実行する。
MacBookのユーザ辞書のリセット
  • MacBookで、~/Library/Mobile Documents/com~apple~TextInput/ を削除する。(Finderでゴミ箱へ)
iPhoneのユーザ辞書のリセット
  • iPhoneをMacBookに接続する。
  • iTunesを起動して「今すぐバックアップ」を実行する。
    • 事前に、写真や動画をiPhotoなどに取り込んで、カメラロールを空っぽにしておくと、バックアップが素早く完了する。

  • reset_iOS7_UserDictionary.appを実行する。
    • ユーザ辞書の設定がリセットされたバックアップを生成する。

f:id:zariganitosh:20140208141417p:image:w450


  • iTunesで、「バックアップを復元...」を実行する。
  • バックアップリストから「端末名-reset-user-dictionary」を選択して、復元ボタンを押す。

f:id:zariganitosh:20140208135627p:image:w450

  • 復元が成功すれば、iPhoneのユーザ辞書はリセットされているはず。

  • すべての端末(MacBook、iPhone)で、iCloudの「書類とデータ」の同期をオンに戻す。

待つこと暫し、ユーザ辞書の同期が始まるかもしれない。

  • 不要になった「端末名-reset-user-dictionary」は、iTunes >> 環境設定... >> デバイスから削除できる。

f:id:zariganitosh:20140208142910p:image:w450

こじゃるこじゃる 2014/03/11 19:25 このスクリプト、素晴らしいですね。
ずっと辞書同期がうまくいかなくて困ってたんですが、このスクリプトを使って全てのOS X/iOS端末で辞書同期が動くようになりました!
本当にありがとうございました!∩(´・ω・`)∩qrhfgj

aizackaizack 2014/03/11 23:32 こちらのサイトのおかげで、ずっと頭を悩ませていたユーザー辞書のiCloudとの同期トラブルを解消できました!
どうもありがとうございました☆

penpenpenpen 2014/03/14 00:44 はじめまして!検索でたどり着きました。
今、「reset_iOS7_UserDictionary.appを実行する。」の段階なのですが、
ipを回答してファイルをダブルクリックすると、name のタイプを string に返還できません。
というメッセージが出て次に進めないのですが、その場合はどうしたらよろしいでしょうか?
宜しくお願いします!

zariganitoshzariganitosh 2014/03/14 07:58 こじゃる さん、aizackさん、
お役に立てたようで嬉しいです。

penpenさん、
OSX 10.9以降の環境を使っていますか?

height185cmheight185cm 2014/03/19 20:14 はじめまして。こちらのスクリプトで見事に不具合が解決しました。本当にありがとうございました。iMac、iPhone4,5s、iPad air、MacBookと同期していたのですが、全ての端末で日本語入力に不具合が出ていたのでストレスだらけでしたが、こちらのスクリプトで解決してすっきりできました。これで今夜はゆっくり寝られそうです。。。

本当にありがとうございました。

しめじしめじ 2014/03/21 22:07 ユーザー辞書がダメになってiOS7.1にしたことをしてましたがなおりました!ありがとうございます!!
iPhoneのユーザー辞書リセットに15分くらいかかったのですが、その間がドキドキでした><b

zariganitoshzariganitosh 2014/03/31 14:09 height185cmさん、しめじ さん、
お役に立てたようで嬉しいです。

Round-Headed BoyRound-Headed Boy 2014/05/08 16:42 今朝、MacとiPhoneのユーザー辞書が同期できる事を知りました(遅っ)。
ところが設定は問題無いのに、同期できていない…。
ネット上を検索したところ、こちらに辿り着きました。
こちらに記載されている方法を試したところ、
無事同期できるようになりました。
お陰で快適に使える様になりました、ありがとうございます。

zariganitoshzariganitosh 2014/05/08 16:57 Round-Headed Boyさん、
お役に立ててよかったです!
快適な辞書環境を堪能してください。

TomoshibiTomoshibi 2014/06/01 17:40 おかげ様で、諦めていたユーザー辞書の不具合が解消されました。ありがとうございます。

zariganitoshzariganitosh 2014/06/05 02:12 お役に立ててよかったです!

WolfWolf 2014/06/13 00:26 いやー、困っていたんです。
Appleに問い合わせしても頭抱えてしまうばかりで。
この問題が解決できた事で辞書のシンクロができるようになりました。
ついでにiTunesのiOS機器、空き容量誤表示問題も解決できたようです。
ありがとうございました。

白い月の鏡白い月の鏡 2014/06/15 05:10 >> zariganitosh さん
こんにちは。
僕もこのスクリプトに助けられた一人です。ありがとうございます。
トラックバックしているページにも書きましたが、Wolfさんも書いておられるように、このスクリプトは「空き容量誤表示問題」にも有効でした。
帰納的に考えるに、「空き容量誤表示問題」のうち何割か(全部かどうかは判らない)はiPhoneのユーザ辞書破損問題に起因していると考えられると思った次第で。
これは言い換えると「iPhoneのユーザ辞書破損問題に起因している空き容量誤表示問題」(であるかどうかは結果論的にしかわからないけども)も、このスクリプトで解決できる可能性が大きいということになります。
お礼方々、ご報告まで。

春色春色 2014/06/24 09:17 とても簡単でわかりやすい説明付きで実行しようと思っています
そこでひとつの質問なのですが書類をリセットするということはGarageBandやpagesのデータも当然消えちゃいますよね(^_^;)?
これだけ引っかかっててなかなか実行できずにいます
こればかりはMacにインポートするしかありませんよね(^_^;)

zariganitoshzariganitosh 2014/06/24 10:15 Wolfさん、白い月の鏡さん、

お役に立てたようでよかったです。
ちなみに、空き容量の問題(その他領域が異常に大きく確保されてしまう状態)については、このスクリプトを使わずとも、単に復元するだけで直ることもあるようです。

zariganitoshzariganitosh 2014/06/24 10:44 春色さん、

> 書類をリセットするということはGarageBandやpagesのデータも当然消えちゃいますよね(^_^;)?

保証はできませんが、消えないと思っています。
iCloudの書類は一時的にリセットされ消えますが、MacBookやiPhoneにはGarageBandやpagesのデータが残っているはずです。
このスクリプトが削除するのは、iPhoneのユーザー辞書に関するデータだけなので、GarageBandやpagesのデータは以前のまま残ります。
ユーザー辞書リセット後、GarageBandやpagesは以前と同じデータを保持して、iCloud同期を再開します。

仮にこのスクリプトの動作が期待と違った場合でも、事前のバックアップから復元することで、以前の状態に戻せるはずです。

春色春色 2014/06/24 13:23 お返事ありがとうございます(^_^;)
何故かiOS7.1.1の環境とMacOS10.9の私の環境ではどうしても戻らないみたいです
何か相性があるのでしょうか(^_^;)
一応Apple歴は長い方なので削除するファイルも置き換えるバックアップデータも間違いないはずなのですが
解決した方も多いようですので何か手順を間違えているのでしょうね
良いスプリクトの提供ご苦労様ですm(_ _)m

まあやまあや 2014/07/07 13:11 ~/Library/Mobile Documents/com~apple~TextInput/ が見つからなかったんですけど、
これはどういうことなんでしょうか??
初心者なものでごめんなさい><

zariganitoshzariganitosh 2014/07/07 13:41 0. まず、Apple社製のマシン環境であることを確認します。
MacBook、iMac、MacMiniなどのOSX10.9以降の環境であること。
iCloudを利用していること。

1. 以下のファイルパスをコピーして下さい。
~/Library/Mobile Documents/

2. その後、以下のキー操作を実行してみてください。
Finderを選択して、command-N、command-shift-G、command-V、return

3. 開かれたFinderウィンドウの中に com~apple~TextInput が見えないでしょうか?
com~apple~TextInputが見つかったら、ゴミ箱へ削除します。

com~apple~TextInputは、iCloudとOSXのユーザー辞書を同期するためのファイルです。(と思っています)

まあやまあや 2014/07/08 00:21 見つかりました!ありがとうございます!!

t0morit0mori 2014/07/29 14:20 こちらのスクリプト、大変助かっております。
TrackBack先の方が書かれていましたが、完全に辞書をクリアな状態から始めるなら、~/Library/Dictionaries/CoreDataUbiquitySupportと「ユーザ辞書」を削除した方が良いようです。最近、ことえりを使わなくなっていたもので盲点でしたが、こちらがローカルのことえりの辞書ファイルそのもののようです。

実は、こちらのスクリプトでリセットさせて頂くのも、最初に5月くらいに初めて使わせて頂いてから3回目でして、2回目まではどうも端末(iPhone5s、iPad mini、Mac Pro、MacBook Air、隠居MacBook Proの計5台がiCloud同期に参加)によって登録語の復活の仕方がまちまちになっており、原因がいまいち掴めておりませんでした。そこで、上記の辞書の削除を取り入れてやり直したところ、完全に同期を取る事が出来ました。3台のMacの辞書に齟齬があったのが、原因だったようです。

また、Mavericks 10.9.4のMacですと、上記辞書ファイルを削除した場合、再起動してからでないと、システム環境設定->キーボード->ユーザー辞書からドラッグ&ドロップで保存した、ユーザー辞書.plistの再読み込みが出来ませんでした。また、iOS7.1.2端末も、「書類とデータ」の同期を開始してから、改めて再起動が必要でした。
これも念のため、追加情報まで。

いやしかし、お陰様ですっきりしました。ありがとうございます!

zariganitoshzariganitosh 2014/08/04 10:44 なるほど、場合によっては、ことえりのユーザ辞書の削除まで必要となることがあるのですね。
 ~/Library/Dictionaries/CoreDataUbiquitySupport
 ~/Library/Dictionaries/ユーザ辞書
いろいろな追加情報ありがとうございます。
最終的にことえり辞書の同期が復活したようで、よかったです。

ふりたまふりたま 2014/09/25 18:39 突然のご訪問すみません。
以前MacBook Airでこのザリガニさんのスクリプトを使わせていただいていた時は問題なく
ユーザ辞書のリセットを行うことが出来、何度もユーザ辞書の同期まで辿り着くことが出来ていたのですが、
最近パソコンを買い替え、MacBook Proに切り替えて使い始めた途端にこのスクリプトを実行することが出来なくなってしまいました。

エラーの内容としては

「AppleScript エラー リスト内の項目がありません。」

と出てしまい。
以前は出ていた、バックアップ済みのiOS端末名のリストが出て来ない状態となっています。
おそらく考えられる原因としてはiTunesのバックアップをシンボリックリンクによって外付けのHDDに
送ってしまっているので、それによってスクリプトがバックアップのデータを参照出来なくなってしまっていることが
考えられるのですが、MacBook Airで実行していた際にも同じような方法や環境で問題なく使えていたので原因が分からず困っています。

もしかしたらシンボリックリンクを作成する際にパスやフォルダの関係が微妙に以前の環境と違ってしまっているのかもしれませんが、バックアップ自体は問題なくiTunesで取れているので不思議に思っています。
お手数おかけしますが、上記のようなエラーが出る際に考えられる原因としてはどのようなものがあるのか
よろしかったらご教授のほどお願いいたします。

zariganitoshzariganitosh 2014/09/26 08:32 ふりたま さん、
ユーザーホームのライブラリ >> Application Support >> MobileSync >> Backupの中に、
英数字を羅列したようなフォルダが見えているでしょうか?
例:9e4f64aca80f3bc6ad705ed034cf04f668a8ec71

このスクリプトはOSX標準の上記Buckupフォルダを対象に処理をする仕組みになっています。
「AppleScript エラー リスト内の項目がありません。」と出る場合は、
おそらくOSX標準の上記Buckupフォルダの中身が空っぽであることが考えられます。

以前は正常に動いていたと言うことは、OSX標準のBuckupフォルダにバックアップ(もしかしたら最新ではないかもしれませんが)が存在していたと思われます。
新しい環境にして、最初からシンボリックで外付けHDDを指定してしまうと、OSX標準のBuckupフォルダは空っぽになります。

一時的にiTunesの環境をOSX標準に戻せば、正常に動くかもしれません。
あるいは、reset_iOS7_UserDictionary.app/Contents/Resources/current_backup_list.shが対象としているバックアップのパスを、外付けHDDのバックアップパスに書き換えれば、正常に動くかもしれません。
書き換え箇所は以下の部分です。
2行目backup_folders=`find "$HOME/Library/Application Support/MobileSync/Backup" -name Info.plist -exec dirname {} \;`
上記コードの"$HOME/Library/Application Support/MobileSync/Backup"の部分を外付けHDDのバックアップパスにします。

MichiyoMichiyo 2014/11/24 15:44 Yosemite 10.10.1 と OS 8.1.1 でも同じようにできますか?

zariganitoshzariganitosh 2014/11/25 08:10 こちらは未だ、Mavericks 10.9.5とiOS7.1.2の環境なので、試すことができません。
後日、アップグレードした時に確認してみます。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/zariganitosh/20140208/reset_ios7_user_dictionary
リンク元