必ず見つかるSpotlightにしておく

OSX 10.8 Mountain Lionでは、Spotlightで見つけられないキーワードがあるらしい。具体的には、「じじい」とか「シンガポール」とか「ノーライフキング」など。

OSX 10.8.5 Mountain Lion の Spotlight

本当なのか?実験してみた。

  • ターミナルで、以下のファイルを作成してみた。(コピー&ペーストで実行)
cd ~/Documents
> じじい.txt
> シンガポール.txt
> ノーライフキング.txt
  • テキストエディット.appで、以下の新規テキスト書類(ファイル名:spotlight_test.txt)を作ってみた。
  • 同様にメモ.appで、以下の新規メモを作ってみた。
スポットライトのテスト

じじい
シンガポール
ノーライフキング
  • Spotlightで検索してみると...
現実の結果 期待する結果
NG: じじい.txt
NG: spotlight_test.txt
NG: スポットライトのテスト
NG: シンガポール.txt
NG: spotlight_test.txt
NG: スポットライトのテスト
OK: ノーライフキング.txt
NG: spotlight_test.txt
NG: スポットライトのテスト

や、や、や!

  • ことごとく検索をミスっている。唯一「ノーライフキング.txt」というファイル名のみはヒットしているが、それ以外はすべてノーマッチ。
  • 「じじい」と「ノーライフキング」については、Webやメールの履歴がヒットしているが、「シンガポール」では完全なノーマッチ。

これは由々しき問題である!

  • 本来ヒットするはずの項目が、検索結果に上がって来ない...。
  • これほど明白な検索漏れがあると、Spotlightの信頼性が、がた落ちである。
  • SpotlightとTimeMachineの存在こそが、NeXTに始まるOSXが目指したOSの完成形だったはずなのに。
  • 検索漏れのある検索エンジンなんて、計算間違いするCPUみたいなもの。

何とかしなければならない!

OSX 10.6.8 Snow Leopard の Spotlight

  • ちなみに、Spotlightの名誉のために、OSX 10.6.8 Snow Leopardで全く同じ条件で検索してみると...
現実の結果 期待する結果
OK: じじい.txt
OK: spotlight_test.txt
OK: スポットライトのテスト
OK: シンガポール.txt
OK: spotlight_test.txt
OK: スポットライトのテスト
OK: ノーライフキング.txt
OK: spotlight_test.txt
OK: スポットライトのテスト
  • すべての検索で期待どおりにヒットした。本来のSpotlight性能は、こうゆうものだと思っている。


OSX 10.8 Mountain LionのSpotlightで、いったい何が起こっているのだろう?

消極的な回避策

以下の方法を覚えておけば、ある程度はまともに検索できるのだけど、あまり良い方法とは言えない...。
そもそもSpotlightとは、こんな小手先の検索技なんて知らなくても、思い浮かんだキーワードで素早く検索できることが売りのはず。また検索するなら、短いキーワードで広範囲にヒットして、そこから条件を追加して絞り込む方が使いやすい。少なくとも、Snow LeopardまでのSpotlightはそのような仕様になっていたはず。

  • ファイル名を検索する時は、「ファイル名:」あるいは「filename:」を付加して検索すると、ちゃんと見つかる。
ファイル名:シンガポール
filename:シンガポール
  • Finderのファイル検索でも、「ファイル名:」あるいは「filename:」を付加して検索すると、ちゃんと見つかる。
  • あるいは、検索条件で「ファイル名」を選択して、その右側に「検索テキスト」を入力すると、ちゃんと見つかる。


  • うっかり「ファイル名 名前が一致:」を選択してしまうと、何もヒットしない...。(誘導されやすいので気をつける)

  • Finderのファイル検索と、control-option-スペースのSpotlightのウィンドウ検索は、同じではない。
    • Finderのファイル検索では、OSやアプリケーションが管理するファイルを検索対象にしない。
      • ライブラリフォルダの中や、iTunesの音楽ファイル、iPhotoの写真ファイル、メールのファイルなど、検索対象にしない。
    • 一方、Spotlightのウィンドウ検索では、すべてのファイルが検索対象になる。
  • この違いを忘れて検索してしまうと、本来見つかるものも、見つからなくなってしまう...。

ファイル名の検索については上記対応でどうにかなるが、テキストファイルの内容検索については無理...。さらに探求する必要がある。

検索インデックスの検証

  • Snow LeopardとMountain Lionで、検索インデックスに違いがあるのか?検証してみた。
  • 空の外付けハードディスクを、まずSnow Leopardに接続する。
  • 上記実験で使った「spotlight_test.txt」をコピーした。
  • Snow LeopardのSpotlightで「スポットライトのテスト」を検索してみると、ちゃんと「spotlight_test.txt」がヒットした。
  • 次に、同じ外付けハードディスクをMountain Lionに接続するのだけど、
  • その前に以下のコマンドを実行して、検索インデックスの作成を一時停止しておく。
