AppleScript - Deleting selected files and folders whose filename contain a certain string

Hello,
we work with template files (generally psds), all saved together in folders, that have a generic name. We copy them and remove the ones not needed for the project, and mass rename the generic string, which in our case is an X. Inside of these folders there are also residue folders and files that have some specific name (this is relevant later).

The templates will contain in the name either ITEM_A or ITEM_B depending which project they are designed for, DRAFT or FINAL depending on the status.

So a typical filename will be ITEM_A_X_DRAFT, where X will be replaced with the current project specifics.

I managed to get a script together that basically saves us a bunch of clicks. The version of the script I’ll use for this topic, will take the current selection in Finder, remove any files whose names contain the ITEM_A or FINAL strings, plus other strings that belong to the loose files or folders, and request a text input which will be used to replace the X in-between the first two underscores in the filename.

tell application "Finder"
    set selectedItems to selection
    set replacementText to text returned of (display dialog "Event#_AssetName" default answer "")
    
    -- Process each item
    repeat with i from 1 to length of selectedItems
        set currentItem to item i of selectedItems
        set currentName to name of currentItem
        if ¬
            name of currentItem contains ("ITEM_A") ¬
            or name of currentItem contains ("FINAL") ¬
            or name of currentItem contains ("assets") ¬
            then
            delete currentItem
        else
            if currentName contains "ITEM_B" then
                -- Replace the first "X" between underscores in the filename with the specified text
                set modifiedName to my replaceFirstX(currentName, replacementText)
                set name of currentItem to modifiedName
            end if
        end if
    end repeat
end tell


-- Function to replace the first 'X' between underscores in the filename
on replaceFirstX(fileName, newText)
    -- Check if the file has an extension by finding the last period (.)
    set fileExtensionPos to offset of "." in fileName
    if fileExtensionPos is greater than 0 then
        -- Split the file name and extension
        set fileExtension to text (fileExtensionPos + 1) through end of fileName
        set fileNameWithoutExtension to text 1 through (fileExtensionPos - 1) of fileName
    else
        -- If there's no extension, handle it as a normal file name
        set fileNameWithoutExtension to fileName
        set fileExtension to ""
    end if
    
    -- Find the first underscore
    set firstUnderscorePos to offset of "_" in fileNameWithoutExtension
    
    -- Find the last underscore by searching from the end of the string
    set lastUnderscorePos to offset of "_" in (reverse of characters of fileNameWithoutExtension as string)
    
    -- Convert the position from reverse back to regular position
    set lastUnderscorePos to (length of fileNameWithoutExtension) - lastUnderscorePos + 1
    
    -- Check if both underscores exist and are valid
    if firstUnderscorePos is not 0 and lastUnderscorePos is not 0 then
        -- Find the part before the first underscore and after the last underscore
        set beforeUnderscore to text 1 through firstUnderscorePos of fileNameWithoutExtension
        set afterUnderscore to text (firstUnderscorePos + 1) through (lastUnderscorePos - 1) of fileNameWithoutExtension
        
        -- Replace the X between the underscores
        set newMiddle to my replaceText(afterUnderscore, "X", newText)
        
        -- Form the modified file name, keep the extension intact
        set afterLastUnderscore to text (lastUnderscorePos + 1) through end of fileNameWithoutExtension
        
        return beforeUnderscore & newMiddle & "_" & afterLastUnderscore & "." & fileExtension
    else
        -- No X found, return original name with extension
        return fileNameWithoutExtension & "." & fileExtension
    end if
end replaceFirstX

-- Function to replace text inside a string
on replaceText(theText, searchText, replaceText)
    set AppleScript's text item delimiters to searchText
    set theTextItems to text items of theText
    set AppleScript's text item delimiters to replaceText
    set theText to theTextItems as string
    set AppleScript's text item delimiters to {""}
    return theText
end replaceText

The script works well, without any error, as long as any subfolder selected is not expanded, as per this screenshot
Screenshot 2025-03-26 at 17.29.17

resulting correcty in any item of the selection whose name contained “FINAL” or “ITEM_A” removed, “X” in the filename replaced by “Test”, which was the text input in the initial prompt.

However, if i have the subfolders expanded (and the content selected of course) as in this screenshot, the script will return this error

I half understand why this happens.
In my mind, I would like the script to work “top-down” in the folder structure, and if the subfolders match the criteria to be deleted, they should be deleted together with their content and obviously their content ignored in the script altogether, even if part of the selection, if that makes sense.

I am a total noob btw, so I hope i made any sense, and if I didn’t I am very happy to (try) to clarify.

Could anyone help be troubleshoot this please?

