I have this script provided by Chris, a Keyboard Maestro user:
--------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2021/01/15 11:12
# dMod: 2021/01/15 11:12
# Appl: Finder
# Task: Move the Selected Files to Parent-Parent Folder.
# : Throw an error if there are any collisions and quit.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Finder
--------------------------------------------------------
try
tell application "Finder"
set finderSelectionList to selection as alias list
if length of finderSelectionList = 0 then error "No files were selected in the Finder!"
set parentFolder to parent of parent of item 1 of finderSelectionList
repeat with i in finderSelectionList
set itemName to name of i
tell parentFolder
if exists of its item itemName then
error "An item named “" & itemName & "” already exists!"
end if
end tell
end repeat
move finderSelectionList to parentFolder
end tell
on error errMsg number errNum
set errMsg to errMsg & return & return & "Num: " & errNum
if errNum ≠ -128 then
try
beep
tell application (path to frontmost application as text) to set ddButton to button returned of ¬
(display dialog errMsg with title "ERROR!" buttons {"Copy Error Message", "Cancel", "OK"} ¬
default button "OK" giving up after 30)
if ddButton = "Copy Error Message" then set the clipboard to errMsg
end try
end if
end try
--------------------------------------------------------
Now I would like to customize it, because the solution for files/folders already present in the folder 1 level above, is not ideal for me:
1 - I don’t need to copy the error message
2 - Clicking OK doesn’t ignore that issue to keep trying to move the other files (basically it’s working like a Cancel button)
3 - If I have 10 files that are unique, but one has the same name as 1 in the folder above, then none of the files are copied, just because of that 1.
So I would to either:
1 - Allow Finder to try to move the files and then be presented with the usual message:
or
2 - Rename all files that have the same name as other files in the level above, so a “document.txt” would become “document (copy).txt” for example.
How can I achieve this?
I have some basic understanding of AS, but still very basic and this seems more complex than I can handle…
What happens if the parent folder already contains a ‘document (copy).txt
’ file?
This should work as long as there won’t be any ’ (copy)
’ collisions.
I separated getting the selection from moving the files to reduce the level of Finder participation.
tell application "Finder"
set finderSelectionList to selection as alias list
if length of finderSelectionList = 0 then error "No files were selected in the Finder!"
set parentFolder to container of container of item 1 of finderSelectionList
end tell
tell application "System Events"
repeat with sel in finderSelectionList -- each alias
set nom to name of sel -- each alias name
tell parentFolder
if exists of its item nom then -- dirty move
set copyName to my dupeName(sel) -- take extension into account
set name of item nom to copyName
move sel to parentFolder
else -- clean move
move sel to parentFolder
end if
end tell
end repeat
end tell
on dupeName(addCopy)
tell application "Finder"
set nn to name of item addCopy
set ne to name extension of item addCopy
set nel to length of ne
if nel is 0 then -- file has no extension
set ext to ""
set dn to nn
else
set ext to "." & ne
set dn to text 1 thru -(nel + 2) of nn
end if
set cn to dn & " (copy)" & ext
end tell
end dupeName
What the handler does is check for an extension and its length. If one exists, insert the ‘copy’ string before it; otherwise, append the ‘copy’ string.
2 Likes
Thank you so much, Ken.
This is in fact super complex. I wish I could understand everything happening here.
Maybe one day…
Regarding another file having (copy)
, that will be almost rare.
But in case that happens, what if instead of (copy)
we add the current time?
Just the time, not the day. The chances of having a file with the exact same AND the exact same time (hour:minute:second) is almost impossible, if not completely impossible.
Would that be easy to implement instead of (copy)
?
Or another option would be to just add a random string with maybe 4 letters?
Here is a slightly more streamlined version that only uses the Finder once
local sel, nom, nn
tell application "Finder"
set finderSelectionList to selection as alias list
if (count finderSelectionList) = 0 then error "No files were selected in the Finder!"
set parentFolder to container of container of item 1 of finderSelectionList
end tell
tell application "System Events"
repeat with sel in finderSelectionList -- each alias
set sel to contents of sel
set nom to name of sel -- each alias name
if exists item nom of parentFolder then -- dirty move
set nn to nom
set ne to name extension of sel
set nel to length of ne
if nel is 0 then -- file has no extension
set ext to ""
set dn to nn
else
set ext to "." & ne
set dn to text 1 thru -(nel + 2) of nn
end if
set copyName to dn & " (copy)" & ext
set name of item nom of parentFolder to copyName
end if
tell parentFolder to move sel to parentFolder
end repeat
end tell
1 Like
The script isn’t actually (very) complicated. It just looks that way, primarily because of the potential for files having an extension, no extension, or ending with a period (eg document.txt, document, document.) and that it must handle one of those situations differently (ie the (copy) should be placed before the extension, if one exists).
However, having two different outcomes would likely make it genuinely complicated as you’d likely have to add recursion to the mix.
If you want to simply replace the ‘copy’ string with a time string, that would easy to implement.
Replace this line…
set cn to dn & " (copy)" & ext
With this line…
set cn to dn & timeStamp() & ext
And then add these lines to the very bottom of the script:
on timeStamp()
set rstr to time of (current date)
set h to rstr div 3600 -- hours
if h is less than 10 then set h to "0" & h
set m to (rstr mod 3600) div 60 -- minutes
if m is less than 10 then set m to "0" & m
set s to (rstr mod 3600) mod 60 -- seconds
if s is less than 10 then set s to "0" & s
set hms to space & h & m & s as text
end timeStamp
This handler will generate a 6-digit number that is made up of the current hour, minute and second, e.g. “135306” for 13:53:06. If any of the three components is under 10 (eg it is 9 am) then it will be padded with a zero.
Aside:
I think that @robertfern’s script is more straightforward and probably quicker. When working on my own version, I had attempted to use system events for almost everything but kept getting errors so I ended up with what I did. I had no issues with his script though.
This version will rename the file in the child-folder instead of the parent-folder
local sel, nom, nn, ctnr
tell application "Finder"
set finderSelectionList to selection as alias list
if (count finderSelectionList) = 0 then error "No files were selected in the Finder!"
set parentFolder to container of container of item 1 of finderSelectionList as text
end tell
tell application "System Events"
repeat with sel in finderSelectionList -- each alias
set sel to contents of sel
set ctnr to path of container of sel
set nom to name of sel -- each alias name
set sel to path of sel
if exists item nom of folder parentFolder then -- dirty move
set nn to nom
set ne to name extension of disk item sel
set nel to length of ne
if nel is 0 then -- file has no extension
set ext to ""
set dn to nn
else
set ext to "." & ne
set dn to text 1 thru -(nel + 2) of nn
end if
set copyName to dn & " (copy)" & ext
set name of disk item sel to copyName
set sel to ctnr & copyName
end if
move disk item sel to folder parentFolder --
end repeat
end tell
The reason you and probably everyone gets error is because “Finder” aliases and “System Events” 's aliases are not quite the same. It is best to convert to paths between the 2 programs.
1 Like