$ mdutil -i off
  • 外付けハードディスクを接続したら、Spotlightで「スポットライトのテスト」を検索してみる。何もヒットしない...。
  • 検索インデックスの作成をチェックするため、以下のコマンドを実行中にしておく。
$ sudo opensnoop -n mdworker
Password:
  UID    PID COMM          FD PATH                 
  • この状態で、検索インデックスの作成を再開する。
$ mdutil -i on
  • すると、先ほどの監視コマンドの出力に、テストファイルが見えた!
$ sudo opensnoop -n mdworker
Password:
  UID    PID COMM          FD PATH                 

...中略...

   89  15504 mdworker       4 /Volumes/名称未設定 10/spotlight_test.txt 
   89  15504 mdworker       5 /Volumes/名称未設定 10/ノーライフキング.txt 
   89  15504 mdworker       5 /Volumes/名称未設定 10/シンガポール.txt 
   89  15504 mdworker       5 /Volumes/名称未設定 10/じじい.txt 
   89  15504 mdworker      -1 /Volumes/.DS_Store   
   89  15504 mdworker       4 /Volumes/名称未設定 10/.DS_Store 
  • その後再び、Spotlightで「スポットライトのテスト」を検索してみる。今度は「spotlight_test.txt」がヒットした!


つまり、Mountain Lionは、Snow Leopardの検索インデックスをそのまま使っていない!

  • 上書きしてるか、別の検索インデックスを作っているはずである。
  • Spotlightの検索インデックスは、各ボリュームのルート直下の .Spotlight-V100 に格納されている。
  • そう思って、ls コマンドで探索してみると、二つの検索インデックス(と思われるファイル構造)を発見した!
$ ls -al /Volumes/名称未設定\ 10/.Spotlight-V100/Store-V1/Stores/D47DAAC7-5E5F-458E-B187-00E6348D1DB9
total 1560
drwx------  61 bebe  staff  -  2074 10 23 15:44 ./
drwx------   3 bebe  staff  -   102 10 21 10:03 ../
-rw-------   1 bebe  staff  - 53248 10 23 15:44 .store.db
-rw-------   1 bebe  staff  - 68484 10 21 10:03 0.indexArrays
-rw-------   1 bebe  staff  -     8 10 21 10:03 0.indexCompactDirectory
-rw-------   1 bebe  staff  -  2056 10 21 10:03 0.indexDirectory
-rw-------   1 bebe  staff  -  2731 10 21 10:14 0.indexGroups
-rw-------   1 bebe  staff  -  4096 10 21 10:14 0.indexHead
-rw-------   1 bebe  staff  - 32768 10 21 10:03 0.indexIds
-rw-------   1 bebe  staff  -    20 10 21 10:03 0.indexPositions
-rw-------   1 bebe  staff  -   752 10 21 10:03 0.indexPostings
-rw-------   1 bebe  staff  -     4 10 21 10:14 0.shadowIndexGroups
-rw-------   1 bebe  staff  -  4096 10 21 10:14 0.shadowIndexHead
-rw-------   1 bebe  staff  -    28 10 23 15:44 indexState
-rw-------   1 bebe  staff  -     0 10 21 10:03 journalExclusion
-rw-------   1 bebe  staff  -     0 10 23 15:42 journalLive
-rw-------   1 bebe  staff  -     0 10 23 15:42 journalSync
-rw-------   1 bebe  staff  - 68600 10 23 15:42 live.0.indexArrays
-rw-------   1 bebe  staff  -     8 10 23 15:42 live.0.indexCompactDirectory
-rw-------   1 bebe  staff  -  2056 10 23 15:42 live.0.indexDirectory
-rw-------   1 bebe  staff  -     6 10 23 15:42 live.0.indexGroups
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.0.indexHead
-rw-------   1 bebe  staff  -    64 10 23 15:42 live.0.indexIds
-rw-------   1 bebe  staff  -    18 10 23 15:42 live.0.indexPositions
-rw-------   1 bebe  staff  -   813 10 23 15:42 live.0.indexPostings
-rw-------   1 bebe  staff  -    10 10 23 15:42 live.0.indexUpdates
-rw-------   1 bebe  staff  -     6 10 23 15:42 live.0.shadowIndexGroups
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.0.shadowIndexHead
-rw-------   1 bebe  staff  - 66588 10 23 15:42 live.1.indexArrays
-rw-------   1 bebe  staff  -     8 10 23 15:42 live.1.indexCompactDirectory
-rw-------   1 bebe  staff  -  2056 10 23 15:42 live.1.indexDirectory
-rw-------   1 bebe  staff  -  2731 10 23 15:42 live.1.indexGroups
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.1.indexHead
-rw-------   1 bebe  staff  - 32768 10 23 15:42 live.1.indexIds
-rw-------   1 bebe  staff  -     4 10 23 15:42 live.1.indexPositions
-rw-------   1 bebe  staff  -   148 10 23 15:42 live.1.indexPostings
-rw-------   1 bebe  staff  -     2 10 23 15:42 live.1.shadowIndexGroups
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.1.shadowIndexHead
-rw-------   1 bebe  staff  - 65536 10 23 15:42 live.2.indexArrays
-rw-------   1 bebe  staff  -  1024 10 23 15:42 live.2.indexCompactDirectory
-rw-------   1 bebe  staff  -  8224 10 23 15:42 live.2.indexDirectory
-rw-------   1 bebe  staff  -  2731 10 23 15:42 live.2.indexGroups
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.2.indexHead
-rw-------   1 bebe  staff  - 32768 10 23 15:42 live.2.indexIds
-rw-------   1 bebe  staff  -  8192 10 23 15:42 live.2.indexPositionTable
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.2.indexPositions
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.2.indexPostings
-rw-------   1 bebe  staff  -  8192 10 23 15:42 live.2.indexTermIds
-rw-------   1 bebe  staff  -     0 10 23 15:42 live.2.indexUpdates
-rw-------   1 bebe  staff  - 65536 10 23 15:42 live.2.shadowIndexArrays
-rw-------   1 bebe  staff  -     8 10 23 15:42 live.2.shadowIndexCompactDirectory
-rw-------   1 bebe  staff  -  2056 10 23 15:42 live.2.shadowIndexDirectory
-rw-------   1 bebe  staff  -     1 10 23 15:42 live.2.shadowIndexGroups
-rw-------   1 bebe  staff  -  4096 10 23 15:42 live.2.shadowIndexHead
-rw-------   1 bebe  staff  -     0 10 23 15:42 live.2.shadowIndexPositionTable
-rw-------   1 bebe  staff  -     0 10 23 15:42 live.2.shadowIndexTermIds
-rw-------   1 bebe  staff  - 65620 10 23 15:44 permStore
-rw-r--r--   1 bebe  staff  -     4 10 23 15:44 shutdown_time
-rw-------   1 bebe  staff  - 53248 10 23 15:42 store.db
-rw-------   1 bebe  staff  -     6 10 23 15:44 store.updates
-rw-------   1 bebe  staff  -     0 10 21 10:03 tmp.SnowLeopard
  • こちらがMountain Lionの検索インデックス。
