Archive for the 'ファイル検索(spotlight)' Category

2017/03/15 Spotlightで指定フォルダ以下の指定文字を含むファイル一覧を取得

Cocoaの機能を呼び出すAppleScriptObjCでは、さまざまなCocoaのAPIを呼び出す手法が模索されてきました。その中でもSpotlight検索は「割とまだ決定版の手法が確立していない」ものでありました。

spotlightでタグを指定して検索
http://piyocast.com/as/archives/3731

ASOCでmdfindするじっけん v4(フルパスを返す)
http://piyocast.com/as/archives/4122

などなど、徐々に進化してきて、Shane StanleyのAppleScript Libraries「BridgePlus」にSpotlightの呼び出し命令が実装されたりと、手段が洗練されてきたという経緯があります。

書籍「AppleScript 10大最新技術」にも掲載していますが、その時点のバージョンよりもはるかにこなれた書き方がML上でShane Stanleyから提示されました。いままでよりもはるかに短いので、ちょっと腰を抜かしてしまったほど。しかも、とてもさりげなく、、、

「ああ、こういう書き方でもいいんだ、、、」

と、かなりすごいことになっています。

AppleScript名:Spotlightで指定フォルダ以下の指定文字を含むファイル一覧を取得
– Created 2017-03-15 By Shane Stanley
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
–http://piyocast.com/as/archives/4528

set thePath to POSIX path of (path to desktop) – whatever

–ファイル名で検索
set theKeyword to “スクリーンショット”
set fResList to my searchPath:thePath searchPredicate:“(kMDItemFSName CONTAINS [c]%@)” predicateArgs:{theKeyword}
–>  {”/Users/me/Desktop/2013/03/スクリーンショット 2016-12-15 8.44.28.png”, “/Users/me/Desktop/FromDesktop/samples/list splitted or broken/スクリーンショット 2015-08-31 10.49.30.png”, …… }

–UTI(File Kind)で検索
set theKeyword to “public.png”
set fResList to my searchPath:thePath searchPredicate:“(kMDItemContentType == [c]%@)” predicateArgs:{theKeyword}
–> {”/Users/me/Desktop/2013/03/スクリーンショット 2016-12-15 8.44.28.png”, “/Users/me/Desktop/FromDesktop/IMG_3143_resized.png”, “/Users/me/Desktop/FromDesktop/asoctable.png”….}

–ファイルの内容で検索
set fResList to my searchPath:thePath searchPredicate:“(kMDItemTextContent == [c]%@)” predicateArgs:{“戦場の絆”}
–>  {”/Users/me/Desktop/2013/01/gundam_games_history.txt”, “/Users/me/Desktop/book1_2.0.pdf”}

on searchPath:thePath searchPredicate:predString predicateArgs:argList
  set thePred to current application’s NSPredicate’s predicateWithFormat:predString argumentArray:argList
  
set targetURL to current application’s |NSURL|’s fileURLWithPath:thePath
  
set theQuery to current application’s NSMetadataQuery’s new()
  
  
theQuery’s setPredicate:thePred
  
theQuery’s setSearchScopes:{targetURL}
  
theQuery’s startQuery()
  
  
repeat while theQuery’s isGathering() as boolean
    delay “0.001″ as real
  end repeat
  
theQuery’s stopQuery()
  
  
set theCount to theQuery’s resultCount()
  
set theResults to current application’s NSMutableArray’s array()
  
  
repeat with i from 1 to theCount
    set aResult to (theQuery’s resultAtIndex:(i - 1))
    
set thePath to (aResult’s valueForAttribute:(current application’s NSMetadataItemPathKey))
    (
theResults’s addObject:thePath)
  end repeat
  
  
return (theResults’s sortedArrayUsingSelector:“compare:”) as list
end searchPath:searchPredicate:predicateArgs:

★Click Here to Open This Script 

2016/07/12 Absolute Timeを取得

2001年1月1日 00:00:00からの相対時間であるAbsolute Timeを求めるAppleScriptです。

なんでこんなものが必要になったのかといえば、Spotlightでファイル作成日時を指定して検索したいと思ったときに、例によってFinder上でSpotlight検索を行い、その内容を保存。

保存すると紫色のアイコンである「保存された検索クエリー」になるので、Command-iで情報を見るとかんたんにmdfindの検索パラメータが確認できます。

saved_search.png

なじみのない数値が……。そこで、1年の秒数(3600×24×365)で割ってみたところ、2000年近辺の数字が出てきました。

