AppleScriptObjC でエイリアスのオリジナルを得たい

Finderに問い合わせてエイリアスのオリジナル(パス)を得る方法はわかっているのですが、

速度的な問題からあまり使いたいと思っていません。


そこで、AppleScriptObjC ならどうにかなるのでは!? と思いそれらしいコードを書いてみたのですが

結果が常に missing value (表示上は msng) となり、期待する値を得られないでいます。

AppleScriptObjC では使えない Objective-C のクラスがあるとも聞きますし、

そもそも見当違いなコードを書いている可能性もあるので

AppleScriptObjC でエイリアスのオリジナルを得る方法をご存知のかたがいらっしゃれば

お教えいただければと思い質問しました。


ただ、AppleScriptObjC に固執しているわけでなく

例えば JavaScript や do shell script 命令で同様のことができるのであればそれでもかまいません。


# macOS Mojave 10.14.6 / Mac mini 2018

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

set aFile to choose file

tell application "Finder"
	set origI to (original item of aFile) # 通常のファイルだった場合エラーとなり止まる
end tell
display dialog "【オリジナルのパス】" & POSIX path of (origI as text)

set r to current application's NSFileManager's defaultManager()'s ¬
	destinationOfSymbolicLinkAtPath:(POSIX path of aFile) |error|:(missing value)
display dialog r # 常に msng と表示される

Mac mini, macOS 10.14

投稿日 2020/10/08 04:44

返信
スレッドに付いたマーク ランキングトップの返信

投稿日 2020/10/08 07:42

こんな感じでどうでしょうか?


ASOC

use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

property OSX : current application
property nil : missing value