$ ls -al /Volumes/名称未設定\ 10/.Spotlight-V100/Store-V2/843F7ECA-ADE0-4063-ADF9-09412B12BDDE/
total 1624
drwx------  58 bebe  staff  -   1972 10 23 15:45 ./
drwx------   3 bebe  staff  -    102 10 23 15:36 ../
-rw-------   1 bebe  staff  - 118784 10 23 15:42 .store.db
-rw-------   1 bebe  staff  -  65536 10 23 15:45 0.directoryStoreFile
-rw-------   1 bebe  staff  -   1088 10 23 15:36 0.directoryStoreFile.shadow
-rw-------   1 bebe  staff  -   1792 10 23 15:36 0.indexArrays
-rw-------   1 bebe  staff  -      8 10 23 15:36 0.indexCompactDirectory
-rw-------   1 bebe  staff  -   2056 10 23 15:36 0.indexDirectory
-rw-------   1 bebe  staff  -   3277 10 23 15:36 0.indexGroups
-rw-------   1 bebe  staff  -   4096 10 23 15:36 0.indexHead
-rw-------   1 bebe  staff  -     48 10 23 15:36 0.indexIds
-rw-------   1 bebe  staff  -    976 10 23 15:36 0.indexPositionTable
-rw-------   1 bebe  staff  -   4096 10 23 15:36 0.indexPositions
-rw-------   1 bebe  staff  -   4096 10 23 15:36 0.indexPostings
-rw-------   1 bebe  staff  -    976 10 23 15:36 0.indexTermIds
-rw-------   1 bebe  staff  -     14 10 23 15:36 0.indexUpdates
-rw-------   1 bebe  staff  -      5 10 23 15:36 0.shadowIndexGroups
-rw-------   1 bebe  staff  -   4096 10 23 15:36 0.shadowIndexHead
-rw-------   1 bebe  staff  -      0 10 23 15:36 Lion.created
-rw-------   1 bebe  staff  -      0 10 23 15:45 Lion.modified
-rw-------   1 bebe  staff  -     28 10 23 15:36 indexState
-rw-------   1 bebe  staff  -      0 10 23 15:45 journalAttr.2
-rw-------   1 bebe  staff  -      0 10 23 15:36 journalExclusion
drwx------   2 bebe  staff  -     68 10 23 15:36 journals.live/
drwx------   2 bebe  staff  -     68 10 23 15:45 journals.repair/
drwx------   3 bebe  staff  -    102 10 23 15:36 journals.scan/
-rw-------   1 bebe  staff  -  65536 10 23 15:45 live.0.directoryStoreFile
-rw-------   1 bebe  staff  -   1088 10 23 15:36 live.0.directoryStoreFile.shadow
-rw-------   1 bebe  staff  -  65536 10 23 15:45 live.0.indexArrays
-rw-------   1 bebe  staff  -   1024 10 23 15:45 live.0.indexCompactDirectory
-rw-------   1 bebe  staff  -   8224 10 23 15:45 live.0.indexDirectory
-rw-------   1 bebe  staff  -   3277 10 23 15:45 live.0.indexGroups
-rw-------   1 bebe  staff  -   4096 10 23 15:36 live.0.indexHead
-rw-------   1 bebe  staff  -  32768 10 23 15:45 live.0.indexIds
-rw-------   1 bebe  staff  -   8192 10 23 15:45 live.0.indexPositionTable
-rw-------   1 bebe  staff  -   4096 10 23 15:45 live.0.indexPositions
-rw-------   1 bebe  staff  -   4096 10 23 15:45 live.0.indexPostings
-rw-------   1 bebe  staff  -   8192 10 23 15:45 live.0.indexTermIds
-rw-------   1 bebe  staff  -      0 10 23 15:36 live.0.indexUpdates
-rw-------   1 bebe  staff  -  65536 10 23 15:36 live.0.shadowIndexArrays
-rw-------   1 bebe  staff  -      8 10 23 15:36 live.0.shadowIndexCompactDirectory
-rw-------   1 bebe  staff  -   2056 10 23 15:36 live.0.shadowIndexDirectory
-rw-------   1 bebe  staff  -      1 10 23 15:36 live.0.shadowIndexGroups
-rw-------   1 bebe  staff  -   4096 10 23 15:36 live.0.shadowIndexHead
-rw-------   1 bebe  staff  -      0 10 23 15:36 live.0.shadowIndexPositionTable
-rw-------   1 bebe  staff  -      0 10 23 15:36 live.0.shadowIndexTermIds
-rw-------   1 bebe  staff  -  65621 10 23 15:45 permStore
-rw-------   1 bebe  staff  -  65536 10 23 15:45 reverseDirectoryStore
-rw-------   1 bebe  staff  -   3136 10 23 15:36 reverseDirectoryStore.shadow
-rw-------   1 bebe  staff  -      2 10 23 15:42 reverseStore.updates
-rw-r--r--   1 bebe  staff  -      4 10 23 15:42 shutdown_time
-rw-------   1 bebe  staff  - 118784 10 23 15:36 store.db
-rw-------   1 bebe  staff  -      8 10 23 15:42 store.updates
-rw-r--r--   1 bebe  staff  -      4 10 23 15:36 store_generation
-rw-------   1 bebe  staff  -      0 10 23 15:36 tmp.Lion
-rw-------   1 bebe  staff  -      0 10 23 15:36 tmp.SnowLeopard
-rw-------   1 bebe  staff  -   6608 10 23 15:36 tmp.spotlight.loc
-rw-------   1 bebe  staff  -   4096 10 23 15:42 tmp.spotlight.state
  • 中身はブラックボックスだけど、修正日時のタイミングから、二つの検索インデックスが使い分けられていることは確かだと思う。
    • Snow Leopardは、/.Spotlight-V100/Store-V1/...
    • Mountain Lionは、/.Spotlight-V100/Store-V2/...

