パッケージはフォルダですがFinderではファイルに見えます。パッケージを検索したり、パッケージであることを判断する方法はないものかと長年思い募っていましたが mdls と mdfind がその答えでした。
パッケージの情報なさすぎる
パッケージはファイルに見えるフォルダのことで、Finder では上手く扱えますがネットストレージやダイアログでは期待通りな見え方をしてくれない場合があります。あるときはただのフォルダになったり、あるときは何も見えなかったりします。
何かに付け、パッケージ内ファイルを一緒くたにしたくないこともあります。find を使ってもパッケージを判断できないし、exiftool や stat や file や getFileInfo を使ってもパッケージであることを判別できません。使い分けが非常に難しいことになります。
長年、パッケージを判断したり検索する方法を求めてきました。しかしついに知りました。Spotlight 関連のコマンド mdls と mdfind がその答えでした。
mdfind はSpotlightのインデックスを検索するコマンドで、ぶっ飛びの速さに加えて内容検索までできる優れもの。でもそれだけじゃなかった。いろんなメタ情報でも検索できまして、その情報の中にパッケージである旨も含まれていました。
mdls は md の ls ということでしょうか。こちらもファイル・フォルダに関する情報を知ることができます。パッケージである旨も含まれていたんですねえ。
パッケージを判断
mdls で得られる情報の中にパッケージである旨が含まれます。
パッケージ情報を絞り込むには、-name のオプションに kMDItemContentTypeTree を指定します。属性というかメタデータというかそういうのが「名前」に含まれるというのが誰も思いつかないところです。
mdls -name kMDItemContentTypeTree PATH
こうですね。試しにパッケージになったファイルを指定してみると次のような情報が返ってきます。
kMDItemContentTypeTree = ( "com.apple.mainstage.concert", "public.item", "com.apple.package", "com.apple.mainstage.concert", "public.directory" )
“com.apple.package” がパッケージですね。感動。ついにパッケージを判断できる方法を見つけました😭
パッケージを検索
検索コマンドは mdfind です。Spotlightインデックスから高速に検索できます。
パッケージを検索するには kMDItemContentTypeTree の中に “package” の文言が含まれることを条件にします。
mdfind -onlyin 'PATH' 'kMDItemContentTypeTree == *package* '
こうですか。これで検索できます。-onlyin ‘PATH’ は、指定したパス(ディレクトリ)の中で探すという指令です。ついに、パッケージを探す方法に出会えました😭
検索したリストの出力
しかしここからどうしましょ。確認して終わり探して終わりというだけでは意味ありません。その結果を利用してなんぼです。まずは、検索した結果を出力しないと話になりません。
mdfind -onlyin 'PATH' 'kMDItemContentTypeTree == *package* '
ターミナル上なら結果がターミナルウインドウにつらつら出てきますからこれだけで良いのですが、実際にはターミナルを使いません。do shell script を使い、結果をクリップボードに納めます。
検索スクリプトをいつものように do shell script 化してみるとこうなります。
do shell script "mdfind -onlyin 'PATH' 'kMDItemContentTypeTree == *package* ' | LANG=ja_JP.UTF-8 pbcopy &"
これで良いわけですが、ちょっと個人的な事情があって使いたくありません。というのも、結果が空であるとき、クリップボードに変化が起きないということになります。空が入るでもなく、今入っているクリップボードの内容が消えてくれることもない。変化しないんです。これが実に具合悪い。このような状況に自作システム(CodeRun)が未対応なんです。はい。かなり個人的な事情でした。
なので、もうちょっとちゃんとした条件分岐を成立させる書き方が必要です。
do shell script で条件分岐 ChatGPT のやり方
ターミナルでは if とか then とか fi とか書けば正しく動きます。でもそのスクリプトを丸ごと do shell script に放り込むとエラーになります。書き方を工夫して試しても私のような無学な雑魚のスキルでは無駄でした。
諦めて別の方法に切り替える前に、試しに ChatGPT に訪ねると次のようなスクリプトを教えてくださいました。
set searchResult to do shell script "mdfind -onlyin 'PATH' -name 'kMDItemContentTypeTree == *package*' 2>/dev/null"
if searchResult is not equal to "" then
do shell script "echo " & quoted form of searchResult & " | LANG=ja_JP.UTF-8 pbcopy"
else
do shell script "echo '0'" & " | LANG=ja_JP.UTF-8 pbcopy"
end if
ははーん。多分、条件分岐は do shell script 内ではできなくて、その外つまりAppleScript 側でやれ。と、そういうことのようです。でもこのスクリプトは汚いですね。しかも何だか腑に落ちません。ただし悔しいことに正しく動きました。
これを採用しようと一度はしました。しかし美しくないし、理解もできていないのに採用するのが癪なので辞めました。代わりに行った方法はこうです。
pbcopy を諦めてファイルにした
クリップボードの難解さを克服することを諦め、ファイルに書き出しただけの解決方法です。
mdfind -onlyin 'PATH' 'kMDItemContentTypeTree == *package*' > 'FILEPATH'
結果が空なら中身空のファイルが書き出されます。エラーにはなりません。シンプルかつ判りやすい、なんてスマートな解決方法でしょう。これでパッケージの有無を簡単に知ることができ、それを条件に find コマンドを完成させることができるようになりました。
こんなふうに使ってみました
使用例というか自分はこのように使いましたという例です。自分の使い方は、FileMaker で面倒な作業を済ませてコマンドラインツールをシンプルに使います。あまり参考にならないと思いますが。
目的
特定ディレクトリ以下のメディアファイルをインポートするため、条件に合致したファイルパスのリストを入手します。
条件は複数ありますが、パッケージ内のファイルを除外するのもその一つ。
Find を使ったスクリプトのテンプレがあります。最終的に{ OPTION } をパッケージ除外の条件で置換し、スクリプトを完成させます。
find '目的ディレクトリパス' -type f {OPTION} -exec stat -f '%N, %B, %m' {} + | pbcopy
パッケージ除外の option文 を作る
パッケージ除外の条件を作成するために mdfind を使います。
‘目的ディレクトリパス’ 内のパッケージを検索します。
mdfind -onlyin '目的ディレクトリパス' 'kMDItemContentTypeTree == *package*' > 'filePath'
pbcopy が使えないのでファイルに書き出しています。ファイルパスを指定したり読み込んだり読み終えたら閉じて棄てるという作業の流れがありますが詳細は割愛します。
目的ディレクトリ内にあるパッケージのパスリストが入手できました。パッケージがなければ何も取得しませんし、何も処理する必要はありませんのでここでは入手したとして続けます。
取得したパッケージのパスは、find コマンドの除外指定に使うわけですが、フルパスである必要はありません。もっと少ない文字列で済ませましょう。パッケージには拡張子みたいなのが付いていますから、それだけを抜き出します。
取得したのはパスのリストですから1行ずつ処理します。1行しかなくても1行ずつ処理します。
行ごとに、文字列を追加してこういう形に仕上げます。
-not -ipath '*拡張子/*'
リストが複数行あれば、ユニーク処理などした後、最後は改行をスペースに変換してリストからただの文字列へと変更します。
完成形の find には他にも -name オプションがたくさん追加され、かなりの長文になることもあります。長すぎたらエラーが出るので簡潔にまとめなければなりません。
\\( -not -ipath '*拡張子/*' -not -ipath '*拡張子/*' -not -ipath '*拡張子/*' \\)
do shell script を使うのでエスケープ付きになっていますが、括弧で括ってまとめています。実際の話パッケージがそれほどたくさんあるとは考えにくいので、こちらの括弧処理は不要かもしれません。
で、こうして出来上がったOption文を、最初の find テンプレの {OPTION}の場所に填め込みます。
find '目的ディレクトリパス' -type f \\( -not -ipath '*拡張子/*' -not -ipath '*拡張子/*' \\) -exec stat -f '%N, %B, %m' {} + | pbcopy
こんな感じでしょうか。最終目的であるパスのリストをゲットできました。
find オプション -name は名前、-path はパス全体
大体において、ファイル名、フォルダ名、パスというのは結局パスであることが基本です。いろんなコマンドでも、Name がパスを指すことが多いですね。stat でも name を出力するとファイル名ではなくパスになります。
find はちょっと違いますね。-name と -path が明確に分けられています。-name はパスの最後、ファイル名やフォルダ名だけを指します。パスの中に含まれる文言で検索したいときは -path を付けるのであるという、これわりと最近まで知りませんでした。大文字小文字を区別しないときは -iname, -ipath にしておきましょう。
というわけで、あまり関係のないオマケ話もくっつきましたが、パッケージ情報を得られる mdls と、検索できる mdfind でした。