Move files to folder 1 level above

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… :slight_smile:

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