Mountain Lionでは、検索インデックスのバージョンがV2となり、構造が変更されているようだ。

検索クエリ(検索条件)の検証

  • OSXには、Spotlightに対応するコマンドとして、mdfindが用意されている。
  • mdfindのマニュアルには、コントロール-スペースのSpotlightに対応する検索条件が解説されている。
$ man mdfind
...中略...
(* = search* cdw || kMDItemTextContent = search* cdw)
  • 例えば「シンガポール」を検索する場合、以下のmdfindコマンドを実行すると、Spotlightと同等になるのだ。
$ mdfind "* = 'シンガポール*'cdw || kMDItemTextContent = 'シンガポール*'cdw"
  • 上記コマンドを実行しても、Spotlightと同じく、さっぱり何もヒットしない...。
  • ところが、キーワードに続く cdw の d を外してみると、見事、かつてのSpotlightのようにヒットしてしまった!
    • -onlyinオプションを指定して、検索範囲を~/Documentsに限定している。
$ mdfind "* = 'シンガポール*'cw || kMDItemTextContent = 'シンガポール*'"cw -onlyin ~/Documents
/Users/bebe/Documents/シンガポール.txt
/Users/bebe/Documents/spotlight_test.txt
...中略...
cdwの意味
  • そもそも、cdwには何の意味があるのか?
c 大文字・小文字を区別しない
d アクセント記号のあり・なしを区別しない
w 小文字から大文字へ変化する部分も単語の区切りと見なす

参考ページ:(感謝です!)

  • なるほど、cdwは主にアルファベットの検索条件で役立ちそうな指定である。
  • しかし、なぜd(=アクセント記号のあり・なしを区別しない)指定が日本語の検索に影響してしまうのだろう?
  • 少なくとも、Snow Leopardまでは影響していない。