そのため、2000年あたりからの相対秒だと当たりをつけて、Appleのオンラインドキュメントを検索してみたところ、大当たり。これで、(実行時の)現在時刻からAbsolute Timeを求めることができました。ただ求めただけでは、AppleScriptの数値では指数表示になってしまうため、なるべくオリジナルのまま壊さないように文字列化してみました。

Absolute Timeを求める方法はほかにもありそうなので、探してみることとしましょう。

この記述を用いて、「指定フォルダ内のPages書類とMarkdown書類をデスクトップ上にPDF出力し、それを順番に連結して1つのPDFに出力。デスクトップ上に書き出した不要なPDFは削除する」というAppleScriptを仕上げることができました。

つまり、電子ブック1冊分を完全自動でビルドできるようになったわけで(以前は、書類ごとのPDFを書き出すプロセスとPDFの合成プロセスが別々)、たいへんにけっこうなことです。

AppleScript名:Absolute Timeを取得
– Created 2016-07-12 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

–タイムスタンプ取得(Jan 1 2001 00:00:00 GMTからの相対秒、Absolute Timeで取得)
set aTime to current application’s NSString’s stringWithFormat_(“%@”, current application’s CFAbsoluteTimeGetCurrent()) as string
–>  ”490022703.57607″

★Click Here to Open This Script 

2016/06/18 指定フォルダ以下にあるMacDownとPages書類をソートしてPDFに書き出す

指定フォルダ以下にあるMacDownで記述したMarkdown書類と、Pages書類をすべての階層からピックアップしてファイル名でソートして、すべてデスクトップにPDFで書き出すAppleScriptです。

コンパイル(構文確認)および実行に際しては、Shane StanleyのScript Library「Bridge Plus」をインストールしておく必要があります。また、GUI Scriptingを利用しているため「システム環境設定」>「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」でスクリプトエディタ(アプレットとして実行する場合にはアプレットそのもの)を登録して許可しておく必要があります。

「技術書典」に出す電子ブックのフォーマットがギリギリまで決まらず、しかも縦長のスクロールさせるタイプのものにできないかとあがいていたのですが、結局iPadあたりで読むことを考慮するとiPadのリーダーの仕様にしたがう必要があります。

