Retrieve the original path from a broken alias

Having a list of aliases, I need to detect which one has a broken target path and I need also to get this path.


I've tested Applescript:


tell application "Finder"

try

set reference_doc to original item of (aliaspath_string as POSIX file as alias)

set originalpath_string to POSIX path of (reference_doc as alias)

on error

return empty

end try

end tell


but applescript is slow, it try always to mount remote servers and don't return the broken path. Any suggestion? Maybe a "do shell script" command line expression?


Thanks


Paul

Macbook Pro, Mac OS X (10.5.4)

Posted on Jun 10, 2014 5:40 AM

Reply
34 replies

Jun 11, 2014 5:05 PM in response to pauclaude

Hello


Under 10.9, I guess the said command line utility at [ https://github.com/rptb1/aliasPath ] should work although I think the target resolution method in that programme is problematic. (It should have first tried to resolve the alias and only when it failed it should try to retrieve target path from alias record in the alias file because target path in alias record may be stale and need to be updated which resolving alias does.)


Anyway, you may try the following rubycocoa script wrapped in applescript. It will let you choose alias file(s) and then return text whose line consists of [alias file path] => [target path] for those aliases that can NOT be resolved but whose target path is retrieved. If target of alias resides on a volume not mounted, alias resolution is not performed and the target is retrieved from alias record in alias file.


You may filter the result by using return code of resolve_alias() method in rubycocoa. E.g., if you want to list every alias whose target is obtained by any means, use condition rc < 2 which is currently commented out.


Script is partially tested under 10.6.8. (NSURL's resource key NSURLPathKey is introduced in 10.8 and I cannot test it.)


Good luck,

H



_main()
on _main()
    set ff to choose file with prompt "Choose alias file(s) to resolve." with multiple selections allowed
    set args to ""
    repeat with f in ff
        set args to args & space & f's POSIX path's quoted form
    end repeat

    do shell script "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w <<'EOF' - " & args & " 
# 
#     This script will -
#         1) try to obtain bookmark data from alias file and if failed then return nothing;
#         2) try to resolve alias (bookmark data) and and if successful then return the target path;
#         3) try to retrieve target path from bookmark data and if successful then return the target path;
#         
#     For 1), use NSURL +bookmarkDataWithContentsOfURL:error:
#     For 2), use NSURL +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:
#     For 3), use NSURL +resourceValuesForKeys:fromBookmarkData:
#     
#     These methods are available under 10.6 or later. 
#     However, 3) uses NSURLPathKey, which is only available under 10.8 or later.
#
require 'osx/cocoa'
include OSX

begin
    URLKEY = NSURLPathKey    # 10.8 or later
rescue
    URLKEY = NSURLNameKey    # 10.6 or later (fallback)
end

def resolve_alias(f, opts = {})
    #     
    #     string f : POSIX path of alias file
    #    hash opts : {:quiet => boolean}
    #         :quiet => true to suppress error message, false otherwise; default = false
    #     return array : [rc, f1]
    #         rc = return code 
    #             0 = alias is resolved
    #             1 = alias is not resolved but target is retrieved
    #             2 = target is not retrieved
    #             3 = bookmark data is not obtained (e.g., f is not alias)
    #         f1 = POSIX path of target file or nil if failed
    # 
    {:quiet => false}.merge!(opts)
    url = NSURL.fileURLWithPath(f)
    err = OCObject.new
    bmrk = NSURL.objc_send(
        :bookmarkDataWithContentsOfURL, url, 
        :error, err)
    unless bmrk
        $stderr.puts %[#\\t%s\\t=>\\tError: failed to get bookmark data - %s] % [f, err] unless opts[:quiet]
        return [3, nil]
    end

    stale = ObjcPtr.new(:char)    # BOOL*
    err = OCObject.new
    url1 = NSURL.objc_send(
        :URLByResolvingBookmarkData, bmrk,
        :options, NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
        :relativeToURL, nil,
        :bookmarkDataIsStale, stale,
        :error, err)
    unless url1
        $stderr.puts %[#\\t%s\\t=>\\tWarning: failed to resolve alias - %s] % [f, err] unless opts[:quiet]
        dict = NSURL.objc_send(
            :resourceValuesForKeys, [URLKEY],    # NSURLPathKey is available under 10.8+
            :fromBookmarkData, bmrk)
        path2 = dict[NSURLNameKey]
        unless path2
            $stderr.puts %[#\\t%s\\t=>\\tError: failed to get target path - %s] % [f, err] unless opts[:quiet]
            return [2, nil]
        else
            return [1, path2]
        end
    end
    if stale.bool != 0
        $stderr.puts %[#\\t%s\\t=>\\tWarning: stale alias.] % f unless opts[:quiet]
    end
    return [0, url1.path]
end

ARGV.each do |f|
    rc, f1 = resolve_alias(f, :quiet => true)
#     puts %[%s\\t=>\\t%s] % [f, f1] if rc < 2    # shows all aliases that is resolved or whose target is retrieved.
    puts %[%s\\t=>\\t%s] % [f, f1] if rc == 1    # shows aliases that is not resolved but whose target is retrieved.
end
EOF"
end _main

Jun 11, 2014 7:56 PM in response to Hiroto

Oops. Of course, this line:


path2 = dict[NSURLNameKey]


should have been:


path2 = dict[URLKEY]


Corrected script is as follows. Sorry for confusions.


All the best,

Hiroto



_main()
on _main()
    set ff to choose file with prompt "Choose alias file(s) to resolve." with multiple selections allowed
    set args to ""
    repeat with f in ff
        set args to args & space & f's POSIX path's quoted form
    end repeat
    
    do shell script "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w <<'EOF' - " & args & " 
# 
#     This script will -
#         1) try to obtain bookmark data from alias file and if failed then return nothing;
#         2) try to resolve alias (bookmark data) and and if successful then return the target path;
#         3) try to retrieve target path from bookmark data and if successful then return the target path;
#         
#     For 1), use NSURL +bookmarkDataWithContentsOfURL:error:
#     For 2), use NSURL +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:
#     For 3), use NSURL +resourceValuesForKeys:fromBookmarkData:
#     
#     These methods are available under 10.6 or later. 
#     However, 3) uses NSURLPathKey, which is only available under 10.8 or later.
#
require 'osx/cocoa'
include OSX

begin
    URLKEY = NSURLPathKey    # 10.8 or later
rescue
    URLKEY = NSURLNameKey    # 10.6 or later (fallback)
end

def resolve_alias(f, opts = {})
    #     
    #     string f : POSIX path of alias file
    #    hash opts : {:quiet => boolean}
    #         :quiet => true to suppress error message, false otherwise; default = false
    #     return array : [rc, f1]
    #         rc = return code 
    #             0 = alias is resolved
    #             1 = alias is not resolved but target is retrieved
    #             2 = target is not retrieved
    #             3 = bookmark data is not obtained (e.g., f is not alias)
    #         f1 = POSIX path of target file or nil if failed
    # 
    {:quiet => false}.merge!(opts)
    url = NSURL.fileURLWithPath(f)
    err = OCObject.new
    bmrk = NSURL.objc_send(
        :bookmarkDataWithContentsOfURL, url, 
        :error, err)
    unless bmrk
        $stderr.puts %[#\\t%s\\t=>\\tError: failed to get bookmark data - %s] % [f, err] unless opts[:quiet]
        return [3, nil]
    end
    
    stale = ObjcPtr.new(:char)    # BOOL*
    err = OCObject.new
    url1 = NSURL.objc_send(
        :URLByResolvingBookmarkData, bmrk,
        :options, NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
        :relativeToURL, nil,
        :bookmarkDataIsStale, stale,
        :error, err)
    unless url1
        $stderr.puts %[#\\t%s\\t=>\\tWarning: failed to resolve alias - %s] % [f, err] unless opts[:quiet]
        dict = NSURL.objc_send(
            :resourceValuesForKeys, [URLKEY],    # NSURLPathKey is available under 10.8+
            :fromBookmarkData, bmrk)
        path2 = dict[URLKEY]
        unless path2
            $stderr.puts %[#\\t%s\\t=>\\tError: failed to get target path - %s] % [f, err] unless opts[:quiet]
            return [2, nil]
        else
            return [1, path2]
        end
    end
    if stale.bool != 0
        $stderr.puts %[#\\t%s\\t=>\\tWarning: stale alias.] % f unless opts[:quiet]
    end
    return [0, url1.path]
end

ARGV.each do |f|
    rc, f1 = resolve_alias(f, :quiet => true)
#     puts %[%s\\t=>\\t%s] % [f, f1] if rc < 2    # shows all aliases that is resolved or whose target is retrieved.
    puts %[%s\\t=>\\t%s] % [f, f1] if rc == 1    # shows aliases that is not resolved but whose target is retrieved.
end
EOF"
end _main

Jun 12, 2014 2:46 AM in response to Hiroto

Oops^2. And of course, this line:


    {:quiet => false}.merge!(opts)


should have been:


    opts = {:quiet => false}.merge(opts)



Apparently I needed more coffee. 😝

Corrected script is as follows although previous code happens to work as intended in this specific case.


Cheers,

Hiroto



_main()
on _main()
    set ff to choose file with prompt "Choose alias file(s) to resolve." with multiple selections allowed
    set args to ""
    repeat with f in ff
        set args to args & space & f's POSIX path's quoted form
    end repeat
    
    do shell script "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w <<'EOF' - " & args & " 
# 
#     This script will -
#         1) try to obtain bookmark data from alias file and if failed then return nothing;
#         2) try to resolve alias (bookmark data) and and if successful then return the target path;
#         3) try to retrieve target path from bookmark data and if successful then return the target path;
#         
#     For 1), use NSURL +bookmarkDataWithContentsOfURL:error:
#     For 2), use NSURL +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:
#     For 3), use NSURL +resourceValuesForKeys:fromBookmarkData:
#     
#     These methods are available under 10.6 or later. 
#     However, 3) uses NSURLPathKey, which is only available under 10.8 or later.
#
require 'osx/cocoa'
include OSX

begin
    URLKEY = NSURLPathKey    # 10.8 or later
rescue
    URLKEY = NSURLNameKey    # 10.6 or later (fallback)
end

def resolve_alias(f, opts = {})
    #     
    #     string f : POSIX path of alias file
    #    hash opts : {:quiet => boolean}
    #         :quiet => true to suppress error message, false otherwise; default = false
    #     return array : [rc, f1]
    #         rc = return code 
    #             0 = alias is resolved
    #             1 = alias is not resolved but target is retrieved
    #             2 = target is not retrieved
    #             3 = bookmark data is not obtained (e.g., f is not alias)
    #         f1 = POSIX path of target file or nil if failed
    # 
    opts = {:quiet => false}.merge(opts)
    url = NSURL.fileURLWithPath(f)
    err = OCObject.new
    bmrk = NSURL.objc_send(
        :bookmarkDataWithContentsOfURL, url, 
        :error, err)
    unless bmrk
        $stderr.puts %[#\\t%s\\t=>\\tError: failed to get bookmark data - %s] % [f, err] unless opts[:quiet]
        return [3, nil]
    end
    
    stale = ObjcPtr.new(:char)    # BOOL*
    err = OCObject.new
    url1 = NSURL.objc_send(
        :URLByResolvingBookmarkData, bmrk,
        :options, NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
        :relativeToURL, nil,
        :bookmarkDataIsStale, stale,
        :error, err)
    unless url1
        $stderr.puts %[#\\t%s\\t=>\\tWarning: failed to resolve alias - %s] % [f, err] unless opts[:quiet]
        dict = NSURL.objc_send(
            :resourceValuesForKeys, [URLKEY],    # NSURLPathKey is available under 10.8+
            :fromBookmarkData, bmrk)
        path2 = dict[URLKEY]
        unless path2
            $stderr.puts %[#\\t%s\\t=>\\tError: failed to get target path - %s] % [f, err] unless opts[:quiet]
            return [2, nil]
        else
            return [1, path2]
        end
    end
    if stale.bool != 0
        $stderr.puts %[#\\t%s\\t=>\\tWarning: stale alias.] % f unless opts[:quiet]
    end
    return [0, url1.path]
end

ARGV.each do |f|
    rc, f1 = resolve_alias(f, :quiet => true)
#     puts %[%s\\t=>\\t%s] % [f, f1] if rc < 2    # shows all aliases that is resolved or whose target is retrieved.
    puts %[%s\\t=>\\t%s] % [f, f1] if rc == 1    # shows aliases that is not resolved but whose target is retrieved.
end
EOF"
end _main

Jun 12, 2014 3:39 AM in response to Hiroto

Hi Hiroto,


thanks, this script seems to work very well in Applescript, but when I use it on my livecode application (with a "do" statement) it gave me this error

-:23: syntax error, unexpected $end, expecting ')'


copying back the code in Applescript give the same error

sh: -c: line 0: syntax error near unexpected token `)'


the two scripts seems identical, except that the original linefeed seem changed, by livecode, in carriage return (13), and this seem to produce the error. I've tried to internally replace cr with linefeed, but the error persists. Any suggestion? How can I re-format your script to avoid this error?

Jun 12, 2014 5:46 AM in response to pauclaude

Hello


I don't know livecode and cannot help on it. But the lines in the ruby code must be terminated by LF (U+000A LINEFEED).


I'd guess LF is replaced by CR (U+000D CARRIAGE RETURN) inadvertently in some application's copy-paste operation. It happens. In such case, I copy the text into TextWrangler set to use (auto-translation to) LF line ending and copy the cleaned text back to the source.


And in case, here's the original ruby code not wrapped in applescript if it helps.


Good luck,

H



#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w
# 
#     This script will -
#         1) try to obtain bookmark data from alias file and if failed then return nothing;
#         2) try to resolve alias (bookmark data) and and if successful then return the target path;
#         3) try to retrieve target path from bookmark data and if successful then return the target path;
#         
#     For 1), use NSURL +bookmarkDataWithContentsOfURL:error:
#     For 2), use NSURL +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:
#     For 3), use NSURL +resourceValuesForKeys:fromBookmarkData:
#     
#     These methods are available under 10.6 or later. 
#     However, 3) uses NSURLPathKey, which is only available under 10.8 or later.
#
require 'osx/cocoa'
include OSX

begin
    URLKEY = NSURLPathKey    # 10.8 or later
rescue
    URLKEY = NSURLNameKey    # 10.6 or later (fallback)
end

def resolve_alias(f, opts = {})
    #     
    #     string f : POSIX path of alias file
    #    hash opts : {:quiet => boolean}
    #         :quiet => true to suppress error message, false otherwise; default = false
    #     return array : [rc, f1]
    #         rc = return code 
    #             0 = alias is resolved
    #             1 = alias is not resolved but target is retrieved
    #             2 = target is not retrieved
    #             3 = bookmark data is not obtained (e.g., f is not alias)
    #         f1 = POSIX path of target file or nil if failed
    # 
    opts = {:quiet => false}.merge(opts)
    url = NSURL.fileURLWithPath(f)
    err = OCObject.new
    bmrk = NSURL.objc_send(
        :bookmarkDataWithContentsOfURL, url, 
        :error, err)
    unless bmrk
        $stderr.puts %[#\t%s\t=>\tError: failed to get bookmark data - %s] % [f, err] unless opts[:quiet]
        return [3, nil]
    end
    
    stale = ObjcPtr.new(:char)    # BOOL*
    err = OCObject.new
    url1 = NSURL.objc_send(
        :URLByResolvingBookmarkData, bmrk,
        :options, NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
        :relativeToURL, nil,
        :bookmarkDataIsStale, stale,
        :error, err)
    unless url1
        $stderr.puts %[#\t%s\t=>\tWarning: failed to resolve alias - %s] % [f, err] unless opts[:quiet]
        dict = NSURL.objc_send(
            :resourceValuesForKeys, [URLKEY],
            :fromBookmarkData, bmrk)
        path2 = dict[URLKEY]
        unless path2
            $stderr.puts %[#\t%s\t=>\tError: failed to get target path - %s] % [f, err] unless opts[:quiet]
            return [2, nil]
        else
            return [1, path2]
        end
    end
    if stale.bool != 0
        $stderr.puts %[#\t%s\t=>\tWarning: stale alias.] % f unless opts[:quiet]
    end
    return [0, url1.path]
end

ARGV.each do |f|
    rc, f1 = resolve_alias(f, :quiet => true)
#     puts %[%s\t=>\t%s] % [f, f1] if rc < 2    # shows all aliases that is resolved or whose target is retrieved.
    puts %[%s\t=>\t%s] % [f, f1] if rc == 1    # shows aliases that is not resolved but whose target is retrieved.
end

Jun 12, 2014 8:27 AM in response to Hiroto

Hi Hiroto,


I've used your ruby code without applescript: by passing a list of aliases, one by one, each path enclosed in quotes, escaping eventual quotes in the path, the result is perfect, but it runs very slow.


By passing the same alias list, with each path quoted and separed by spaces, it seems very fast, but it always stop with this error (and without echoing the wrong path):


User uploaded file

In the list there are path that contains spaces, single quotes, parentheses, etc.


Maybe it would be better to pass a list separed by lines and not by spaces?

Jun 12, 2014 11:59 AM in response to pauclaude

Hello Paul,


Probably you're quoting the path wrongly. Quoting in shell is fairly straightforward. For strong quoting (in bash) that does not allow interpolation in quoted string, you may put everything in a pair of single quotes (apostrophe ') except for single quote itself which you can quote by putting backslash before it. Note that backslash has no special meaning in string quoted by single quotes and a single quote may not occur between single quotes, even when preceded by a backslash.


E.g., Given string = :


ab "cd" e'f


single quoted string = :


'ab "cd" e'\''f'


which consists of three quoted strings:


'ab "cd"'
\'
'f'



By the way, AppleScript's "quoted form" property of string yields this sort of quoted string, that I used in my previous AppleScript wrapper scripts. If you're using ruby script directly in shell without AppleScript wrapper, you'd need to do this quoting by yourself.


Hope this may help,

Hiroto

Jun 13, 2014 6:11 AM in response to Hiroto

Dear Hiroto,


I've made a test. I've passed directly this path string to your Applescript wrapped tool:


User uploaded file

the result has been perfect:

"/Users/paolo/Desktop/untitled folder/ab \"cd\" e'f=>/Users/paolo/Desktop/iSerial Reader.app
/Users/paolo/Desktop/untitled folder/iSerial \"Reader\" 2=>/Users/paolo/Desktop/iSerial Reader.app
/Users/paolo/Desktop/untitled folder/iSerial Reader=>/Users/paolo/Desktop/iSerial Reader.app"


then I've passed exactly the same string as argoument to the shell call to your tool:


User uploaded file

and the result has been:


User uploaded file

please consider that the same shell call works perfectly if there are non strange characters in the paths.


I don't know how ruby works: it' a possible solution to change the tool to pass the arguments as a return delimited (or another unique char) list, so that it can interpret as path all the characters found in each line?

Jun 14, 2014 8:20 AM in response to pauclaude

Hello Paul,


Sorry for late reply. It took me some time to try LiveCode Community myself. It is an open-source HyperCard of present day. Thanks for letting me know about this interesting software. 😉


Before writing about LiveCode specific issue, I'd like to post another version of script which will process every alias file in given directory tree as described in your last message. The first is Ruby script and the second is its AppleScript wrapper. Both scripts will accept a folder where target directory tree is rooted at and will process every alias file in the tree (except for those in package file) and print [unresolved alias path] => [retrieved target path].



1) Ruby script



#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w
# 
#     This script will -
#         0) scan every alias file under given directory tree (except for those in package file); and
#         1) try to obtain bookmark data from alias file and if failed then return nothing;
#         2) try to resolve alias (bookmark data) and if successful then return the target path;
#         3) try to retrieve target path from bookmark data and if successful then return the target path;
#         
#     For 1), use NSURL +bookmarkDataWithContentsOfURL:error:
#     For 2), use NSURL +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:
#     For 3), use NSURL +resourceValuesForKeys:fromBookmarkData:
#     
#     These methods are available under 10.6 or later. 
#     However, 3) uses NSURLPathKey, which is only available under 10.8 or later.
#
require 'osx/cocoa'
include OSX

begin
    URLKEY = NSURLPathKey    # 10.8 or later
rescue
    URLKEY = NSURLNameKey    # 10.6 or later (fallback)
end

def resolve_alias(f, opts = {})
    #     
    #     string f : POSIX path of alias file
    #    hash opts : {:quiet => boolean}
    #         :quiet => true to suppress error message, false otherwise; default = false
    #     return array : [rc, f1]
    #         rc = return code 
    #             0 = alias is resolved
    #             1 = alias is not resolved but target is retrieved
    #             2 = target is not retrieved
    #             3 = bookmark data is not obtained (e.g., f is not alias)
    #         f1 = POSIX path of target file or nil if failed
    # 
    opts = {:quiet => false}.merge(opts)
    url = NSURL.fileURLWithPath(f)
    err = OCObject.new
    bmrk = NSURL.objc_send(
        :bookmarkDataWithContentsOfURL, url, 
        :error, err)
    unless bmrk
        $stderr.puts %[#\t%s\t=>\tError: failed to get bookmark data - %s] % [f, err] unless opts[:quiet]
        return [3, nil]
    end
    
    stale = ObjcPtr.new(:char)    # BOOL*
    err = OCObject.new
    url1 = NSURL.objc_send(
        :URLByResolvingBookmarkData, bmrk,
        :options, NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
        :relativeToURL, nil,
        :bookmarkDataIsStale, stale,
        :error, err)
    unless url1
        $stderr.puts %[#\t%s\t=>\tWarning: failed to resolve alias - %s] % [f, err] unless opts[:quiet]
        dict = NSURL.objc_send(
            :resourceValuesForKeys, [URLKEY],
            :fromBookmarkData, bmrk)
        path2 = dict[URLKEY]
        unless path2
            $stderr.puts %[#\t%s\t=>\tError: failed to get target path - %s] % [f, err] unless opts[:quiet]
            return [2, nil]
        else
            return [1, path2]
        end
    end
    if stale.bool != 0
        $stderr.puts %[#\t%s\t=>\tWarning: stale alias.] % f unless opts[:quiet]
    end
    return [0, url1.path]
end

raise ArgumentError, %Q[Usage: #{File.basename($0)} directory] unless ARGV.length == 1
dir, = ARGV.map { |a| a == '/' ? a : a.chomp('/')}
denum = NSFileManager.defaultManager.enumeratorAtPath(dir)
wks = NSWorkspace.sharedWorkspace

aa = []        # list of aliases under dir
while (f = denum.nextObject) != nil do
    file = dir + '/' + f.to_s
    denum.skipDescendants if wks.isFilePackageAtPath(file)        # ignore package contents
    if denum.fileAttributes.objectForKey(NSFileType) == NSFileTypeRegular
        aa << file if wks.objc_send(:typeOfFile, file, :error, nil) == 'com.apple.alias-file'
    end
end

aa.each do |f|
    rc, f1 = resolve_alias(f, :quiet => true)
#     puts %[%s\t=>\t%s] % [f, f1] if rc < 2    # shows all aliases that is resolved or whose target is retrieved.
    puts %[%s\t=>\t%s] % [f, f1] if rc == 1    # shows aliases that is not resolved but whose target is retrieved.
end



2) AppleScript wrapper


_main()
on _main()
    set d to (choose folder with prompt "Choose folder where target directory tree is rooted at.")'s POSIX path
    do shell script "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w <<'EOF' - " & d's quoted form & " 
# 
#     This script will -
#         0) scan every alias file under given directory tree (except for those in package file); and
#         1) try to obtain bookmark data from alias file and if failed then return nothing;
#         2) try to resolve alias (bookmark data) and if successful then return the target path;
#         3) try to retrieve target path from bookmark data and if successful then return the target path;
#         
#     For 1), use NSURL +bookmarkDataWithContentsOfURL:error:
#     For 2), use NSURL +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:
#     For 3), use NSURL +resourceValuesForKeys:fromBookmarkData:
#     
#     These methods are available under 10.6 or later. 
#     However, 3) uses NSURLPathKey, which is only available under 10.8 or later.
#
require 'osx/cocoa'
include OSX

begin
    URLKEY = NSURLPathKey    # 10.8 or later
rescue
    URLKEY = NSURLNameKey    # 10.6 or later (fallback)
end

def resolve_alias(f, opts = {})
    #     
    #     string f : POSIX path of alias file
    #    hash opts : {:quiet => boolean}
    #         :quiet => true to suppress error message, false otherwise; default = false
    #     return array : [rc, f1]
    #         rc = return code 
    #             0 = alias is resolved
    #             1 = alias is not resolved but target is retrieved
    #             2 = target is not retrieved
    #             3 = bookmark data is not obtained (e.g., f is not alias)
    #         f1 = POSIX path of target file or nil if failed
    # 
    opts = {:quiet => false}.merge(opts)
    url = NSURL.fileURLWithPath(f)
    err = OCObject.new
    bmrk = NSURL.objc_send(
        :bookmarkDataWithContentsOfURL, url, 
        :error, err)
    unless bmrk
        $stderr.puts %[#\\t%s\\t=>\\tError: failed to get bookmark data - %s] % [f, err] unless opts[:quiet]
        return [3, nil]
    end
    
    stale = ObjcPtr.new(:char)    # BOOL*
    err = OCObject.new
    url1 = NSURL.objc_send(
        :URLByResolvingBookmarkData, bmrk,
        :options, NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
        :relativeToURL, nil,
        :bookmarkDataIsStale, stale,
        :error, err)
    unless url1
        $stderr.puts %[#\\t%s\\t=>\\tWarning: failed to resolve alias - %s] % [f, err] unless opts[:quiet]
        dict = NSURL.objc_send(
            :resourceValuesForKeys, [URLKEY],
            :fromBookmarkData, bmrk)
        path2 = dict[URLKEY]
        unless path2
            $stderr.puts %[#\\t%s\\t=>\\tError: failed to get target path - %s] % [f, err] unless opts[:quiet]
            return [2, nil]
        else
            return [1, path2]
        end
    end
    if stale.bool != 0
        $stderr.puts %[#\\t%s\\t=>\\tWarning: stale alias.] % f unless opts[:quiet]
    end
    return [0, url1.path]
end

raise ArgumentError, %Q[Usage: #{File.basename($0)} directory] unless ARGV.length == 1
dir, = ARGV.map { |a| a == '/' ? a : a.chomp('/')}
denum = NSFileManager.defaultManager.enumeratorAtPath(dir)
wks = NSWorkspace.sharedWorkspace

aa = []        # list of aliases under dir
while (f = denum.nextObject) != nil do
    file = dir + '/' + f.to_s
    denum.skipDescendants if wks.isFilePackageAtPath(file)        # ignore package contents
    if denum.fileAttributes.objectForKey(NSFileType) == NSFileTypeRegular
        aa << file if wks.objc_send(:typeOfFile, file, :error, nil) == 'com.apple.alias-file'
    end
end

aa.each do |f|
    rc, f1 = resolve_alias(f, :quiet => true)
#     puts %[%s\\t=>\\t%s] % [f, f1] if rc < 2    # shows all aliases that is resolved or whose target is retrieved.
    puts %[%s\\t=>\\t%s] % [f, f1] if rc == 1    # shows aliases that is not resolved but whose target is retrieved.
end
EOF"
end _main



I'm going to write about LiveCode specific issues in separate reply.


Regards,

H

Jun 14, 2014 3:05 PM in response to pauclaude

Hello Paul,


You're quite welcome. Glad to know it works for you. 🙂



The rest is what I found in my brief experiences in LiveCode, specifically LiveCode Community 6.6.2 for Mac downloaded from [ http://downloads.livecode.com/livecode/ ].


As far as I can tell, its command:


do s as "AppleScript"


is seriously flawed in that it arbitrarily changes every line ending, whether quoted or not, in s to CR before passing it to AppleScript interpreter. Because of this behaviour, multi-line shell script passed to "do shell script" command in AppleScript is doomed to fail. It is bug or design flaw of LiveCode's do command. (AppleScript Editor replaces line ending token with CR in its decompiled source but it is limited to line ending as language token, i.e., non-quoted line ending, and it justly preserves things in quoted string as is.)


So we avoid the do command to run AppleScript script. Fortnately we can employ osascript(1) in its shell() function to run AppleScript script stored in external file. I'll show an example later.


As for shell() function and the said error, it seems due to bare quote (") in the argument string given to shell() function. I said "it seems" because I cannot reproduce the said error exactly, as shown in the following screenshot (wheree background source code is LiveCode script).


User uploaded file



Note that backslash + quote (\") in string literal causes compilation error in LiveCode. Quoting rules for AppleScript's "do shell script" command and LiveCode shell() function are different and you cannot simply use the same string as their arguments.


In order to avoid quoting problem in passing source string to shell() function, I'd create a dedicated field for shell script and get source string from there. Like this (where background source code is LiveCode script).


User uploaded file



By this scheme, we may safely use osascript to run an AppleScript script saved as external file. Like this (where background source code is AppleScript script).


User uploaded file



Note that stderr output in Ruby code does not show in final result because "do shell script" command swallows it in this case.


Hope this may help.


Best wishes,

Hiroto

Jun 16, 2014 1:39 AM in response to Hiroto

Hi Hiroto,


I know that livecode may run a multi lines shell by using a field (or better an object's custom property), but it seems that, in some moment of the process, it turns linefeed into carriage returns.


Very good the idea of processing all externally by using your last example. There are problems, anyway, in the result:


2014-06-16 10:36:44.854 osascript[87424:507] Error loading /Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX: dlopen(/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX, 262): no suitable image found. Did find:

/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX: no matching architecture in universal wrapper

osascript: OpenScripting.framework - scripting addition "/Library/ScriptingAdditions/24U Appearance OSAX.osax" declares no loadable handlers.

2014-06-16 10:36:44.861 osascript[87424:507] Error loading /Library/ScriptingAdditions/QXPScriptingAdditions.osax/Contents/MacOS/QXPScript ingAdditions: dlopen(/Library/ScriptingAdditions/QXPScriptingAdditions.osax/Contents/MacOS/QX PScriptingAdditions, 262): no suitable image found. Did find:

/Library/ScriptingAdditions/QXPScriptingAdditions.osax/Contents/MacOS/QXPScript ingAdditions: no matching architecture in universal wrapper

osascript: OpenScripting.framework - scripting addition "/Library/ScriptingAdditions/QXPScriptingAdditions.osax" declares no loadable handlers.


Regards


Paul

Jun 16, 2014 5:00 AM in response to pauclaude

Hello Paul,


Roughly speaking, what those errors mean are that the two OSAXen:


/Library/ScriptingAdditions/24U Appearance OSAX.osax

/Library/ScriptingAdditions/QXPScriptingAdditions.osax


contain binary only for 32-bit architecture and cannot be loaded in application run in 64-bit mode.


You may suppress those (warning) errors thrown by osascript by redirecting its stderr to /dev/null. Like this.


/usr/bin/oasscript ~/desktop/test.applescript 2>/dev/null


But preferred solutions would be either to -


1) remove those 32-bit only OSAXen from /Libirary/ScriptingAdditions if you can or upgrade them to 64-bit capable versions; or


2) execute osascript in 32-bit mode. Like this


arch -i386 /usr/bin/osascript ~/desktop/test.applescript


cf.

https://developer.apple.com/library/mac/releasenotes/AppleScript/RN-AppleScript

https://developer.apple.com/library/mac/releasenotes/AppleScript/RN-AppleScript/ RN-AppleScript.pdf


AppleScript Release Notes

> 10.6 Changes

> Compatibility Notes

> 64-bit



All the best,

H

This thread has been closed by the system or the community team. You may vote for any posts you find helpful, or search the Community for additional answers.

Retrieve the original path from a broken alias

Welcome to Apple Support Community
A forum where Apple customers help each other with their products. Get started with your Apple Account.