$ mdfind "* == 'シンガポール*'cdw || kMDItemTextContent == 'シンガポール*'cdw" -onlyin ~/Documents
/Users/bebe/Documents/spotlight_test.txt
/Users/bebe/Documents/シンガポール.txt
...中略...
メタデータの種類
$ mdfind "* == 'シンガポール*'cdw || kMDItemTextContent == 'シンガポール*'cdw" -onlyin ~/Documents
  • 先ほどの検索クエリで、「kMDItemTextContent == 'シンガポール*'cdw」の部分は、メタデータの種類を指定している。
  • kMDItemTextContentは、テキストファイルの内容である。
  • テキストの内容に「シンガポール」で始まる単語が含まれていたら、ヒットするのだ。
$ mdfind "* == 'シンガポール*'cw" -onlyin ~/Documents
/Users/bebe/Documents/シンガポール.txt
  • 「kMDItemTextContent」も含めると、ちゃんとテキスト内容の「シンガポール」もヒットした。
$ mdfind "* == 'シンガポール*'cw || kMDItemTextContent == 'シンガポール*'cw" -onlyin ~/Documents
/Users/bebe/Documents/spotlight_test.txt
/Users/bebe/Documents/シンガポール.txt

$ cat /Users/bebe/Documents/spotlight_test.txt
スポットライトのテスト

じじい
シンガポール
ノーライフキング


参考ページ:(感謝です!)

Spotlightの使い方

  • 以上の検証から、検索インデックスのバージョンが変わったMountain Lionでも、検索クエリの「cdw」の部分を「cw」にすれば、かなり満足できる結果になりそうである。
  • しかし、Spotlightで検索クエリを指定することなんて、果たしてできるのだろうか?
  • Spotlightの基本的な使い方として「メタデータ名称:値」で検索条件を指定できることは知っている。
  • メタデータ名称には、kMDItem...で始まる正式な名称以外に、短縮名称も利用できる。日本語名称も用意されている。
入力語句 意味
Time Capsule "Time"と"Capsule"の両方を含むファイルにマッチ。
"Time Capsule" "Time Capsule"という一連の語句を含むファイルにマッチ。
Time -Capsule "Time"は含むが、"Capsule"を含まないファイルにマッチ。
Time OR Capsule "Time"か"Capsule"のどちらか一方を含むファイルにマッチ。
NOT(Time Capsule) "Time"も"Capsule"も含まないファイルにマッチ。
ファイル名:シンガポール ファイル名に"シンガポール"を含むファイルにマッチ。
ファイル名:シンガポール 種類:text ファイル名に"シンガポール"を含む、テキストファイルにマッチ。
ファイル名:シンガポール 日付:2013/10/23 ファイル名に"シンガポール"を含む、2013/10/23のファイルにマッチ。
ファイル名:シンガポール 日付:>2013/10/23 ファイル名に"シンガポール"を含む、2013/10/23以降のファイルにマッチ。
ファイル名:シンガポール 日付:<2013/10/23 ファイル名に"シンガポール"を含む、2013/10/23以前のファイルにマッチ。
kMDItemPixelHeight:>3000
height:>3000
高さ:>3000
画像の高さが3000ピクセルより大きいファイルにマッチ。
  • 上記の方法でメタデータを指定することはできる。ところが、生成される検索クエリの詳細な指定(cdwの部分)まではできないのだ...。
参考ページ:(感謝です!)

独自の検索クエリーを指定する

  • Spotlightの入力部分で直接的に指定できないからと言って、諦めるのはまだ早い。
  • 試行錯誤しているうちに、独自の検索クエリを自由に指定する方法を思いついた。
  • 相変わらず何もヒットしないのだけど、気にしないで「Finderにすべてを表示します」を選択する。
  • すると、何もヒットしてないウィンドウが表示されるので、検索条件を保存する。
  • 「~/ライブラリ/保存済みの検索条件」を開くと、さっき保存した検索条件が見つかる。
  • 選択して、command-Iで情報を見てみる。
  • すると、クエリー:の項目にマニュアルどおりの条件が記載されている。
  • このクエリーを修正できれば、もっと自由なSpotlight検索ができるはず。
  • この検索条件をテキストエディットで開いてみる。
  • すると、その中身はplistであり、以下の内容が確認できた。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CompatibleVersion</key>
	<integer>1</integer>
	<key>RawQuery</key>
	<string>(true) &amp;&amp; (((* = "シンガポール*"cdw || kMDItemTextContent = "シンガポール*"cdw)))</string>
	<key>RawQueryDict</key>
	<dict>
		<key>FinderFilesOnly</key>
		<false/>
		<key>RawQuery</key>
		<string>(true) &amp;&amp; (((* = "シンガポール*"cdw || kMDItemTextContent = "シンガポール*"cdw)))</string>
		<key>SearchScopes</key>
		<array>
			<string>kMDQueryScopeComputer</string>
		</array>
		<key>UserFilesOnly</key>
		<true/>
	</dict>
...中略...
  • 先ほど見た検索クエリーも確認できる!
  • すかさずcdwの部分をすべてcwに修正。
  • 修正したら、忘れずに保存しておく。
  • そして、Finderから修正後の検索条件をダブルクリックして開いてみると...
