Looks like no one’s replied in a while. To start the conversation again, simply ask a new question.

Use AppleScript to recursively fix aliases

I found the script posted here:


Re: Can AppleScript run a FixAlias script?


by @Hiroto 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

Reply
Question marked as Best reply

Posted on Sep 18, 2015 7:42 PM

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

11 replies
Question marked as Best reply

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

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?

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

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

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?

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

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

Use AppleScript to recursively fix aliases

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