Matty725

Q: Use AppleScript to recursively fix aliases

I found the script posted here:

 

Re: Can AppleScript run a FixAlias script?

 

by @ to be useful but it doesn't appear to iterate all subfolders beneath the selected folder.

 

Does anyone have a modified version of this script or a similar script that replaces a drive name in the "Origin" property of aliases with that of another specified name?

iPad, iOS 7.1

Posted on Sep 18, 2015 12:07 AM

Close

Q: Use AppleScript to recursively fix aliases

  • All replies
  • Helpful answers

  • by Hiroto,Helpful

    Hiroto Hiroto Sep 18, 2015 7:42 PM in response to Matty725
    Level 5 (7,286 points)
    Sep 18, 2015 7:42 PM in response to Matty725

    Hello

     

    It is an old script written under OS 9. Good to know it still works.

     

    Here's minimally revised version to process aliases in directory tree rooted at the chosen folder. I modified the script so that it now flags originally broken aliases and failed to redirect aliases by label colour instead of moving them to predefined folders in order to avoid non-essential complication to solve name conflicts of alias files retrieved from different directories.

     

    Note that this script may be very slow if the target directory tree is large due to the slowness of Finder. There's far faster modern way using cocoa methods to do the same thing. If this is too slow, post back and I'll be crafting completely new script.

     

    Regards,

    H

     

     

    -- SCRIPT
    (*
        redirect alias files
        v0.21
        
        This script will redirect alias files in directory tree rooted at the specified folder so that each original item is 
        reassigend by replacing its partial leading path given in property 'path1' with the partial path given in property 'path2'.
        
        (Meanwhile any broken aliases of which original items are not found will be labeled by GRAY and
        any aliases of which assumed reassigned original items are not found will be labeled by RED.
        And aliases of which original items' path do not start with path given in property 'path1' will remain as they are.)
        
        Usage:
            Change properties path1 and path2 to suit your needs and run the script, and it will
             let you choose a folder where alias files to be redirected reside.
            
            E.g.
            Provided original folders are:
                (1) HD:Users:[user_name]:Documents:Pictures:
                (2) HD:Users:[user_name]:Documents:Pictures:Aliases:    -- (this is irrelevant here)
            and copied folders are:
                (1a) ExtDisk:Pictures:
                (2a) ExtDisk:Pictures:Aliases:  -- (this is assumed to contain 'wrong' alias files pointing to items in (1))
    
            Then set:
                property path1 : "HD:Users:[user_name]:Documents:Pictures:"
                property path2 : "ExtDisk:Pictures:"
            and in choose folder prompt, choose folder:
                "ExtDisk:Pictures:Aliases:"
    
    
        * originally published as v0.2 in the thread:
        
            Can AppleScript run a FixAlias script?
            https://discussions.apple.com/thread/683171
    *)
    
    main()
    on main()
        script o
            property path1 : "TEST1:test folder:" -- searching leading partial path of original item of alias file.
            property path2 : "TEST2:test folder:" -- replacing leading partial path of original item of alias file.
            property aa : {} -- list of original alias files.
            property aa1 : {} -- list of broken alias files of which original items are not found.
            property aa2 : {} -- list of failed alias files of which assumed reassigned original items are not found.
            property aa3 : {} -- list of reassigend alias files.
            property nn : {} -- list of name of original alias files.
            property xx : {} -- list of original items of aa
            property yy : {} -- list of reassigned orignal items of aa
            property broken_label : 7 -- label index for aa1.
            property failed_label : 2 -- label index for aa2.
            (*
                label index = colour
                          0 = none
                          1 = orange
                          2 = red
                          3 = yellow
                          4 = blue
                          5 = purple
                          6 = green
                          7 = gray      
            *)
            property mss1 : "Choose folder of alias files to be redirected."
            property mss2 : "Alias redirection done."
            on reassign(a)
                (*
                    alias a
                    property path1 -- searching leading partial path
                    property path2 -- replacing leading partial path
                    return either alias or false:
                        alias a : original alias if no path substitution applied.
                        alias a1 : reassigend alias created by a's leading partial path 'path1' being replaced by 'path2'
                        false : reassignment failed, i.e. assumed reassigned object not found
                *)
                local p
                if a's class is not alias then return a
                set p to "" & a
                if p starts with path1 then
                    set p to path2 & (p's text ((path1's length) + 1) thru -1)
                    try
                        return (p as alias)
                    on error -- assumed reassigned object not found
                        return false
                    end try
                else
                    return a
                end if
            end reassign
            
            -- (0) accept folder of alias files
            set fda to choose folder with prompt mss1
            
            -- (1) get list of alias files, their name and original items
            tell application "Finder"
                tell item fda's entire contents's alias files
                    if (count) = 0 then
                        set {aa, xx} to {{}, {}}
                    else
                        try
                            set aa to it as alias list
                        on error
                            set aa to it as alias as list
                        end try
                        set nn to name as list
                        try
                            if (count my aa) = 1 then
                                set xx to original item as alias as list
                            else
                                set xx to original item as alias list
                            end if
                        on error number -1700 -- some alias file(s) broken
                            repeat with a in my aa
                                set a to a's contents
                                try
                                    set end of my xx to a's original item as alias
                                on error -- broken alias file
                                    set end of my xx to null
                                end try
                            end repeat
                        end try
                    end if
                end tell
            end tell
            
            -- (2) reassign original items
            repeat with x in my xx
                set end of yy to reassign(x's contents)
            end repeat
            
            -- (3) sort out original alias files and create new ones if necessary
            repeat with i from 1 to count my aa
                set yi to my yy's item i
                if yi is null then -- broken
                    set end of my aa1 to my aa's item i
                else if yi is false then -- failed, i.e. assumed reassigned original item not found
                    set end of my aa2 to my aa's item i
                else if yi is not my xx's item i then -- reassigned
                    set end of my aa3 to my aa's item i
                end if
            end repeat
            repeat with a in my aa1
                tell application "Finder" to set a's label index to broken_label
            end repeat
            repeat with a in my aa2
                tell application "Finder" to set a's label index to failed_label
            end repeat
            if my aa3 is not {} then
                -- recreate alias files
                repeat with i from 1 to count my aa
                    set a to my aa's item i
                    if a is in my aa3 then
                        tell application "Finder"
                            set d to a's container as alias
                            delete a
                            make new alias file at d to (my yy's item i) with properties {name:my nn's item i}
                        end tell
                    end if
                end repeat
            end if
            
            -- (4) post-processings: empty trash (optional) and decent notice
            tell application "Finder"
                --empty trash
                activate
                display dialog mss2 with icon 1 giving up after 5
            end tell
        end script
        tell o to run
    end main
    -- END OF SCRIPT
    
  • by Matty725,

    Matty725 Matty725 Sep 18, 2015 7:51 PM in response to Hiroto
    Level 1 (10 points)
    Sep 18, 2015 7:51 PM in response to Hiroto

    Thanks so much for providing that. I selected the root of my backup drive which contains almost 2TB of data and about 1,000 aliases in total scattered throughout many deeply nested sub-folders. I played around with the values of path1 and path2 and and found that specifying the unqualified volume names got the script to run. Is this how they should be referenced?

     

    property path1 : "ORIGINAL_DRIVE" -- searching leading partial path of original item of alias file.

    property path2 : "BACKUP_DRIVE" -- replacing leading partial path of original item of alias file.

     

    The script took about 2 minutes to run which is acceptable considering I won't be running it all of the time. However, I found that nearly all of the aliases on the drive had a grey or a red dot next to them. The red ones had successfully resolved the aliases correctly to the local drive but the grey ones were still pointing to the original drive. Can you suggest what might be going wrong here?

  • by Hiroto,Solvedanswer

    Hiroto Hiroto Sep 21, 2015 12:20 AM in response to Matty725
    Level 5 (7,286 points)
    Sep 21, 2015 12:20 AM in response to Matty725

    Sorry for late reply.

     

    As for the parameters, yes, AppleScript's alias specifier requires HFS path, which starts with volume name and uses colon (:) for node separator in path.

     

    As for your example, the properties should be like -

     

    property path1 : "ORIGINAL_DRIVE:" -- searching leading partial path of original item of alias file.
    property path2 : "BACKUP_DRIVE:" -- replacing leading partial path of original item of alias file.
    

     

     

    provided that you are to redirect aliases under disk "BACKUP_DRIVE", which are originally linked to files in disk "ORIGINAL_DRIVE", to files in "BACKUP_DRIVE" and choose disk "BACKUP_DRIVE" in the choose folder dialague. (I added trailing : to volume paths in path1 and path2 so as to let them strictly match volume name in path)

     

    As for your result, I'm not sure what is going on but I'd guess poor Finder is failing to handle such large directory tree. In the script, alias list originally broken to be labeled by gray colour (aa1), alias list with missing new target to be labeled by red colour (aa2) and alias list to be redirected to existing new target (aa3) are mutually exclusive. So if the aliases labeled by red are correctly resolved to new target, it means they had been already linked to new target before the script is run (unless Finder has done alias redirection when requested to get the original item).

     

    Anyway, gray alias file means Finder could not get its original item in the initial scan and red alias file means AppleScript could not build alias object of its new original item's path in reassign() handler. I think the former would indicate Finder is failing to handle large directory tree and the latter would indicate path1 and/or path2 may not be correct.

     

     

    ----

    Now let us be free from miserable Finder scripting in AppleScript.

     

    Here's newly written RubyCocoa script to redirect aliases under specified directory by editing its target path.

     

    Usage e.g.

     

     

    #!/bin/bash
    #
    #   Provided that rubycocoa script is saved in ~/Desktop/redirect_aliases.rb, this script will let it
    #   retrieve and resolve each alias file under /Volumes/DISK B,
    #   build new targt path by replacing '/Volumes/DISK A/' with '/Volumes/DISK B/' in its original target path,
    #   and replace the original alias file with new alias file linked to new target.
    #
    #   * See comments in source code for more details
    #
    cd ~/Desktop
    chmod u+x redirect_aliases.rb
    ./redirect_aliases.rb '/Volumes/DISK B' '/Volumes/DISK A/' '/Volumes/DISK B/'
    

     

     

     

    redirect_aliases.rb

     

     

    #!/usr/bin/ruby -w
    #
    #   file :
    #       redirect_aliases.rb
    # 
    #   ARGV :
    #       [directory, search, replace]
    #           directory : POSIX path of directory which target tree is rooted at
    #           search    : search string in alias's target path
    #           replace   : replace string
    # 
    #   This script will resolve each alias file under specified <directory>, 
    #   build new target path by replacing <search> string with <replace> string in original target path,
    #   and replace the original alias file with new alias file linked to new target.
    # 
    #   * If original alias file is unresolvable, it is labeled by gray colour.
    #   * If new target does not exist, the original alias file is preserved and labeled by red colour.
    #   * Alias resolution is static, i.e., target volume must be mounted in advance.
    #   * Package contents are not scanned.
    #   
    #   version :
    #       v0.10
    # 
    #   written by Hiroto, 2015-09
    # 
    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 %[%s: 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 %[%s: 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 %[s: 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 %[%s: stale alias] % f unless opts[:quiet]
        end
        return [0, url1.path]
    end
    
    def create_alias(g, f, opts = {})
        #   string g : POSIX path of original file (target of alias file)
        #   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 file is created successfully
        #           1 = bookmark data is created but not written to alias file
        #           2 = bookmark data is not created (e.g., g does not exist)
        #       f1 = POSIX path of alias file or nil if failed
        opts = {:quiet => false}.merge(opts)
        err = OCObject.new
        bmrk = NSURL.fileURLWithPath(g).objc_send(
            :bookmarkDataWithOptions, NSURLBookmarkCreationSuitableForBookmarkFile,
            :includingResourceValuesForKeys, [],
            :relativeToURL, nil,
            :error, err)
        unless bmrk
            $stderr.puts %[%s: failed to create bookmark data - %s] % [g, err] unless opts[:quiet]
            return [2, nil]
        end
        err = OCObject.new
        url = NSURL.fileURLWithPath(f)
        b = NSURL.objc_send(
            :writeBookmarkData, bmrk,
            :toURL, url,
            :options, NSURLBookmarkCreationSuitableForBookmarkFile,
            :error, err)
        unless b
            $stderr.puts %[%s: failed to write bookmark data - %s] % [f, err] unless opts[:quiet]
            return [1, nil]
        end
        return [0, url.path]
    end
    
    def scan_aliases(p, opts = {})
        #   string p : absolute POSIX path of directory or file
        #   hash opts : {:package => boolean}
        #       :package => true to scan package contents, false otherwise; default = false
        #   return array : aliases in directory tree rooted at p if p is directory; [p] if p is alias file; otherwise []
        opts = {:package => false}.merge(opts)
        aa = []
        ws = NSWorkspace.sharedWorkspace
        uti = ws.typeOfFile_error(p, nil)
        if ['public.folder', 'public.volume'].include?(uti)
            de = NSFileManager.defaultManager.enumeratorAtPath(p)
            while (n = de.nextObject) != nil do
                f = p + '/' + n.to_s
                de.skipDescendants if ! opts[:package] && ws.isFilePackageAtPath(f)
                if de.fileAttributes.objectForKey(NSFileType) == NSFileTypeRegular
                    aa << f if ws.typeOfFile_error(f, nil) == 'com.apple.alias-file'
                end
            end
        elsif uti == 'com.apple.alias-file'
            aa << p
        end
        aa
    end
    
    def set_label(f, lb, opts = {})
        #   string f : POSIX path of file or directory
        #   integer lb : label index
        #       label index => colour
        #                 0 => none
        #                 1 => gray
        #                 2 => green
        #                 3 => purple
        #                 4 => blue
        #                 5 => yellow
        #                 6 => red
        #                 7 => orange
        #   hash opts : {:quiet => boolean}
        #       :quiet => true to suppress error message, false otherwise; default = false
        #   return boolean : true if successful, false otherwise.
        opts = {:quiet => false}.merge(opts)
        err = OCObject.new
        b = NSURL.fileURLWithPath(f).objc_send(
            :setResourceValue, NSNumber.numberWithInt(lb),
            :forKey, NSURLLabelNumberKey,
            :error, err)
        unless b
            $stderr.puts '%s: failed to set label index to %d - %s' % [f, lb, err] unless opts[:quiet]
        end
        b
    end
    
    def main(argv)
        d = File.expand_path(argv.shift)
        s, r, = argv
        scan_aliases(d).each do |f|
            rc, g = resolve_alias(f)
            if rc == 0
                g1 = g.sub(s, r)
                next if g1 == g
                if File.exist?(g1)
                    File.delete(f)  # this should not be necessary but is to update Finder's cache assuredly
                    create_alias(g1, f)
                else
                    # unresolvable redirection (new target does not exist)
                    $stderr.puts '%s: failed to redirect alias - New target (%s) does not exist.' % [f, g1]
                    set_label(f, 6)
                end
            else
                # originally unresolvable alias
                set_label(f, 1)
            end
        end
    end
    
    main(ARGV)
    

     

     

     

    Notes on RubyCocoa script.

     

    - Under OS X 10.10, you need to manually install RubyCocoa 1.2.0 which supports Ruby 2.0 or later.

     

    https://rubycocoa.github.io/

    https://github.com/rubycocoa/rubycocoa/releases

     

     

    - Under OS X 10.9, unless you have installed RubyCocoa 1.2.0 for Ruby 2.0, specify the ruby interpreter at

     

    /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
    

     

     

    - Under OS X 10.5 through 10.8, script should work as is.

     

     

     

     

    ----

    In case, here's an AppleScript wrapper of the script.

     

     

    --APPLESCRIPT
    --set _directory to "/Volumes/DISK B"
    set _directory to (choose folder)'s POSIX path
    set _search to "/Volumes/DISK A/"
    set _replace to "/Volumes/DISK B/"
    
    redirect_aliases(_directory, _search, _replace)
    display dialog "Aliases' redirection done"
    
    on redirect_aliases(d, s, r)
        (*
            string d : POSIX path of directory which target tree is rooted at
            string s : search string in alias's target path
            string r : replace string
        *)
        set args to ""
        repeat with a in {d, s, r}
            set args to args & a's quoted form & space
        end repeat
        
        do shell script "/bin/bash -s <<'HUM' - " & args & "
    /usr/bin/ruby -w <<'EOF' - \"$@\"
    #
    #   ARGV :
    #       [directory, search, replace]
    #           directory : POSIX path of directory which target tree is rooted at
    #           search    : search string in alias's target path
    #           replace   : replace string
    #   
    #   This script will resolve each alias file under specified <directory>, 
    #   build new target path by replacing <search> string in original target path with <replace> string,
    #   and replace the original alias file with new alias file linked to new target.
    # 
    #   * If original alias file is unresolvable, it is labeled by gray colour.
    #   * If new target does not exist, the original alias file is preserved and labeled by red colour.
    #   * Alias resolution is static, i.e., target volume must be mounted in advance.
    #   * Package contents are not scanned.
    #   
    #   version :
    #       v0.10
    # 
    #   written by Hiroto, 2015-09
    # 
    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 %[%s: 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 %[%s: 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 %[s: 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 %[%s: stale alias] % f unless opts[:quiet]
        end
        return [0, url1.path]
    end
    
    def create_alias(g, f, opts = {})
        #   string g : POSIX path of original file (target of alias file)
        #   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 file is created successfully
        #           1 = bookmark data is created but not written to alias file
        #           2 = bookmark data is not created (e.g., g does not exist)
        #       f1 = POSIX path of alias file or nil if failed
        opts = {:quiet => false}.merge(opts)
        err = OCObject.new
        bmrk = NSURL.fileURLWithPath(g).objc_send(
            :bookmarkDataWithOptions, NSURLBookmarkCreationSuitableForBookmarkFile,
            :includingResourceValuesForKeys, [],
            :relativeToURL, nil,
            :error, err)
        unless bmrk
            $stderr.puts %[%s: failed to create bookmark data - %s] % [g, err] unless opts[:quiet]
            return [2, nil]
        end
        err = OCObject.new
        url = NSURL.fileURLWithPath(f)
        b = NSURL.objc_send(
            :writeBookmarkData, bmrk,
            :toURL, url,
            :options, NSURLBookmarkCreationSuitableForBookmarkFile,
            :error, err)
        unless b
            $stderr.puts %[%s: failed to write bookmark data - %s] % [f, err] unless opts[:quiet]
            return [1, nil]
        end
        return [0, url.path]
    end
    
    def scan_aliases(p, opts = {})
        #   string p : absolute POSIX path of directory or file
        #   hash opts : {:package => boolean}
        #       :package => true to scan package contents, false otherwise; default = false
        #   return array : aliases in directory tree rooted at p if p is directory; [p] if p is alias file; otherwise []
        opts = {:package => false}.merge(opts)
        aa = []
        ws = NSWorkspace.sharedWorkspace
        uti = ws.typeOfFile_error(p, nil)
        if ['public.folder', 'public.volume'].include?(uti)
            de = NSFileManager.defaultManager.enumeratorAtPath(p)
            while (n = de.nextObject) != nil do
                f = p + '/' + n.to_s
                de.skipDescendants if ! opts[:package] && ws.isFilePackageAtPath(f)
                if de.fileAttributes.objectForKey(NSFileType) == NSFileTypeRegular
                    aa << f if ws.typeOfFile_error(f, nil) == 'com.apple.alias-file'
                end
            end
        elsif uti == 'com.apple.alias-file'
            aa << p
        end
        aa
    end
    
    def set_label(f, lb, opts = {})
        #   string f : POSIX path of file or directory
        #   integer lb : label index
        #       label index => colour
        #                 0 => none
        #                 1 => gray
        #                 2 => green
        #                 3 => purple
        #                 4 => blue
        #                 5 => yellow
        #                 6 => red
        #                 7 => orange
        #   hash opts : {:quiet => boolean}
        #       :quiet => true to suppress error message, false otherwise; default = false
        #   return boolean : true if successful, false otherwise.
        opts = {:quiet => false}.merge(opts)
        err = OCObject.new
        b = NSURL.fileURLWithPath(f).objc_send(
            :setResourceValue, NSNumber.numberWithInt(lb),
            :forKey, NSURLLabelNumberKey,
            :error, err)
        unless b
            $stderr.puts '%s: failed to set label index to %d - %s' % [f, lb, err] unless opts[:quiet]
        end
        b
    end
    
    def main(argv)
        d = File.expand_path(argv.shift)
        s, r, = argv
        scan_aliases(d).each do |f|
            rc, g = resolve_alias(f)
            if rc == 0
                g1 = g.sub(s, r)
                next if g1 == g
                if File.exist?(g1)
                    File.delete(f)  # this should not be necessary but is to update Finder's cache assuredly
                    create_alias(g1, f)
                else
                    # unresolvable redirection (new target does not exist)
                    $stderr.puts '%s: failed to redirect alias - New target (%s) does not exist.' % [f, g1]
                    set_label(f, 6)
                end
            else
                # originally unresolvable alias
                set_label(f, 1)
            end
        end
    end
    
    main(ARGV)
    EOF
    HUM"
    end redirect_aliases
    --END OF APPLESCRIPT
    

     

     

     

    Tested under OS X 10.6.8 but no warranties of any kind. Please make sure you have backup of directories in advance when running this sort of script. It is good practice to first test it on small test data set.

     

    Good luck,

    H

  • by Hiroto,

    Hiroto Hiroto Sep 21, 2015 12:37 AM in response to Hiroto
    Level 5 (7,286 points)
    Sep 21, 2015 12:37 AM in response to Hiroto

    Oops. Minor collection on "Notes on RubyCocoa".

     

    The specific RubyCocoa script posted in this thread uses NSURL features only available under OS X 10.6 or later. So the line:

     

    - Under OS X 10.5 through 10.8, script should work as is.

     

     

    should have been:

     

    - Under OS X 10.6 through 10.8, script should work as is.

     

     

     

    * RubyCocoa itself is available under OS X 10.5 as well.

     

    Regards,

    H

  • by Hiroto,

    Hiroto Hiroto Sep 21, 2015 1:02 AM in response to Hiroto
    Level 5 (7,286 points)
    Sep 21, 2015 1:02 AM in response to Hiroto

    Ah...

     

    minor collection => minor correction

     

    H

  • by Matty725,

    Matty725 Matty725 Sep 21, 2015 4:38 AM in response to Hiroto
    Level 1 (10 points)
    Sep 21, 2015 4:38 AM in response to Hiroto

    I am running OSX 10.10.5. I couldn't get the Ruby script to run successfully. I installed RubyCocoa 1.2.0. Do I run the following from a Terminal prompt?

     

    cd ~/Desktop

    chmod u+x redirect_aliases.rb

    ./redirect_aliases.rb '/Volumes/DISK B' '/Volumes/DISK A/' '/Volumes/DISK B/'

     

    I ran the AppleScript wrapper from Script Editor and that seemed to work OK. I noticed that some of the aliases appeared not to resolve to DISK B until after I ejected DISK A. I had already installed RubyCocoa 1.2.0. Is this a prerequisite for running this AppleScript on OSX 10.10.5?

  • by Hiroto,Helpful

    Hiroto Hiroto Sep 24, 2015 6:19 PM in response to Matty725
    Level 5 (7,286 points)
    Sep 24, 2015 6:19 PM in response to Matty725

    Hello

     

    Yes, RubyCocoa 1.2.0 is required under OS X 10.10 for the RubyCocoa script and its AppleScript wrapper to work.

     

    As for the shell script starting with the line #!/bin/bash, it is supposed to be run in Terminal. The chmod(1) command is present only to set its executable bit in case it is not already set. If you're not familiar with shell scripting, you may simply use the AppleScript wrapper.

     

    Regarding the fact that you need to eject the original disk in order to let (some) new aliases be resolved to new targets in other disk, I'd guess it is due to Finder's bug. As far as I know, Finder is junk and its caching function has been really buggy. You might try killing Finder instead of ejecting the disk and see if it works. If it should work, I'd conclude Finder's incoherent cache is the culprit.

     

    All the best,

    H

  • by Matty725,

    Matty725 Matty725 Sep 24, 2015 6:20 PM in response to Hiroto
    Level 1 (10 points)
    Sep 24, 2015 6:20 PM in response to Hiroto

    Thanks so much. Very helpful

  • by Hiroto,

    Hiroto Hiroto Sep 25, 2015 1:02 AM in response to Matty725
    Level 5 (7,286 points)
    Sep 25, 2015 1:02 AM in response to Matty725

    My pleasure! Thank you for reviving this problem to be reviewed in modern light.

     

    Best wishes,

    Hiroto

  • by Matty725,

    Matty725 Matty725 Mar 15, 2016 7:46 PM in response to Hiroto
    Level 1 (10 points)
    Mar 15, 2016 7:46 PM in response to Hiroto

    Hi @Hiroto,

     

    I'd like to run this script on El Capitan but it won't allow me to install RubyCocoa 1.2.0 as "OSX is too new". Do you know whether there is a workaround?

  • by Hiroto,

    Hiroto Hiroto Mar 19, 2016 6:00 AM in response to Matty725
    Level 5 (7,286 points)
    Mar 19, 2016 6:00 AM in response to Matty725

    Hello

     

    Sorry for late reply.

     

    Since there seems to be no pre-compiled build of rubycocoa 1.2.0 for OS X 10.11 (yet), you might build and install rubycocoa 1.2.0 from source code by yourself if you're familiar with this sort of open-source handwork.

     

    Meanwhile, you might try pyobjc version of original rubycocoa script. You should be able to use pyobjc under OS X 10.11 without additional installation.

     

    The first script is an AppleScript wrapper of pyobjc script which acts basically the same as the original rubycocoa script. I made a minor revision to AppleScript script so that it now returns errors in result pane/window of (Apple)Script Editor.

     

     

    --APPLESCRIPT
    --set _directory to "/Volumes/DISK B"
    set _directory to (choose folder)'s POSIX path
    set _search to "/Volumes/DISK A/"
    set _replace to "/Volumes/DISK B/"
    
    return redirect_aliases(_directory, _search, _replace)
    --display dialog "Aliases' redirection done"
    
    on redirect_aliases(d, s, r)
        (*
            string d : POSIX path of directory which target tree is rooted at
            string s : search string in alias's target path
            string r : replace string
        *)
        set args to ""
        repeat with a in {d, s, r}
            set args to args & a's quoted form & space
        end repeat
        do shell script "/usr/bin/python <<'EOF' - " & args & " 2>&1
    # coding: utf-8
    #
    #   file :
    #       redirect_aliases.py
    # 
    #   ARGV :
    #       [directory, search, replace]
    #           directory : POSIX path of directory which target tree is rooted at
    #           search    : search string in alias's target path
    #           replace   : replace string
    #   
    #   This script will resolve each alias file under specified <directory>, 
    #   build new target path by replacing <search> string in original target path with <replace> string,
    #   and replace the original alias file with new alias file linked to new target.
    # 
    #   * If original alias file is unresolvable, it is labeled by gray colour.
    #   * If new target does not exist, the original alias file is preserved and labeled by red colour.
    #   * Alias resolution is static, i.e., target volume must be mounted in advance.
    #   * Package contents are not scanned.
    #   
    #   version :
    #       v0.10
    # 
    #   written by Hiroto, 2016-03
    # 
    import sys, os
    import objc
    from Foundation import *
    from AppKit import *
    # 
    #   manual loading of the following bridgesupport metadata is required for pyobjc 2.2b3 under OS X 10.6.8
    # 
    FOUNDATION_BRIDGESUPPORT = '''<?xml version=\"1.0\" standalone=\"yes\"?>
    <!DOCTYPE signatures SYSTEM \"file://localhost/System/Library/DTDs/BridgeSupport.dtd\">
    <signatures version=\"0.9\">
        <constant name='NSURLNameKey' type='@'/>
        <constant name='NSURLPathKey' type='@'/>
        <constant name='NSURLLabelNumberKey' type='@'/>
        <enum name='NSURLBookmarkCreationMinimalBookmark' value='512'/>
        <enum name='NSURLBookmarkCreationPreferFileIDResolution' value='256'/>
        <enum name='NSURLBookmarkCreationSuitableForBookmarkFile' value='1024'/>
        <enum name='NSURLBookmarkResolutionWithoutMounting' value='512'/>
        <enum name='NSURLBookmarkResolutionWithoutUI' value='256'/>
        <class name='NSURL'>
            <method selector='bookmarkDataWithContentsOfURL:error:' class_method='true'>
                <arg type='@' index='0'/>
                <arg type='^@' index='1' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:' class_method='true'>
                <arg type='@' index='0'/>
                <arg type64='Q' type='I' index='1'/>
                <arg type='@' index='2'/>
                <arg type='^B' index='3' type_modifier='o'/>
                <arg type='^@' index='4' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='writeBookmarkData:toURL:options:error:' class_method='true'>
                <arg type='@' index='0'/>
                <arg type='@' index='1'/>
                <arg type64='Q' type='I' index='2'/>
                <arg type='^@' index='3' type_modifier='o'/>
                <retval type='B'/>
            </method>
            <method selector='bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:'>
                <arg type64='Q' type='I' index='0'/>
                <arg type='@' index='1'/>
                <arg type='@' index='2'/>
                <arg type='^@' index='3' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='resourceValuesForKeys:error:'>
                <arg type='@' index='0'/>
                <arg type='^@' index='1' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='setResourceValue:forKey:error:'>
                <arg type='@' index='0'/>
                <arg type='@' index='1'/>
                <arg type='^@' index='2' type_modifier='o'/>
                <retval type='B'/>
            </method>
        </class>
    </signatures>'''
    
    objc.parseBridgeSupport(
        FOUNDATION_BRIDGESUPPORT, 
        globals(), 
        objc.pathForFramework('/System/Library/Frameworks/Foundation.framework')
    )
    
    try:
        URLKEY = NSURLPathKey   # 10.8 or later
    except:
        URLKEY = NSURLNameKey   # 10.6 or later (fallback)
    
    def resolve_alias(f, **opts):
        #   unicode string f : POSIX path of alias file
        #   dict opts : {'quiet' : boolean}
        #       quiet : True to suppress error message, False otherwise; default = False
        #   return tupple : (rc, f1)
        #       (int) 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)
        #       (unicode string) f1 = POSIX path of target file or None if failed
        opts.setdefault('quiet', False)
        url = NSURL.fileURLWithPath_(f)
        bmrk, err = NSURL.bookmarkDataWithContentsOfURL_error_(url, None)
        if not bmrk:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to get bookmark data: %s\\n' % tuple(
                        [ a.encode('utf-8') for a in [f, err.description()] ]
                    )
                )
            return (3, None)
        url1, stale, err = NSURL.URLByResolvingBookmarkData_options_relativeToURL_bookmarkDataIsStale_error_(
            bmrk,
            NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
            None,
            None,
            None)
        if not url1:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to resolve alias: %s\\n' % tuple(
                        [ a.encode('utf-8') for a in [f, err.description()] ]
                    )
                )
            dic = NSURL.resourceValuesForKeys_fromBookmarkData_([URLKEY], bmrk)
            path2 = dic[URLKEY]
            if not path2:
                if not opts['quiet']:
                    sys.stderr.write('%s: failed to get target path: %s\\n' % tuple(
                            [ a.encode('utf-8') for a in [f, err.description()] ]
                        )
                    )
                return (2, None)
            else:
                return (1, path2)
        if stale:
            if not opts['quiet']:
                sys.stderr.write('%s: stale alias\\n' % f.encode('utf-8'))
        return (0, url1.path())
    
    def create_alias(g, f, **opts):
        #   unicode string g : POSIX path of original file (target of alias file)
        #   unicode string f : POSIX path of alias file
        #   dict opts : {'quiet' : boolean}
        #       quiet : True to suppress error message, False otherwise; default = False
        #   return tupple : (rc, f1)
        #       (int) rc = return code 
        #           0 = alias file is created successfully
        #           1 = bookmark data is created but not written to alias file
        #           2 = bookmark data is not created (e.g., g does not exist)
        #       (unicode string) f1 = POSIX path of alias file or None if failed
        opts.setdefault('quiet', False)
        bmrk, err = NSURL.fileURLWithPath_(g).bookmarkDataWithOptions_includingResourceValuesForKeys_relativeToURL_error_(
            NSURLBookmarkCreationSuitableForBookmarkFile,
            [],
            None,
            None)
        if not bmrk:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to create bookmark data: %s\\n' % tuple(
                        [ a.encode('utf-8') for a in [g, err.description()] ]
                    )
                )
            return (2, None)
        url = NSURL.fileURLWithPath_(f)
        b, err = NSURL.writeBookmarkData_toURL_options_error_(
            bmrk,
            url,
            NSURLBookmarkCreationSuitableForBookmarkFile,
            None)
        if not b:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to write bookmark data: %s\\n' % tuple(
                        [ a.encode('utf-8') for a in [f, err.description()] ]
                    )
                )
            return (1, None)
        return (0, url.path())
    
    def scan_aliases(p, **opts):
        #   unicode string p : absolute POSIX path of directory or file
        #   dict opts : {'package' : boolean}
        #       package : True to scan package contents, False otherwise; default = False
        #   return list : aliases in directory tree rooted at p if p is directory; [p] if p is alias file; otherwise []
        opts.setdefault('package', False)
        aa = []
        ws = NSWorkspace.sharedWorkspace()
        uti, err = ws.typeOfFile_error_(p, None)
        if ['public.folder', 'public.volume'].count(uti) > 0:
            de = NSFileManager.defaultManager().enumeratorAtPath_(p)
            while 1:
                n = de.nextObject()
                if not n: break
                f = p + '/' + n
                if not opts['package'] and ws.isFilePackageAtPath_(f):
                    de.skipDescendants()
                if de.fileAttributes().objectForKey_(NSFileType) == NSFileTypeRegular:
                    if ws.typeOfFile_error_(f, None)[0] == 'com.apple.alias-file':
                        aa.append(f) 
        elif uti == 'com.apple.alias-file':
            aa.append(p)
        return aa
    
    def set_label(f, lb, **opts):
        #   string f : POSIX path of file or directory
        #   integer lb : label index
        #       label index => colour
        #                 0 => none
        #                 1 => gray
        #                 2 => green
        #                 3 => purple
        #                 4 => blue
        #                 5 => yellow
        #                 6 => red
        #                 7 => orange
        #   dict opts : {'quiet' : boolean}
        #       quiet : True to suppress error message, False otherwise; default = False
        #   return boolean : True if successful, False otherwise.
        opts.setdefault('quiet', False)
        b, err = NSURL.fileURLWithPath_(f).setResourceValue_forKey_error_(
            NSNumber.numberWithInt_(lb),
            NSURLLabelNumberKey,
            None)
        if not b:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to set label index to %d - %s\\n' % (
                        f.ecnode('utf-8'), lb, err.description().encode('utf-8')
                    )
                )
        return b
    
    def main():
        argv = [ a.decode('utf-8') for a in sys.argv ]
        d = os.path.abspath(argv[1])
        s, r = argv[2:4]
        for f in scan_aliases(d):
            rc, g = resolve_alias(f)
            if rc == 0:
                g1 = g.replace(s, r)
                if g1 == g: continue
                if os.path.exists(g1):
                    os.remove(f)    # this should not be necessary but is to update Finder's cache assuredly
                    create_alias(g1, f)
                else:
                    # unresolvable redirection (new target does not exist)
                    sys.stderr.write('%s: failed to redirect alias - new target (%s) does not exist\\n' % tuple(
                            [ a.encode('utf-8') for a in [f, g1] ]
                        )
                    )
                    set_label(f, 6)
            else:
                # originally unresolvable alias
                set_label(f, 1)
    
    main()
    EOF"
    end redirect_aliases
    --END OF APPLESCRIPT
    

     

     

     

    And here's original pybojc script if you prefer to use it in shell script.

     

     

    #!/usr/bin/python
    # coding: utf-8
    #
    #   file :
    #       redirect_aliases.py
    # 
    #   ARGV :
    #       [directory, search, replace]
    #           directory : POSIX path of directory which target tree is rooted at
    #           search    : search string in alias's target path
    #           replace   : replace string
    #   
    #   This script will resolve each alias file under specified <directory>, 
    #   build new target path by replacing <search> string in original target path with <replace> string,
    #   and replace the original alias file with new alias file linked to new target.
    # 
    #   * If original alias file is unresolvable, it is labeled by gray colour.
    #   * If new target does not exist, the original alias file is preserved and labeled by red colour.
    #   * Alias resolution is static, i.e., target volume must be mounted in advance.
    #   * Package contents are not scanned.
    #   
    #   version :
    #       v0.10
    # 
    #   written by Hiroto, 2016-03
    # 
    import sys, os
    import objc
    from Foundation import *
    from AppKit import *
    # 
    #   manual loading of the following bridgesupport metadata is required for pyobjc 2.2b3 under OS X 10.6.8
    # 
    FOUNDATION_BRIDGESUPPORT = '''<?xml version="1.0" standalone="yes"?>
    <!DOCTYPE signatures SYSTEM "file://localhost/System/Library/DTDs/BridgeSupport.dtd">
    <signatures version="0.9">
        <constant name='NSURLNameKey' type='@'/>
        <constant name='NSURLPathKey' type='@'/>
        <constant name='NSURLLabelNumberKey' type='@'/>
        <enum name='NSURLBookmarkCreationMinimalBookmark' value='512'/>
        <enum name='NSURLBookmarkCreationPreferFileIDResolution' value='256'/>
        <enum name='NSURLBookmarkCreationSuitableForBookmarkFile' value='1024'/>
        <enum name='NSURLBookmarkResolutionWithoutMounting' value='512'/>
        <enum name='NSURLBookmarkResolutionWithoutUI' value='256'/>
        <class name='NSURL'>
            <method selector='bookmarkDataWithContentsOfURL:error:' class_method='true'>
                <arg type='@' index='0'/>
                <arg type='^@' index='1' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:' class_method='true'>
                <arg type='@' index='0'/>
                <arg type64='Q' type='I' index='1'/>
                <arg type='@' index='2'/>
                <arg type='^B' index='3' type_modifier='o'/>
                <arg type='^@' index='4' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='writeBookmarkData:toURL:options:error:' class_method='true'>
                <arg type='@' index='0'/>
                <arg type='@' index='1'/>
                <arg type64='Q' type='I' index='2'/>
                <arg type='^@' index='3' type_modifier='o'/>
                <retval type='B'/>
            </method>
            <method selector='bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:'>
                <arg type64='Q' type='I' index='0'/>
                <arg type='@' index='1'/>
                <arg type='@' index='2'/>
                <arg type='^@' index='3' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='resourceValuesForKeys:error:'>
                <arg type='@' index='0'/>
                <arg type='^@' index='1' type_modifier='o'/>
                <retval type='@'/>
            </method>
            <method selector='setResourceValue:forKey:error:'>
                <arg type='@' index='0'/>
                <arg type='@' index='1'/>
                <arg type='^@' index='2' type_modifier='o'/>
                <retval type='B'/>
            </method>
        </class>
    </signatures>'''
    
    objc.parseBridgeSupport(
        FOUNDATION_BRIDGESUPPORT, 
        globals(), 
        objc.pathForFramework('/System/Library/Frameworks/Foundation.framework')
    )
    
    try:
        URLKEY = NSURLPathKey   # 10.8 or later
    except:
        URLKEY = NSURLNameKey   # 10.6 or later (fallback)
    
    def resolve_alias(f, **opts):
        #   unicode string f : POSIX path of alias file
        #   dict opts : {'quiet' : boolean}
        #       quiet : True to suppress error message, False otherwise; default = False
        #   return tupple : (rc, f1)
        #       (int) 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)
        #       (unicode string) f1 = POSIX path of target file or None if failed
        opts.setdefault('quiet', False)
        url = NSURL.fileURLWithPath_(f)
        bmrk, err = NSURL.bookmarkDataWithContentsOfURL_error_(url, None)
        if not bmrk:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to get bookmark data: %s\n' % tuple(
                        [ a.encode('utf-8') for a in [f, err.description()] ]
                    )
                )
            return (3, None)
        url1, stale, err = NSURL.URLByResolvingBookmarkData_options_relativeToURL_bookmarkDataIsStale_error_(
            bmrk,
            NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI,
            None,
            None,
            None)
        if not url1:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to resolve alias: %s\n' % tuple(
                        [ a.encode('utf-8') for a in [f, err.description()] ]
                    )
                )
            dic = NSURL.resourceValuesForKeys_fromBookmarkData_([URLKEY], bmrk)
            path2 = dic[URLKEY]
            if not path2:
                if not opts['quiet']:
                    sys.stderr.write('%s: failed to get target path: %s\n' % tuple(
                            [ a.encode('utf-8') for a in [f, err.description()] ]
                        )
                    )
                return (2, None)
            else:
                return (1, path2)
        if stale:
            if not opts['quiet']:
                sys.stderr.write('%s: stale alias\n' % f.encode('utf-8'))
        return (0, url1.path())
    
    def create_alias(g, f, **opts):
        #   unicode string g : POSIX path of original file (target of alias file)
        #   unicode string f : POSIX path of alias file
        #   dict opts : {'quiet' : boolean}
        #       quiet : True to suppress error message, False otherwise; default = False
        #   return tupple : (rc, f1)
        #       (int) rc = return code 
        #           0 = alias file is created successfully
        #           1 = bookmark data is created but not written to alias file
        #           2 = bookmark data is not created (e.g., g does not exist)
        #       (unicode string) f1 = POSIX path of alias file or None if failed
        opts.setdefault('quiet', False)
        bmrk, err = NSURL.fileURLWithPath_(g).bookmarkDataWithOptions_includingResourceValuesForKeys_relativeToURL_error_(
            NSURLBookmarkCreationSuitableForBookmarkFile,
            [],
            None,
            None)
        if not bmrk:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to create bookmark data: %s\n' % tuple(
                        [ a.encode('utf-8') for a in [g, err.description()] ]
                    )
                )
            return (2, None)
        url = NSURL.fileURLWithPath_(f)
        b, err = NSURL.writeBookmarkData_toURL_options_error_(
            bmrk,
            url,
            NSURLBookmarkCreationSuitableForBookmarkFile,
            None)
        if not b:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to write bookmark data: %s\n' % tuple(
                        [ a.encode('utf-8') for a in [f, err.description()] ]
                    )
                )
            return (1, None)
        return (0, url.path())
    
    def scan_aliases(p, **opts):
        #   unicode string p : absolute POSIX path of directory or file
        #   dict opts : {'package' : boolean}
        #       package : True to scan package contents, False otherwise; default = False
        #   return list : aliases in directory tree rooted at p if p is directory; [p] if p is alias file; otherwise []
        opts.setdefault('package', False)
        aa = []
        ws = NSWorkspace.sharedWorkspace()
        uti, err = ws.typeOfFile_error_(p, None)
        if ['public.folder', 'public.volume'].count(uti) > 0:
            de = NSFileManager.defaultManager().enumeratorAtPath_(p)
            while 1:
                n = de.nextObject()
                if not n: break
                f = p + '/' + n
                if not opts['package'] and ws.isFilePackageAtPath_(f):
                    de.skipDescendants()
                if de.fileAttributes().objectForKey_(NSFileType) == NSFileTypeRegular:
                    if ws.typeOfFile_error_(f, None)[0] == 'com.apple.alias-file':
                        aa.append(f) 
        elif uti == 'com.apple.alias-file':
            aa.append(p)
        return aa
    
    def set_label(f, lb, **opts):
        #   string f : POSIX path of file or directory
        #   integer lb : label index
        #       label index => colour
        #                 0 => none
        #                 1 => gray
        #                 2 => green
        #                 3 => purple
        #                 4 => blue
        #                 5 => yellow
        #                 6 => red
        #                 7 => orange
        #   dict opts : {'quiet' : boolean}
        #       quiet : True to suppress error message, False otherwise; default = False
        #   return boolean : True if successful, False otherwise.
        opts.setdefault('quiet', False)
        b, err = NSURL.fileURLWithPath_(f).setResourceValue_forKey_error_(
            NSNumber.numberWithInt_(lb),
            NSURLLabelNumberKey,
            None)
        if not b:
            if not opts['quiet']:
                sys.stderr.write('%s: failed to set label index to %d - %s\n' % (
                        f.ecnode('utf-8'), lb, err.description().encode('utf-8')
                    )
                )
        return b
    
    def main():
        argv = [ a.decode('utf-8') for a in sys.argv ]
        d = os.path.abspath(argv[1])
        s, r = argv[2:4]
        for f in scan_aliases(d):
            rc, g = resolve_alias(f)
            if rc == 0:
                g1 = g.replace(s, r)
                if g1 == g: continue
                if os.path.exists(g1):
                    os.remove(f)    # this should not be necessary but is to update Finder's cache assuredly
                    create_alias(g1, f)
                else:
                    # unresolvable redirection (new target does not exist)
                    sys.stderr.write('%s: failed to redirect alias - new target (%s) does not exist\n' % tuple(
                            [ a.encode('utf-8') for a in [f, g1] ]
                        )
                    )
                    set_label(f, 6)
            else:
                # originally unresolvable alias
                set_label(f, 1)
    
    main()
    

     

     

     

    Scripts are briefly tested with pybojc 2.2b3 and python 2.6.1 under OS X 10.6.8.

     

    Good luck,

    H