……あれ?(^ー^; 結局、ページめくりは発生するし、一般的な本と同じような体裁になってしまいますよ → フォーマットがPDFになりました。

Markdown書類とPages書類が混在しているフォルダ構造のトップ階層のフォルダを指定すると、Markdown書類とPages書類をピックアップし、ファイル名でそれらをソートし、順次PDFに書き出すAppleScriptを書いてみました(必要は発明のマザー!)。

book1.png

ただ、MacDownには「書類をPDFに書き出す」という機能がAppleScript側に公開されていません(T_T)。

macdown_dict.png

ないものを「ないない」と嘆いても仕方がないので、さっさとGUI Scriptingで強制的にメニュー操作することにしました。

macdown_gui.png

で、どこに? どこに保存させるのでしょう??

大丈夫! そんなときには、保存ダイアログで幾つかのフォルダに強制的に移動させるキーボードショートカットが存在しており、Command-Dは「カレントディレクトリをデスクトップに移動させる」=「保存先をデスクトップにする」働きをします。

このため、保存先を操作しづらい(不可能とはいいませんけれども)GUI Scriptingにおいて保存先を指定することが、デスクトップフォルダについては可能になっています。

Pagesの方はひじょうに素直に(GUI Scriptingなんて使わずに)PDF書き出しが可能です。

そんなわけで、時間に追い詰められながらもなんとか大量のデータ処理を行っているのでありました。書き出した大量のPDFもAppleScriptでさくっと連結できるので、非常にいい感じです。あとは、本が完成すれば、、、、

AppleScript名:指定フォルダ以下にあるMDとPagesをソートしてPDFに書き出す
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”
–use spotLight : script “spotlightLib”

property searchRes : {}

load framework

set origPath to POSIX path of (choose folder with prompt “Markdown/Pages ファイルの入っているフォルダを選択”)

set aResList to (spotlightSearch(origPath, “kMDItemKind == ’Markdown’ || kMDItemKind == ’Pages 一般書類’”) of me) as list –Caution! this parameter is *localized*

–フルパスとファイル名のペアの2D Listを作成
set newList to {}
repeat with i in aResList
  set j to contents of i
  
set aStr to (current application’s NSString’s stringWithString:j)
  
set aFileName to aStr’s lastPathComponent()
  
set the end of newList to {aStr, aFileName}
end repeat

–番号順にソート
set sortIndexes to {1} –Key Item id: begin from 0, Sort by filename
set sortOrders to {true}
set sortTypes to {“compare:”}
set resList to (current application’s SMSForder’s subarraysIn:(newList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list
if resList = {} then return –No Result

–ソートした順番にMarkdownファイル/Pages書類ファイルをオープンしてデスクトップにPDF生成してクローズ
repeat with i in newList
  copy i to {fullPath, aFileName}
  
  
set apFile to (POSIX file (fullPath as string))
  
set anAlias to apFile as alias
  
set aFileName to aFileName as string
  
  
if aFileName ends with “.md” then
    exportFromMacDown(anAlias) of me –Markdown
  else if aFileName ends with “.pages” then
    exportFromPages(anAlias) of me –Pages
  end if
end repeat

–指定のPagesファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromPages(anAlias)
  tell application “Finder”
    set aName to name of anAlias
  end tell
  
  
set dtPath to (path to desktop) as string
  
set outPath to dtPath & aName & “.pdf”
  
  
tell application “Pages”
    close every document without saving
    
open anAlias
    
export document 1 to file outPath as PDF with properties {image quality:Best}
    
close every document without saving
  end tell
end exportFromPages

–指定のMacDownファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromMacDown(anAlias)
  tell application “MacDown”
    open {anAlias}
  end tell
  
  
tell current application
    delay 1 –ここの時間待ちが少ないと画像抜けが発生?
  end tell
  
macDownForceSave() of me
  
  
tell application “MacDown”
    close every document without saving
  end tell
end exportFromMacDown

–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceSave()
  activate application “MacDown”
  
tell application “System Events”
    tell process “MacDown”
      – File > Export > PDF
      
click menu item 2 of menu 1 of menu item 14 of menu 1 of menu bar item 3 of menu bar 1
      
      
–Go to Desktop Folder
      
keystroke “d” using {command down}
      
      
–Save Button on Sheet
      
click button 1 of sheet 1 of window 1
    end tell
  end tell
end macDownForceSave

–Spotlight Libの内容を引っ張り出してきた
on spotlightSearch(origPOSIXpath, aCondition)
  
  
set searchRes to {} –initialize
  
  
initiateSearchForFullPath(aCondition, origPOSIXpath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while searchRes = {}
    current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
  end repeat
  
  
set anObj to searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
–set anAttrList to anObj’s attributes() –”mdls” attributes
  
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  –  
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2016/06/17 ASOCでmdfindするじっけん v4(フルパスを返す)

Spotlightの機能をCocoa経由で呼び出してmdfindコマンドのクエリーと検索対象フォルダをPOSIX pathで与えると、条件に合致するファイルのPOSIX pathをリストに入れて返してくるAppleScriptです。

以前に掲載したバージョンを実際に使おうとしたら、激しく間違っていることを発見。なんで気がつかなかったんでしょう。いえ、なんでこんな忙しいときに気づいてしまうんでしょう。

AppleScript名:ASOCでmdfindするじっけん v4(フルパスを返す)
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set origPath to POSIX path of (choose folder)
set aList to spotlightSearch(origPath, “kMDItemContentType == ’net.daringfireball.markdown’”) of me

on spotlightSearch(origPath, aMDfindParam)
  
  
set my searchRes to {} –initialize
  
initiateSearchForFullPath(aMDfindParam, origPath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while my searchRes = {}
    delay 0.01
  end repeat
  
  
set anObj to my searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
  
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2015/09/25 spotlightでタグを指定して検索

Cocoaの機能を用いてSpotlight検索で指定フォルダ以下の指定タグのついているファイルを検索するAppleScriptです。

Shane StanleyがAppleScript Users MLに投稿したもので、Spotlightの検索待ち処理でプロパティをループで監視するやり方が採用されていますが、delayの秒数に0.01と指定して済ますあたりが書きこなれています。

実際0.1秒以下の数値指定は無効ではあるものの、delayを使わないとうまく動きません。動いているし、この書き方でエラーを回避できているので、これでいいんでしょう。

複数のPOSIX pathおよび定数で指定されるフォルダをリストにして与え、一括検索を行っているあたりもみどころです。

AppleScript名:spotlightでタグを指定して検索
– Created 2015-09-25 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchIsDone : false

set aRes to my searchInListOfPaths:{POSIX path of (path to home folder)} forTags:{“レッド”}
–>  {{kMDItemPath:”/Users/me/Xxxxxxxxx/XxxxxXxxxxx/XX出撃回数集計(xxxxx)/XXXX_9xxxx_x9(XXXX_XXXX統合版) _9999.9.9XX変更対応.scptd”, kMDItemUserTags:{”レッド”}}, {kMDItemPath:”/Users/me/Xxxxxxxxx/XxxxxXxxxxx/XxxXxxx上のリプレイのダウンロード(alive)/指定IDの戦場の絆のムービーをOffLiberty経由でローカルにダウンロード v3.scpt”, kMDItemUserTags:{”レッド”}}, {kMDItemPath:”/Users/me/Xxxxxxxxx/XxxxxXxxxxx/クリップボードの内容をRTFとPDFとHTMLで書き出す v2.scptd”, kMDItemUserTags:{”レッド”}},…….}

(* scope can be a list of POSIX paths and/or the following enums:
NSMetadataQueryUserHomeScope
NSMetadataQueryLocalComputerScope
NSMetadataQueryNetworkScope
NSMetadataQueryUbiquitousDocumentsScope
NSMetadataQueryUbiquitousDataScope
NSMetadataQueryAccessibleUbiquitousExternalDocumentsScope
NSMetadataQueryIndexedLocalComputerScope — 10.10 or later
NSMetadataQueryIndexedNetworkScope

Eg:
searchInListOfPaths:{current application’s NSMetadataQueryLocalComputerScope} forTags:tagList
*)

–スコープリストの範囲のフォルダから、指定タグを含むファイルをすべてリストアップ
on searchInListOfPaths:scopeList forTags:tagList – pass empty list for any tags
  
  
– Build the search predicate
  
set tagListCount to count of tagList
  
if tagListCount = 0 then – any tags
    set pred to current application’s NSPredicate’s predicateWithFormat:(“kMDItemUserTags LIKE ’*’”)
  else if tagListCount = 1 then
    set pred to current application’s NSPredicate’s predicateWithFormat:“kMDItemUserTags CONTAINS[c] %@” argumentArray:tagList
  else
    set predList to {}
    
repeat with aTag in tagList
      set end of predList to (current application’s NSPredicate’s predicateWithFormat:“kMDItemUserTags CONTAINS[c] %@” argumentArray:{aTag})
    end repeat
    
set pred to current application’s NSCompoundPredicate’s andPredicateWithSubpredicates:predList
  end if
  
  
– make the query
  
set theQuery to current application’s NSMetadataQuery’s alloc()’s init()
  
theQuery’s setPredicate:pred
  
  
– set its scope
  
theQuery’s setSearchScopes:scopeList
  
  
– tell the notification center to tell us when it’s done
  
set notifCenter to current application’s NSNotificationCenter’s defaultCenter()
  
notifCenter’s addObserver:me selector:“searchDone:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:theQuery
  
  
– start searching
  
set my searchIsDone to false
  
theQuery’s startQuery()
  
  
– loop until it’s done
  
repeat
    delay 0.01
    
if searchIsDone then exit repeat
  end repeat
  
  
– get results
  
theQuery’s stopQuery()
  
set theCount to theQuery’s resultCount()
  
set resultsArray to current application’s NSMutableArray’s array() – to hold results
  
repeat with i from 1 to theCount
    (resultsArray’s addObject:((theQuery’s resultAtIndex:(i - 1))’s valuesForAttributes:{current application’s NSMetadataItemPathKey, “kMDItemUserTags”}))
  end repeat
  
  
return resultsArray as list – will be list of records
  
end searchInListOfPaths:forTags:

on searchDone:notification
  set my searchIsDone to true
end searchDone:

★Click Here to Open This Script 

2015/09/05 ASOCでspotlight検索するじっけん v2

Cocoaの機能を用いてspotlight検索を行うAppleScriptです。Script Editor上でControl-Command-RでForeground実行する必要があります。

前バージョンでは、ファイル名しか取得できていませんでしたが、検索でヒットしたアイテムのフルパスを返すようにできたので、ようやくこれで実用レベルに達したと思います。

(1)検索対象フォルダを指定
(2)検索条件を設定

すると、実際にspotlight検索を行って結果を返します。

検索結果待ちループ側でdelayを極小にできるよう、試行錯誤してみましたが、

(1)delayを削除するとうまく動かない
(2)delay 0.01のとおりに本当に0.01秒待っているかはわからない
(3)手元の環境ではうまく動いているが、環境に応じたチューニングが必要?(2台で確認)

という印象を持っています。

ASOCで0.001という数値をプログラム内に記述するのにえっらい苦労しました。そのまま書くと指数表現されてしまい、Pure AppleScriptの世界ではエラーにならないんですが、Cocoaのオブジェクトに渡されるときにうまくブリッジされないのか、エラーに。NSSNumberでfloatValueを設定しようとしてもうまく行かないわで、結局、文字列で書いて強制的にcastするという強引な方法で解決。もっといい方法があるとよいのですが、相当苦労しました。

いろいろ条件を変えて検索の実験をしたところ、与えた検索条件でヒットがない場合には早々に{}を返してくるため、無限ループで処理が終わらないということはないようです。

これでもう、spotlight検索のためにshell commandのmdfindを呼び出す必要はなくなりました。

AppleScript Users MLの過去ログや、ShaneのEveryday AppleScriptObjC添付のサンプルコード、ASObjC Explorer 4添付のサンプルコードなども探し回ってspotlight検索のサンプルを探してみましたが、検索結果のフルパスを返すものはなかったので(ついでに言うとObjective-Cのプログラムを検索エンジンで探しても、けっこう見つからない)、これを書いておいた意義はあるものと思います。

AppleScript名:ASOCでmdfindするじっけん v2(フルパスを返す)
– Created 2015-09-03 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set searchRes to {} –initialize

set origPath to POSIX path of (choose folder)
initiateSearchForFullPath(“kMDItemDisplayName == ’ICON0.PNG’”, origPath) –Predicate & Scope Directory

–Waiting for the result
repeat while searchRes = {}
  current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
end repeat

set anObj to searchRes’s firstObject() –Pick up the first one for test
if anObj = missing value then return {} –No Result

–set anAttrList to anObj’s attributes() –”mdls” attributes
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}

set resArray to current application’s NSMutableArray’s alloc()’s init()
repeat with anItem in searchRes
  (resArray’s addObject:(anObj’s valueForAttribute:“kMDItemPath”))
  
–>  (NSString) “/Users/me/Documents/機動戦士ガンダム戦場の絆 Folder/ULJS00181USERID_0000/ICON0.PNG”
end repeat

resArray
–>  (NSArray) {”/Users/me/Documents/機動戦士ガンダム戦場の絆 Folder/ULJS00181USERID_0000/ICON0.PNG”,……}

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2015/08/31 ASOCでspotlight検索するじっけん

Cocoaの機能を用いてspotlightによる検索を行うAppleScriptの実験的な実装です。

オリジナルは、Appleのオンラインドキュメント「File Metadata Search Programming Guide」にあった、「Listing 2-1 Static Spotlight search implementation」です。これをAppleScriptObjCに(少しアレンジを加えつつ)翻訳したものです。

Script Editor上でCommand-Control-RによるForeground実行する必要があります。プログラムリスト中におけるログ内容はASObjC Explorer 4で表示させたもので、同様の内容を確認したい場合にはASObjC Explorer 4が必要です。

海外のユーザーから「どうやって表示してんの、それ?(Cocoa Objectのログ)」と、問い合わせがあって「通常のScript Editorでは表示できないよ」と回答。意外と知られていないようで、、、(もったいないので、ここで宣伝しておくことにしました)。

ASObjC Explorer 4のログ表示は、AppleScriptのプログラムの間にログ表示用のコードを挟んでいるとのことなので、必ずしも実際のオブジェクトとイコールの内容が表示されているわけではありませんが、実際の内容をつかみやすいものになっています。この機能を持つAppleScript系のエディタは、目下のところASObjC Explorer 4だけです。

Objective-CのプログラムをさらっとAppleScriptObjCのプログラムに書き換えできるのは、ASObjC ExplorerのCocoaオブジェクトログ機能があればこそです。ログを出しつつ属性値を確認できるため、おおいに役立っています。逆に、Cocoaオブジェクトログ機能がないと、全く歯が立ちません(AppleがShaneのプログラムを買い取って、標準のScript Editorに機能をマージすればいいのに、、、)。

AppleScript名:ASOCでspotlight検索するじっけん
– Created 2015-08-31 by Takaaki Naganoya
– 2015 Piyomaru Software

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set searchRes to {} –initialize
initiateSearch(“kMDItemContentTypeTree == ’public.image’”)

–Waiting for the result
repeat while searchRes = {} –Inifinity loop, danger!!
  delay 0.1
end repeat

set anObj to searchRes’s firstObject() –Pick up the first one for test
set anAttrList to anObj’s attributes() –”mdls” attributes
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}

set aFileName to anObj’s valueForKey:“kMDItemDisplayName”
–>  (NSString) ” 1 - 95.7.16.jpg” — example

on initiateSearch(aQueryStrings)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
–>  (NSMetadataQuery)
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
–> (NSComparisonPredicate) kMDItemContentTypeTree == “public.image”
  
aSearch’s setPredicate:aPredicate
  
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{current application’s NSMetadataQueryUserHomeScope, current application’s NSMetadataQueryUbiquitousDocumentsScope}
  
–> (NSArray) {{”kMDQueryScopeHome”, “NSMetadataQueryUbiquitousDocumentsScope”}
  
aSearch’s setSearchScopes:aScope
  
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemDisplayName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
  
aSearch’s startQuery()
  
end initiateSearch

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
  
  
set anObject to sender’s object
  
anObject’s stopQuery()
  
  
set aRes to anObject’s resultCount()
  
–>  173352 –different in each environment. Just a test
  
  
set my searchRes to anObject’s results()
  
–>  (NSArray) {(NSMetadataItem) , (NSMetadataItem) , ……
  
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set anObject to missing value –really needed?
  
end initalGatherComplete:

★Click Here to Open This Script 

2015/04/22 Spotlight検索インデックスを再生成

起動ディスクのSpotlight検索インデックスを再生成するAppleScriptです。ふつうに、スクリプトエディタ上で実行するものです。実行時に管理者パスワードの入力を求められます。

さまざまなマシン環境を見たときに、Spotlightの検索インデックスが「壊れている」マシンを見かけることがあります。こうなると、mdfindコマンドでspotlightの機能を呼び出して、指定のファイルを見つけ出す処理が有効に機能しません。このような場合には、インデックスの再生成が必要になります。

Spotlightのインデックス再生成は、システム環境設定で行うようになっており、そのものズバリの名前がついていないため、ひじょうに分かりにくくなっています。

そのため、説明を省くことを目的にAppleScriptで書いておきました。Terminalが使えないユーザーでも、スクリプトを実行するだけなら可能でしょう。

ただし、Spotlight復旧のためにはSpotlightのインデックス再生成のほか、ディスクユーティリティーで「ユーザー権限の修復」を行う必要があったり、そもそも起動ディスクの一部のセクタが損傷しているケースなどもあり、「万能薬」ではないことに注意が必要です。

AppleScript名:Spotlight検索インデックスを再生成
do shell script “mdutil -E /” with administrator privileges

★Click Here to Open This Script 

2015/01/24 spotlightでファイルタグを指定してファイル一覧を取得

OS X 10.9, Mavericksで採用されたFinderのFile Tagで、指定フォルダ以下のファイルをリストアップするAppleScriptです。

だいたい、OS Xの新機能については1つのバージョンを置いてAppleScript側が追いつく、というスタイルが定着していたので、OS X 10.10でFile Tagのサポートがなかったことには(よくないほうの意味で)驚きました。

ただ、10.10で導入されたScript Editor上でのAppleScriptObjCの機能のサポートによって、File Tagの設定、取得、追加についてはAppleScriptObjCで書くもの、という認識を持っていました。

それでも、指定フォルダ以下のFile Tagによるファイル抽出については情報が出ていなかったので、いろいろと調べたところ・・・mdfindで検索するのが一番簡単という結論に。

filetag1.png

本来的には、フィルタ参照(get every file whose tags is equal to {”Blue”, “Green”} 的な)で抽出できることがAppleScript的な「作法」ではあるものの、Appleがそうした機能を提供していない(できていない)という状況に対処するためには、こうしたサブルーチンの利用が現実的でしょう。

ただ、{”グリーン”, “ブルー”, “ヤンキー”} というファイルタグを持つファイルが{”グリーン”, “ブルー”}でリストアップされてしまうので、まだ調整の余地がありそうです。

filetag2.png

などと書いたので、Finder上でSpotlightの動作を調べてみたら・・・この通りの動きをするようなので、仕方ないのかも、、

filetag3.png

AppleScript名:spotlightでファイルタグを指定してファイル一覧を取得
– Created 2015-01-24 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions

set aFol to path to desktop
set aTagList to {“グリーン”, “ブルー”}
set aRes to getFileListByTagsWithSpotLight(aTagList, aFol) of me
–> {alias “Macintosh HD:Users:me:Desktop:fra5.png”, alias “Macintosh HD:Users:me:Desktop:fra6.png”}

–指定階層下で、指定タグ(複数をリストで指定)を持つファイルを取得(alias list)
on getFileListByTagsWithSpotLight(aTagList as list, startDir as {alias, string})
  –Error Case
  
if aTagList = {} then return {}
  
if startDir as string = “” then return
  
  
–Make File Tag Search Query String
  
set aQuery to {}
  
repeat with i in aTagList
    set aTag to contents of i
    
set aStr to “kMDItemUserTags==” & aTag
    
set the end of aQuery to aStr
  end repeat
  
set queryText to retArrowText(aQuery, ” && “) of me
  
  
–Make mdfind command string
  
set sDirText to quoted form of POSIX path of startDir
  
set shellText to “/usr/bin/mdfind ’” & queryText & “’ -onlyin “ & sDirText
  
try
    set aRes to do shell script shellText
  on error
    return {}
  end try
  
  
–Parse Result into alias list
  
set pList to paragraphs of aRes
  
set aList to {}
  
repeat with i in pList
    set aPath to POSIX file i
    
set aPath to aPath as alias
    
set the end of aList to aPath
  end repeat
  
return aList
  
end getFileListByTagsWithSpotLight

–リストを任意のデリミタ付きでテキストに
on retArrowText(aList, aDelim)
  set aText to “”
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retArrowText

★Click Here to Open This Script 

2014/12/25 アプリケーションごとのローカライズ言語数を求める

/Applicationsフォルダ以下の全アプリケーションのローカライズ言語数を求めて多い順にソートして返すAppleScriptです。

便利な部品ができてきたので、本来の用途以外に転用するのも簡単になってきました。

ただ、こういう「調査活動」以外にこの処理が生きるかどうかは未知数です、、、

OS X自体のローカライズ言語数=35ということが見て取れますが(ただし、対応言語は幾つかのレベルに分けられており、ユーザー数によって対応度が違うはず)、それ以上のローカライズ言語を持っているアプリケーションの存在が気になります。

AppleScript名:アプリケーションごとのローカライズ言語数を求める
– Created 2014-12-25 by Takaaki Naganoya

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

–/Applications以下のアプリケーションのパスをすべて求める
set apPath1 to (path to applications folder) as string
set aList to getFileListWithSpotLight(“kMDItemContentType”, “com.apple.application-bundle”, apPath1) of me

–各アプリケーションのローカリゼーション情報を取得(重複情報はカウントしつつ保持)
set aaList to {}

repeat with i in aList
  set j to contents of i
  
  
–アプリケーションのローカリゼーションを取得する(重複言語の除去ずみ)
  
set locList to getSpecifiedAppFilesLocalizationList(j) of me
  
  
tell application “System Events”
    set aName to name of j
  end tell
  
  
set the end of aaList to {aName, length of locList}
  
end repeat

–Sort Result (Many->Little)
set sortIndexes to {1} –Key Item id: begin from 0
set sortOrders to {false} –Descending Sort Order
set sortTypes to {“compare:”}
set rList to (current application’s SMSFord’s subarraysIn:aaList sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list

return rList
–> {{”VLC.app”, 93}, {”Creative Cloud.app”, 61}, {”Adobe Application Manager.app”, 61}, {”Google Drive.app”, 53}, {”Google Chrome.app”, 53}, {”Uninstall Product.app”, 43}, {”Uninstall Product.app”, 43}, {”Google Earth.app”, 42}, {”VueScan.app”, 40}, {”Evernote.app”, 40}, {”Adobe Muse CC 2014.app”, 37}, {”iTunes.app”, 35}, {”Digital Color Meter.app”, 35}, {”Image Capture.app”, 35}, {”Grab.app”, 35}, {”Game Center.app”, 35}, {”iBooks.app”, 35}, {”Boot Camp Assistant.app”, 35}, {”Bluetooth File Exchange.app”, 35}, {”System Information.app”, 35}, {”Safari.app”, 35}, {”Terminal.app”, 35}, {”Contacts.app”, 35}, {”Preview.app”, 35}, {”App Store.app”, 35}, {”Automator.app”, 35}, ……

–指定アプリケーションファイルの、指定Localeにおけるローカライズ言語リストを求める。重複を排除
on getSpecifiedAppFilesLocalizationList(anAppAlias as alias)
  
  
set aURL to (current application’s SMSFord’s URLFrom:anAppAlias)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
  
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:“en” –Output Locale Name in English (en)
  
  
–Get Localization Info
  
set locList to aBundle’s localizations()
  
–> {”de”, “English”, “fr”, “French”, “German”, “ja”, “Japanese”}
  
set theEnumerator to locList’s objectEnumerator()
  
  
set theSet to current application’s NSMutableSet’s |set|()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
    
set theName to (theNSLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:aValue)
    
    
if theName is missing value then
      –”English”や”French”などは、missing valueが返ってくるので、”English”や”French”などをそのまま追加
      
theSet’s addObject:aValue
    else
      –”de”や”fr”などは、”German”や”French”が返ってくるので、それを追加
      
theSet’s addObject:theName
    end if
  end repeat
  
  
–NSCountedSetをNSMutableArrayに変換
  
set theArray to current application’s NSMutableArray’s array()
  
set theEnumerator to theSet’s objectEnumerator()
  
  
–NSMutableArrayから順次取り出して、NSArrayに出力
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
theArray’s addObject:aValue
  end repeat
  
  
return theArray as list
  
end getSpecifiedAppFilesLocalizationList

–指定階層下で、指定メタデータが指定パラメータであるファイルを取得(alias list)
on getFileListWithSpotLight(aMetaDataItem as string, aParam as string, startDir as {alias, string})
  set sDirText to quoted form of POSIX path of startDir
  
set shellText to “/usr/bin/mdfind ’” & aMetaDataItem & ” == \”" & aParam & “\”’ -onlyin “ & sDirText
  
try
    set aRes to do shell script shellText
  on error
    return {}
  end try
  
set pList to paragraphs of aRes
  
set aList to {}
  
repeat with i in pList
    set aPath to POSIX file i
    
set aPath to aPath as alias
    
set the end of aList to aPath
  end repeat
  
return aList
end getFileListWithSpotLight

★Click Here to Open This Script 

2009/02/04 指定階層下で、指定クリエータコードのファイルを取得

指定フォルダ以下に存在する、指定クリエーターコードのファイル一覧をmdfindコマンド(Spotlight)で取得します。

スクリプト名:指定階層下で、指定クリエータコードのファイルを取得
set startDir to choose folder
set bList to getFileListWithSpotLight(”kMDItemContentType“, com.apple.xcode.project“, startDir) of me Xcodeのファイルをクリエータコードで検索

指定階層下で、指定クリエータコードのファイルを取得(Mac OS X 10.4.x以上で動作)
on getFileListWithSpotLight(aMetaDataItem, aParam, startDir)
  if my checkTiger() = false then return {}
  
  
set sDirText to quoted form of POSIX path of startDir
  
set shellText to mdfind ‘ & aMetaDataItem & == \” & aParam & \”‘ -onlyin & sDirText
  
try
    set aRes to do shell script shellText
  on error
    return {}
  end try
  
set pList to paragraphs of aRes
  
set aList to {}
  
repeat with i in pList
    set aPath to POSIX file i
    
set aPath to aPath as alias
    
set the end of aList to aPath
  end repeat
  
return aList
end getFileListWithSpotLight

Mac OS X 10.4以上で動作中かどうかチェック
on checkTiger()
  tell application System Events
    set sysV to system attribute sysv
    
if sysV < 4159 then
      return false
    else
      return true
    end if
  end tell
end checkTiger

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2008/04/05 SpotLightで検索

AppleScriptでも、Mac OS X 10.4で導入されたSpotlightを使って、ファイルの検索を行えます。shellコマンドのmdfindで検索を、mdlsでSpotlightの検索要素を調べます。指定ディレクトリ下の特定ファイルのリストアップなど、再帰処理で探すよりもSpotlightで探した方が圧倒的に高速処理できるため、実行環境をMac OS X 10.4以降に限定できる場合にはSpotlightの使用をお勧めします。

なお、mdfindのパラメータをよく理解できない場合には、Finderで検索したい内容のSmart Folderを作成し、出来上がったSmart Folderの情報をFinder上で確認すると、「クエリー」の欄にそのパラメータが書かれています。これがほぼそのまま使えます。ある程度の試行錯誤は必要ですが、予想外に簡単なのでぜひトライしてみてください。

Spotlight検索パラメータの調べ方1

Spotlightパラメータの調べ方2

スクリプト名:SpotLightで検索
return do shell scriptmdfind \”kMDItemTextContent == ‘ぴよまる’ || kMDItemTitle == ‘ぴよまる’\”

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に