見事!「シンガポール」を含むあらゆるファイルがヒットしている!

ノーライフキング」問題

  • 喜んだのもつかのま、「ノーライフキング」については、この検索クエリーの修正でもテキストファイルの内容がヒットしないことに気付いた。
    • ファイル名はヒットした。
  • なぜ「ノーライフキング」はヒットしないのか?
  • ノーライフキング」は小説(映画にもなった)のタイトルであるが、おそらく、この文字の並びに秘密がある。
  • 固有名詞なのだけど、「ノーライフキング」を知らない人が読んだら、「ノー」と「ライフ」と「キング」に分割して、必至に意味を探ろうと思うはず。
  • 英語などは単語区切りが明白だが、日本語の場合は先頭から文字を読んで、意味のある語句を認識しながら読み進める。
  • おそらく、Spotlightが検索インデックスを作成する時も同様に、意味のある語句で区切って、検索インデックスに登録するはずである。
  • その時、「ノーライフキング」は「ノー」と「ライフ」と「キング」に分かれて登録されているのかもしれない...。
  • 試しに、テキストファイルの内容を "ノーライフキング" のようにクォートしてみると、ちゃんとヒットした。
  • 少なくともSnow Leopard時代のSpotlightではちゃんとヒットしていたのに、Mountain Lionの検索インデックスはクォートしないとヒットしない、残念な仕様である。
  • 検索インデックスが違ってしまっているのだから、「ノーライフキング」に対しては、なす術がないのかと諦めかけていたが、一つ閃いた。
  • 検索クエリーを「ノー*ライフ*キング*」のように指定してみた。
$ mdfind 'kMDItemTextContent = "ノー*ライフ*キング*"' -onlyin ~/Documents
/Users/zari/Documents/spotlight_test.txt
すると、見事にヒット!
  • 但し、"ノー*ライフ*キング*"'の場合は、クォートされた「"ノーライフキング"」がヒットしない...。
  • よって、より一般的に利用するなら、"ノーライフキング*"'と"ノー*ライフ*キング*"'の両方を指定した方が良さそう。
  • ちなみに、"ノー*ライフ*キング*"'の検索クエリーは、テキストファイル中に「ノー」と「ライフ」と「キング」が順に出現すれば、ヒットしてしまう。
  • 例えば、「ノーと言えない日本人。ライフスタイルは欧米化している。キングコングが好き。」なんて内容でもヒットすることになる。
    • と、予想したのだけど、試してみたらヒットしなかった...。
    • 結果としては嬉しいのだけど、クエリーがマッチする挙動は読み切れてない。
  • しかし、「ノーライフキング」が全くヒットしないよりも、余分なファイルを含んでもヒットした方が嬉しいはず。(と思っている)

スクリプトにまとめる

  • 以上の技を駆使すれば、Mountain LionのSpotlightでも、かなり満足できる検索結果を得られそうだ。
  • 但し、毎回手作業で検索クエリーを修正するなんて言うのは、面倒くさすぎる...。
  • ここはもう、いつものAppleScriptでショートカット一発、素早く満足できる検索結果を手に入れたい。
やってみた。
  • メニューバーのSpotlightで検索して、出力される検索結果に満足できない時に、ショートカットから素早く漏れのない検索結果を出力するのだ。