on run
    set dstfpath to OSX's NSString's stringWithString:(POSIX path of (choose file))
    set dstfurl to OSX's NSURL's fileURLWithPath:dstfpath

    set {res, is_alias} to dstfurl's ¬
        getResourceValue:(reference) forKey:(OSX's NSURLIsAliasFileKey) |error|:nil

    if res then
        if is_alias then
            set o1 to OSX's NSURLBookmarkResolutionWithoutUI
            set o2 to OSX's NSURLBookmarkResolutionWithoutMounting
            set opt to o1 + o2
            set srcfurl to OSX's NSURL's ¬
                URLByResolvingAliasFileAtURL:dstfurl options:opt |error|:nil
            set srcfpath to srcfurl's |path| as text
            return srcfpath
        else
            return "not alias file: " & dstfpath as text
        end if
    end if
end run


JXA

'use strict';

var App = Application.currentApplication(); App.includeStandardAdditions = true;

function run() {
    let dstfpath = App.chooseFile().toString();
    let dstfurl = $.NSURL.fileURLWithPath(dstfpath);
    let is_alias = $();
    if (dstfurl.getResourceValueForKeyError(is_alias, $.NSURLIsAliasFileKey, $())) {
        if (is_alias.js) {
            let srcfurl = $.NSURL.URLByResolvingAliasFileAtURLOptionsError(
                dstfurl,
                $.NSURLBookmarkResolutionWithoutUI | $.NSURLBookmarkResolutionWithoutMounting,
                $()
            );
            let srcfpath = srcfurl.path.js;
            return srcfpath;
        } else {
            return `not alias file: ${dstfurl.path.js}`;
        }
    }
}


返信: 8
スレッドに付いたマーク ランキングトップの返信

2020/10/08 07:42 light289 への返信

こんな感じでどうでしょうか?


ASOC

use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

property OSX : current application
property nil : missing value

on run
    set dstfpath to OSX's NSString's stringWithString:(POSIX path of (choose file))
    set dstfurl to OSX's NSURL's fileURLWithPath:dstfpath

    set {res, is_alias} to dstfurl's ¬
        getResourceValue:(reference) forKey:(OSX's NSURLIsAliasFileKey) |error|:nil

    if res then
        if is_alias then
            set o1 to OSX's NSURLBookmarkResolutionWithoutUI
            set o2 to OSX's NSURLBookmarkResolutionWithoutMounting
            set opt to o1 + o2
            set srcfurl to OSX's NSURL's ¬
                URLByResolvingAliasFileAtURL:dstfurl options:opt |error|:nil
            set srcfpath to srcfurl's |path| as text
            return srcfpath
        else
            return "not alias file: " & dstfpath as text
        end if
    end if
end run


JXA

'use strict';

var App = Application.currentApplication(); App.includeStandardAdditions = true;

function run() {
    let dstfpath = App.chooseFile().toString();
    let dstfurl = $.NSURL.fileURLWithPath(dstfpath);
    let is_alias = $();
    if (dstfurl.getResourceValueForKeyError(is_alias, $.NSURLIsAliasFileKey, $())) {
        if (is_alias.js) {
            let srcfurl = $.NSURL.URLByResolvingAliasFileAtURLOptionsError(
                dstfurl,
                $.NSURLBookmarkResolutionWithoutUI | $.NSURLBookmarkResolutionWithoutMounting,
                $()
            );
            let srcfpath = srcfurl.path.js;
            return srcfpath;
        } else {
            return `not alias file: ${dstfurl.path.js}`;
        }
    }
}


2020/10/19 05:18 Hiro__S への返信

ASOC (Hiro.S氏のコードに追記したもの)

use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

property OSX : current application
property nil : missing value

on run
	set dstfpath to OSX's NSString's stringWithString:(POSIX path of (choose file))
	set dstfurl to OSX's NSURL's fileURLWithPath:dstfpath
	
	set {res, is_alias} to dstfurl's ¬
		getResourceValue:(reference) forKey:(OSX's NSURLIsAliasFileKey) |error|:nil
	
	if res then
		if is_alias then
			set o1 to OSX's NSURLBookmarkResolutionWithoutUI
			set o2 to OSX's NSURLBookmarkResolutionWithoutMounting
			set opt to o1 + o2
			set srcfurl to OSX's NSURL's ¬
				URLByResolvingAliasFileAtURL:dstfurl options:opt |error|:nil
			if (srcfurl is nil) then return "original item not found: " & dstfpath as text # 追加した行.
			set srcfpath to srcfurl's |path| as text # リンク切れファイルの時はここでエラーが発生する.
			return srcfpath
		else
			return "not alias file: " & dstfpath as text
		end if
	end if
end run


「リンク切れエイリアスファイルを選んだ時にエラーが発生する箇所」の直前にコードを追加しました。choose file とかで選ばず、パスにフォルダやフォルダのエイリアスを指定して実行するとどうなるだろう……という検証が必要な気もしますが、このコードで当初の目的は果たせたように思います。

多謝。

2020/10/19 04:51 Hiro__S への返信

返信が遅くなりすいません。ASOC のほうで動作確認が取れました。ありがとうございます。

結果的に見当違いのコードを書いていたようで、その点とても恥ずかしく思います。そもそも Objective-C についてちゃんと学んでいなかったことも一因だと思うので、時代的には Swift なのでしょうが、 ASOC と長く親しむためにも学んでいこうと思います。


ところで。

後学のために教えていただきたいのですが、以下のようなメソッドのAppleScript的な戻り値は「out id」と「Nullable *」、つまりサンプルコードで書かれているところの {res, is_alias} というリスト型の値となるのでしょうか。

XCode ドキュメンテーションより
- (BOOL)getResourceValue:(out id  _Nullable *)value forKey:(NSURLResourceKey)key error:(out NSError * _Nullable *)error;

ずっと BOOL値 だけが返されるものだと思っていたので、はじめはよくわかりませんでした。けれど実際にサンプルコードを走らせると正しく(望んだどおり)実行できたこと、エイリアスのリンクが切れている時は is_alias が nil となっていることを見て、ああ、そういうことなのかなぁと思った次第です。確証はありませんが。

某Piyomaru氏のサイトでもこの {a,b} のようなリスト型の値を戻り値としているコードをよく見かけるのですが、そのたびにモヤモヤしていました。。

ご教授いただけると幸いです。

2020/10/24 03:10 Hiro__S への返信

訂正

誤: choose file ではシンボリックリンク自体を選択することができません。

正: choose file ではシンボリックリンク自体のパスを取得することができません。


ーーーーー


choose file とした際のシンボリックリンクの問題は解決できませんが、次のようにすればエイリアスファイルのみを選択することはできます。


choose file of type {"com.apple.alias-file"}


となると、対象のファイルがエイリアスであるか否かの判別が不要となるのでコードはかなり短くできますね。


use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

property OSX : current application
property nil : missing value

on run
    set dstfpath to choose file of type {"com.apple.alias-file"}
    set dstfurl to OSX's NSURL's fileURLWithPath:(dstfpath's POSIX path)

    set {srcfurl, err} to OSX's NSURL's ¬
        URLByResolvingAliasFileAtURL:dstfurl options:0 |error|:(reference)

    if err is nil then
        set srcfpath to srcfurl's |path| as text
        return srcfpath
    else
        return err's localizedDescription as text
    end if
end run


ーーーーー


あと、エイリアスのオリジナルを得るにはこんな方法もあります。


URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:

https://developer.apple.com/documentation/foundation/nsurl/1572035-urlbyresolvingbookmarkdata


具体的なコードは省略しますが、URLByResolvingAliasFileAtURL:options:error: より詳細な情報を取得できます。bookmarkDataIsStale がミソ。


まあ、Finder からオリジナルを求める方法の代わりとしては、URLByResolvingAliasFileAtURL:options:error: で十分でしょうけど、一応、ご参考まで。


2020/10/19 06:41 light289 への返信

戻り値はリスト型になります。それが AppleScriptObjC の特徴でしょうね。さらに次のようにするとエラー内容も捕獲できます。(リストの要素数が一つ増えます)


use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

property OSX : current application
property nil : missing value

on run
    set dstfpath to OSX's NSString's stringWithString:(POSIX path of (choose file))
    set dstfurl to OSX's NSURL's fileURLWithPath:dstfpath

    set {res, is_alias, err} to dstfurl's ¬
        getResourceValue:(reference) forKey:(OSX's NSURLIsAliasFileKey) |error|:(reference)

    log res
    log is_alias as boolean
    log err
end run


ちなみに冒頭のコードも次のようにすればエラーを捕獲できます。(reference) がミソ。


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

set aFile to choose file

set {r, err} to current application's NSFileManager's defaultManager()'s ¬
    destinationOfSymbolicLinkAtPath:(POSIX path of aFile) |error|:(reference)

log r
log err

if err is missing value then
    r
else
    err's localizedDescription as text
end if


2020/10/19 07:11 light289 への返信

エイリアスのオリジナルを得るコードはこんな感じでも良いかもしれません。オリジナルが不明のエイリアス、シンボリックリンク、ディレクトリ、通常ファイルを Finder で選択してスクリプトを実行してみてください。


use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

property OSX : current application
property nil : missing value

on run
    tell application "Finder"
        set dstfobj to item 1 of (selection as list)
        set dstfurl to OSX's NSURL's URLWithString:(URL of dstfobj)
    end tell
    set attrs to dstfurl's resourceValuesForKeys:{"NSURLIsAliasFileKey"} |error|:nil
    if (attrs's objectForKey:"NSURLIsAliasFileKey") as boolean then
        set {srcfurl, err} to OSX's NSURL's URLByResolvingAliasFileAtURL:dstfurl options:0 |error|:(reference)
        if err is nil then
            set srcfpath to srcfurl's |path| as text
            return srcfpath
        else
            #
            # could not resolve alias
            #
            return err's localizedDescription as text
        end if
    else
        return "not alias or symbolic link: " & dstfurl's |path| as text
    end if
end run


ところで、choose file の戻り値には注意が必要です。エイリアスの場合は問題ありませんが、シンボリックリンクを選択するとオリジナルのパスが返されます。つまり choose file ではシンボリックリンク自体を選択することができません。


一方 Finder でファイルを選択した場合はシンボリックリンクでも大丈夫。ただし、... as alias とするとオリジナルになるので要注意。(結構面倒です...)



このスレッドはシステム、またはAppleコミュニティチームによってロックされました。 問題解決の参考になる情報であれば、どの投稿にでも投票いただけます。またコミュニティで他の回答を検索することもできます。

AppleScriptObjC でエイリアスのオリジナルを得たい

Apple サポートコミュニティへようこそ
Apple ユーザ同士でお使いの製品について助け合うフォーラムです。Apple Account を使ってご参加ください。