Sorry for the long post!

Cheers

Hi all!
Just wanted to say that i fixed this by changing the code slightly in this way.

tell application "Finder"
	set selectedItems to selection
	set replacementText to text returned of (display dialog "Event#_AssetName" default answer "")
	
	repeat with currentItem in selectedItems
		try
			if (kind of currentItem is "folder") then
				set currentName to name of currentItem
			else
				
				set currentName to name of currentItem
			end if
			
			
			if ¬
				currentName contains ("ITEM_A") ¬
				then
				delete currentItem
			end if
end try
end repeat
end tell

Rex_Rexa. I’m glad you found a solution. I spent some time with your script and thought I’d post my thoughts FWIW. If you do not encounter any issues with your fixed script, please ignore what follows.

Just in general, I think you have correctly diagnosed the issue, which is that the script potentially attempts to rename/delete files that are in folders that have been deleted and attempts to rename/delete files whose paths have been changed. In limited testing, I’m not sure that your fix will avoid this issue. For example:

There are a number of possible solutions, but the simplest one is to rename/delete files before folders. This might be a bit inefficient but that can only be determined with certainty by testing other alternatives. The following are three possible approaches that put files before folders–I tested approach one with your script and it seemed to work.

--approach one sorts by kind and only works if you have folders and one kind of file
tell application "Finder"
	set selectedItems to reverse of (selection sort by kind) --reverse may not be required depending on kind
end tell

--approach two which puts files before folders
tell application "Finder"
	set theItems to selection
	set selectedItems to {}
	repeat with anItem in theItems
		if class of anItem is folder then
			set end of selectedItems to contents of anItem
		else
			set beginning of selectedItems to contents of anItem
		end if
	end repeat
end tell


--approach three which get files and folders in parent folder and concatenates them
set targetFolder to "Macintosh HD:Users:robert:Test Script:" --edit as desired
tell application "Finder"
	set theFiles to every file of the entire contents of folder targetFolder
	set theFolders to every folder of the entire contents of folder targetFolder
	set selectedItems to theFiles & theFolders
end tell

BTW, another approach that is closer to your proposed fix is to:

  1. get a list of every folder and rename/delete a folder when it meets the script tests; and then
  2. get a list of every file and rename/delete a file when it meets the script tests.

This would not be difficult to script, although you would want to include the code that rename/deletes items in a handler to avoid duplication.

You may indicate in your post that there are subfolders within subfolders. If that’s the case, the above suggestions won’t work and the best solution may be to get the folder/file list using ASObjC. I wrote such a solution but haven’t posted it because there may be simpler solutions as explained above.

BTW, in recent versions of macOS, items selected in a Finder window are returned in the order they are selected. So, another fix is to first select files in the Finder window and then to select folders in the Finder window. This is a bit of a kludge but it’s an easy solution.

Hi peavine,
first of all thank you so much for taking the time to experiment with this, it’s very much appreciated!

Judging from your screen, but correct me if i am wrong, it seems like the failure you get from my revised script stems from the fact that you have added the new code before the old code, instead of replacing it.
Just to be clear I entirely replaced this part

  repeat with i from 1 to length of selectedItems
        set currentItem to item i of selectedItems
        set currentName to name of currentItem
        if ¬
            name of currentItem contains ("ITEM_A") ¬
            or name of currentItem contains ("FINAL") ¬
            or name of currentItem contains ("assets") ¬
            then
            delete currentItem

with this

	repeat with currentItem in selectedItems
		try
			if (kind of currentItem is "folder") then
				set currentName to name of currentItem
			else
				
				set currentName to name of currentItem
			end if
			
			
			if ¬
				currentName contains ("ITEM_A") ¬
				or currentName contains ("FINAL") ¬
				or currentName contains ("assets") ¬
				or currentName starts with ("_") ¬
				then
				delete currentItem

I have tested it on a similar folder structure to the one of your screenshot, and it correctly returns deletion as intended without errors

In any case, thank you very much for your alternative approaches, I’ll explore them and I am sure they’ll come in handy in the future for different scenarios!!

Cheers

1 Like

Addittionally I found out the first if statement is redundant and can be entirely replace with just

set currentName to name of currentItem

without compromising functionality!

Making the new version of the delete part of the code look like this

repeat with currentItem in selectedItems
		try
         set currentName to name of currentItem	
			if ¬
				currentName contains ("ITEM_A") ¬
				or currentName contains ("FINAL") ¬
				or currentName contains ("assets") ¬
				or currentName starts with ("_") ¬
				then
				delete currentItem
            end if
         end try
end repeat

Cheers

1 Like