Spotlight.scpt
  • ~/Library/Scripts/Spotlight.scpt として保存した。
  • 雛形のSpotlight.savedSearch.plistに検索語句を代入して、保存済みの検索条件を作成している。
  • このスクリプトは、メニューバーのSpotlightを利用中に、ショートカットで呼び出す必要がある。
  • よって、Quicksilverなどで、事前にショートカットを割り当てておく必要がある。
    • 自分の場合、control-option-スペースを割り当てた。
    • OSXデフォルトのcontrol-option-スペースは無効にした。
 if spotlight_running() then
   expanded_spotlight()
 else
   normal_spotlight()
 end if
 
 
 
 on expanded_spotlight()
   tell application "System Events"
     delay 0.3
     keystroke "a" using {command down}
     delay 0.3
     keystroke "c" using {command down}
     delay 0.3
     keystroke space using {control down}
   end tell
   
   set key_word to the clipboard as text
   set ext_word to join(key_word's text items, "*")
   
   set savedSearch to (path to temporary items)'s POSIX path & "Spotlight.savedSearch"
   set savedSearch_plist to (path to scripts folder)'s POSIX path & "Spotlight.savedSearch.plist"
   
   do shell script "rm -f " & savedSearch
   do shell script "cat " & savedSearch_plist & " | sed s/__KEYWORD__/" & key_word & "/g | sed s/__CONTENT__/" & ext_word & "/g >> " & savedSearch
   do shell script "open " & savedSearch
   tell application "Finder" to activate
   delay 1
   zoom_finder_window1()
 end expanded_spotlight
 
 on normal_spotlight()
   tell application "System Events"
     keystroke space using {control down}
   end tell
 end normal_spotlight 
 
 
 
 --Spotlightが表示中かどうか
 on spotlight_running()
   tell application "System Events"
     tell process "SystemUIServer"
       menu bar 2's menu bar item 1's selected
     end tell
   end tell
 end spotlight_running
 
 --Finderのアクティブなウィンドウをズームする
 on zoom_finder_window1()
   tell application "System Events"
     tell process "Finder"
       click window 1's button 2
     end tell
   end tell
 end zoom_finder_window1
 
 on join(src_list, delimiter)
   set last_delimiter to AppleScript's text item delimiters
   set AppleScript's text item delimiters to delimiter
   set res to src_list as text
   set AppleScript's text item delimiters to last_delimiter
   res
 end join
  • コーディングするにあたり、「ノーライフキング」から「ノー*ライフ*キング*」をどうやって導き出すか?という問題にぶつかった。
  • 人間なら、自分の知識から簡単に「ノー*ライフ*キング*」を導ける。
  • しかし、コンピュータに同じことさせるのは、非常に難しい。
  • ちゃんとやるなら、MeCabなどの形態素解析エンジンを導入して、分かち書きを求める必要がある。
  • しかし、そこまでやるのは面倒だし、インストールの手間もかかる...。
  • 今回の目的は、正しい単語区切りをすることではなく、検索漏れのないSpotlightにすることである。
  • しばし考え、それはちょっとやり過ぎ感もあると思いながら、決断した。
もう単純に「ノ*ー*ラ*イ*フ*キ*ン*グ*」にしてしまえば、いいやと。
  • よって、このスクリプトは、テキストファイル中に検索語の一文字ずつが、順に出現する内容でもヒットしてしまう...。
  • 短い単語だと、この仕様によって、膨大な検索結果がヒットしてしまう。
    • どうして検索語が含まれていないのにヒットするのか悩んだら、おそらくこの原因。
  • しかし、長い単語なら、ほとんど気にならないレベルで正しい検索結果となりそう。
Spotlight.savedSearch.plist
  • このxmlファイルは、保存済みの検索条件の雛形となる。
  • ~/Library/Scripts/Spotlight.savedSearch.plist として保存しておく。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CompatibleVersion</key>
	<integer>1</integer>
	<key>RawQuery</key>
	<!--
  <string>(true) &amp;&amp; (((* = "__KEYWORD__*"cdw || kMDItemTextContent = "__KEYWORD__*"cdw || kMDItemTextContent = "__CONTENT__*"cdw)))</string>
  -->
	<string>(true) &amp;&amp; (((* = "__KEYWORD__*"cw || kMDItemTextContent = "__KEYWORD__*"cw || kMDItemTextContent = "__CONTENT__*"cw)))</string>
	<key>RawQueryDict</key>
	<dict>
		<key>FinderFilesOnly</key>
		<false/>
		<key>RawQuery</key>
  	<!--
    <string>(true) &amp;&amp; (((* = "__KEYWORD__*"cdw || kMDItemTextContent = "__KEYWORD__*"cdw || kMDItemTextContent = "__CONTENT__*"cdw)))</string>
    -->
		<string>(true) &amp;&amp; (((* = "__KEYWORD__*"cw || kMDItemTextContent = "__KEYWORD__*"cw || kMDItemTextContent = "__CONTENT__*"cw)))</string>
		<key>SearchScopes</key>
		<array>
			<string>kMDQueryScopeComputer</string>
		</array>
		<key>UserFilesOnly</key>
		<true/>
	</dict>
	<key>SearchCriteria</key>
	<dict>
		<key>FXCriteriaSlices</key>
		<array>
			<dict>
				<key>criteria</key>
				<array>
					<string>com_apple_UserSearchStringAttribute</string>
					<integer>104</integer>
				</array>
				<key>displayValues</key>
				<array>
					<string>Items matching text</string>
					<string>__KEYWORD__</string>
				</array>
				<key>rowType</key>
				<integer>0</integer>
				<key>subrows</key>
				<array/>
			</dict>
		</array>
		<key>FXScope</key>
		<integer>1396925814</integer>
		<key>FXScopeArrayOfPaths</key>
		<array>
			<string>kMDQueryScopeComputer</string>
		</array>
	</dict>
	<key>SuggestedAttributes</key>
	<array/>
  <!--
    ==================== ここから ViewSettings ここから ====================
  -->
	<key>ViewSettings</key>
	<dict>
		<key>ExtendedListViewSettings</key>
		<dict>
			<key>calculateAllSizes</key>
			<false/>
			<key>columns</key>
			<array>
				<dict>
					<key>ascending</key>
					<true/>
					<key>identifier</key>
					<string>name</string>
					<key>visible</key>
					<true/>
					<key>width</key>
					<integer>521</integer><!-- "名前"の列幅-->
				</dict>
				<dict>
					<key>ascending</key>
					<false/>
					<key>identifier</key>
					<string>dateModified</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>181</integer>
				</dict>
				<dict>
					<key>ascending</key>
					<false/>
					<key>identifier</key>
					<string>dateCreated</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>181</integer>
				</dict>
				<dict>
					<key>ascending</key>
					<false/>
					<key>identifier</key>
					<string>size</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>97</integer>
				</dict>
				<dict>
					<key>ascending</key>
					<true/>
					<key>identifier</key>
					<string>kind</string>
					<key>visible</key>
					<true/>
					<key>width</key>
					<integer>156</integer><!-- "種類"の列幅-->
				</dict>
				<dict>
					<key>ascending</key>
					<true/>
					<key>identifier</key>
					<string>label</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>100</integer>
				</dict>
				<dict>
					<key>ascending</key>
					<true/>
					<key>identifier</key>
					<string>version</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>75</integer>
				</dict>
				<dict>
					<key>ascending</key>
					<true/>
					<key>identifier</key>
					<string>comments</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>300</integer>
				</dict>
				<dict>
					<key>ascending</key>
					<false/>
					<key>identifier</key>
					<string>dateLastOpened</string>
					<key>visible</key>
					<true/>
					<key>width</key>
					<integer>141</integer><!-- "最後に開いた日"の列幅-->
				</dict>
				<dict>
					<key>ascending</key>
					<false/>
					<key>identifier</key>
					<string>dateAdded</string>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>181</integer>
				</dict>
			</array>
			<key>iconSize</key>
			<real>16</real>
			<key>showIconPreview</key>
			<true/>
			<key>sortColumn</key>
			<string>dateLastOpened</string>
			<key>textSize</key>
			<real>12</real>
			<key>useRelativeDates</key>
			<true/>
			<key>viewOptionsVersion</key>
			<integer>1</integer>
		</dict>
		<key>ListViewSettings</key>
		<dict>
			<key>calculateAllSizes</key>
			<false/>
			<key>columns</key>
			<dict>
				<key>comments</key>
				<dict>
					<key>ascending</key>
					<true/>
					<key>index</key>
					<integer>7</integer>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>300</integer>
				</dict>
				<key>dateCreated</key>
				<dict>
					<key>ascending</key>
					<false/>
					<key>index</key>
					<integer>2</integer>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>181</integer>
				</dict>
				<key>dateLastOpened</key>
				<dict>
					<key>ascending</key>
					<false/>
					<key>index</key>
					<integer>8</integer>
					<key>visible</key>
					<true/>
					<key>width</key>
					<integer>141</integer><!-- "最後に開いた日"の列幅-->
				</dict>
				<key>dateModified</key>
				<dict>
					<key>ascending</key>
					<false/>
					<key>index</key>
					<integer>1</integer>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>181</integer>
				</dict>
				<key>kind</key>
				<dict>
					<key>ascending</key>
					<true/>
					<key>index</key>
					<integer>4</integer>
					<key>visible</key>
					<true/>
					<key>width</key>
					<integer>156</integer><!-- "種類"の列幅-->
				</dict>
				<key>label</key>
				<dict>
					<key>ascending</key>
					<true/>
					<key>index</key>
					<integer>5</integer>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>100</integer>
				</dict>
				<key>name</key>
				<dict>
					<key>ascending</key>
					<true/>
					<key>index</key>
					<integer>0</integer>
					<key>visible</key>
					<true/>
					<key>width</key>
					<integer>521</integer><!-- "名前"の列幅-->
				</dict>
				<key>size</key>
				<dict>
					<key>ascending</key>
					<false/>
					<key>index</key>
					<integer>3</integer>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>97</integer>
				</dict>
				<key>version</key>
				<dict>
					<key>ascending</key>
					<true/>
					<key>index</key>
					<integer>6</integer>
					<key>visible</key>
					<false/>
					<key>width</key>
					<integer>75</integer>
				</dict>
			</dict>
			<key>iconSize</key>
			<real>16</real>
			<key>showIconPreview</key>
			<true/>
			<key>sortColumn</key>
			<string>dateLastOpened</string>
			<key>textSize</key>
			<real>12</real>
			<key>useRelativeDates</key>
			<true/>
			<key>viewOptionsVersion</key>
			<integer>1</integer>
		</dict>
		<key>WindowState</key>
		<dict>
			<key>ShowPathbar</key>
			<true/>
			<key>ShowSidebar</key>
			<false/>
			<key>ShowStatusBar</key>
			<true/>
			<key>ShowToolbar</key>
			<true/>
			<key>SidebarWidth</key>
			<integer>161</integer>
			<key>WindowBounds</key>
			<string>{{9999, 9999}, {834, 425}}</string><!-- ウィンドウのサイズ -->
		</dict>
	</dict>
  <!--
    ==================== ここまで ViewSettings ここまで ====================
  -->
</dict>
</plist>
以上で、control-option-スペースで素早くSnow Leopard時代の検索性能を手に入れられる! OSX 10.9 MavericksのSpotlightで解消された!