Copy files from multiple directories based on file name

Hi, I’ve got thousands of images on a server and frequently I need to retrieve specific images from these folders.
Now, The file name is included in the folder structure. For example I have an image named 1234-5678.jpg. The folder that that image will be in will be (root)/12/34/1234-5678.jpg.

My question is, is there a way to automate this process of going to those folders and copying that image file to a specific local folder? I know nothing about scripting for macs so before I dove in trying to see if I could figure out how to do this I thought I would ask to see if it’s even possible.

Thanks.

EDIT
Please see the end of thread for new question. Thanks!

Hi ndog. Welcome to MacScripter.

Yes. What you describe is quite easy to do. You’d need to decide beforehand if your root and/or destination folders were fixed entities whose paths could be written into the script or things the user would have to choose when the script was run. (But you could alway change this later if you needed to.) Similarly with the name of the file, although I presume you’d want the script to present the user with a dialog in which they could type the file’s name.

Do ask again, giving the relevant details, if you need any help.

Ok great!
The Root will always remain the same. The destination folder would need to be manually set via a prompt. Or could I set the script to work as a droplet and it would find and copy the list of images into the current folder?
It would also be ideal for the script to be able to accept multiple file names at a time. Often I have to find a couple hundred images at a time.

So, Where should I begin with learning how to do this? Again I have zero experience with applescripts. If you could point me in the right direction I would be very grateful.

Thanks

Hi ndog. Sorry about the delay getting back.

I’m not in a position to recommend a good AppleScript tutorial as I’ve not read any of the recent books. Hamish Sanderson & Hanaan Rosenthal’s “Learn AppleScript” seems to be well thought of. I’ve also heard praise for Matt Neuburg’s “AppleScript: The Definitive Guide!” over the years, although the reviews on Amazon don’t seem totally convinced it’s good for beginners. There’s also “AppleScript 1-2-3” by Sal Saghoian (Apple’s Automation Product Manager, no less) and Bill Cheeseman. The first chapter of this is reproduced here. It looks like a good place to start. Once you understand the basics, a handy reference is Apple’s own AppleScript Language Guide, often just called ASLG. There’s a link to download a PDF of it in the top right hand corner of the Web page.

As ASLG’s Introduction explains, the core AppleScript language doesn’t have many commands of its own. Much of the scripting you’ll do will be with “plug-in” commands provided by scriptable applications themselves ” commands relevant to what they do and which usually only they can understand. AppleScript can also get additional commands from plug-ins called OSAXen, it can call executables such as those in the Unix operating system, and it’s recently acquired an extension called AppleScript Objective-C which gives access to the Objective-C API in Mac OS itself. So the subject can seem quite intimidating. But you can actually do a lot just knowing the basics. Once you’ve got those, you can explore further at your leisure in any direction you find useful or interesting.

But to your current task of interest. You originally asked about the possibility of copying a file from a location worked out from its name to a certain folder. This would be simply:


-- Preset the HFS (colon separated) path to the root folder. (Use the actual one, not this!)
property rootPath : "Volume name:any:other:folders:Root folder:"

-- Ask the user for a file name.
set fileName to text returned of (display dialog "Please enter the name of the file you want to copy from the server." default answer "xxxx-xxxx.jpg")
-- Ask the user for the destination folder.
set destinationFolder to (choose folder with prompt "Choose a destination folder for the copied files.")

-- Work out the path to the file.
set filePath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":" & fileName

-- Tell the Finder to 'duplicate' (a Finder command) the file to the destination folder, overwriting if there's already one there with the same name.
tell application "Finder" to duplicate file filePath to destinationFolder replacing yes

It would look even simpler without the explanatory comments. :wink:

If instead you want to copy several files whose names are listed one-per-line in a text file called, say, “File list.txt” on your desktop, you need to read the text file and to act on each line in it:


-- Preset the HFS (colon separated) path to the root folder.
property rootPath : "Volume name:any:other:folders:Root folder:"

-- Get the paragraphs of the text file, assumed to be called "File list.txt" and on the user's desktop.
set fileList to paragraphs of (read file ((path to desktop as text) & "File list.txt"))
-- Ask the user for the destination folder.
set destinationFolder to (choose folder with prompt "Choose a destination folder for the copied files.")

-- Work out the path for each file name (paragraph) and tell the Finder to duplicate the file to the destination folder.
repeat with fileName in fileList
	set filePath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":" & fileName
	
	tell application "Finder" to duplicate file filePath to destinationFolder replacing yes
end repeat

It may be prudent to guard against the possibility of the text file containing an empty line or a non-existent file name:


property rootPath : "Volume name:any:other:folders:Root folder:"

set fileList to paragraphs of (read file ((path to desktop as text) & "File list.txt"))
set destinationFolder to (choose folder with prompt "Choose a destination folder for the copied files.")

repeat with fileName in fileList
	 -- Only act if there are characters in this line.
	if ((count fileName) > 0) then
		set filePath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":" & fileName
		
		try -- If anything goes wrong, catch the error.
			tell application "Finder" to duplicate file filePath to destinationFolder replacing yes
		on error
			-- The only thing likely to go wrong here is the file not existing in the calculated location.
			display dialog "There was some problem duplicating the file " & filePath & ". It may not exist." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
		end try
	end if
end repeat

You asked about the possibility of a droplet. I presume you’d want to drag a text file containing the file names onto it. A droplet has to have an ‘open’ handler and must be saved as an application (using the “File Format” pop-up menu in the “Save” dialog).


property rootPath : "Volume name:any:other:folders:Root folder:"

-- The droplet's 'open' handler. 'theseItems' will receive a list of all the files and folders involved in the drop, so it must be treated as a list even when there's only one item.
on open theseItems
	set destinationFolder to (choose folder with prompt "Choose a destination folder for the copied files.")
	
	-- Repeat with every dropped item.
	repeat with thisItem in theseItems
		-- Only act if this item's likely to be a text file.
		if ((thisItem as text) ends with ".txt") then
			-- Get the paragraphs of this text file.
			set fileList to paragraphs of (read thisItem)
			
			repeat with fileName in fileList
				if (count fileName) > 0 then
					set filePath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":" & fileName
					
					try
						tell application "Finder" to duplicate file filePath to destinationFolder replacing yes
					on error
						display dialog "There was some problem duplicating the file "" & filePath & "". It may not exist." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
					end try
				end if
			end repeat
		else
			-- If this item doesn't look like a text file, rub the user's nose in it.  ;)
			display dialog "The item "" & thisfile & "" doesn't have a ".txt" extension. It may not be a text file." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
		end if
	end repeat
end open

If in fact the text file contains a couple of hundred names, it would probably be faster to one of the Unix methods rather than the Finder, but unless you know Unix, the script would be harder to understand.

Wow!
This is amazing! Thank you for going to all this trouble to make this these! and to comment each section (with a bit of comic relief)!
I tried them out and got the droplet to work!

Something I forgot to mention was that I won’t know the file extension of the files that i’m getting. I know it will be a .tif or a .jpg but that its. is there a way to make it so it won’t care what extension it is, it will just duplicate whatever file is there regardless of it’s format? I tried looking up a solution first before asking since you already went through all that trouble but alas, I couldn’t. Maybe I just don’t know how to search for it with the right wording.

Thanks again soooo much!

Hi.

If you’re saying the names in the list definitely don’t include the extensions and that the actual file names definitely have either .jpg or .tif extensions, you could either go for the first file in each containing folder whose name begins with what you’ve got or, to avoid any possibility of mistaken identity, go for the first file whose name exactly matches what you’ve got plus one of the two possible extensions. (You have to write some thing like ‘first file’ with the ‘whose’ filter syntax, even when you know there’s only going to be one.)


property rootPath : "Volume name:any:other:folders:Root folder:"

-- The droplet's 'open' handler. 'theseItems' will receive a list of all the files and folders involved in the drop, so it must be treated as a list even when there's only one item.
on open theseItems
	set destinationFolder to (choose folder with prompt "Choose a destination folder for the copied files.")
	
	-- Repeat with every dropped item.
	repeat with thisItem in theseItems
		-- Only act if this item's likely to be a text file.
		if ((thisItem as text) ends with ".txt") then
			-- Get the paragraphs of this text file.
			set fileList to paragraphs of (read thisItem)
			
			repeat with fileName in fileList
				if (count fileName) > 0 then
					-- Work out the path to the file's container folder.
					set containerPath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":"
					
					try
						-- Duplicate the first file in that folder whose name is what you've got plus either a .jpg or .tif extension.
						tell application "Finder" to duplicate (first file of folder containerPath whose name is (fileName & ".jpg") or name is (fileName & ".tif")) to destinationFolder replacing yes
					on error
						display dialog "There was some problem duplicating the file "" & fileName & "". It may not exist." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
					end try
				end if
			end repeat
		else
			-- If this item doesn't look like a text file, rub the user's nose in it.  ;)
			display dialog "The item "" & thisfile & "" doesn't have a ".txt" extension. It may not be a text file." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
		end if
	end repeat
end open

Fantastic!
Reading through the script it makes sense what you added.
I tried it but the script hangs now and doesn’t complete or even copy a single image over. I ended up making two different scripts one for jpeg and one for tif to get me by for now.

I tried multiple ways of saving my list of image names into different text formats. The only time I got it to work was from a two item list that I hand typed. almost every time the list of filenames will come from excel. Could a CSV file work better/more reliable?

Thanks again for all your help with this!

Not so fantastic then. :wink:

I’m not sure what the problem is. I set up a folder structure like yours on my other computer and ran the script on this one, copying over 200 files. It worked OK, although it did pause for thought occasionally. (Suspending BOINC on both machines improved the performance!) I found a bug where one of the error messages still contained a variable no longer set in the rewrite. I’ve now corrected that above.

The individual folders on your server probably contain many more files than the bare minimum I set up for testing, so your Finder could be struggling more than mine to filter for the relevant names. Here’s another version in which the Finder returns all the file names in each folder to the script and the core AppleScript language does the checking. It seems to be slightly faster on my system, but I can’t guarantee it’ll solve your hanging problem:


property rootPath : "Volume name:any:other:folders:Root folder:"

-- The droplet's 'open' handler. 'theseItems' will receive a list of all the files and folders involved in the drop, so it must be treated as a list even when there's only one item.
on open theseItems
	set destinationFolder to (choose folder with prompt "Choose a destination folder for the copied files.")
	
	-- Repeat with every dropped item.
	repeat with thisItem in theseItems
		-- Only act if this item's likely to be a text file.
		if ((thisItem as text) ends with ".txt") then
			-- Get the paragraphs of this text file.
			set fileList to paragraphs of (read thisItem)
			
			repeat with fileName in fileList
				if (count fileName) > 0 then
					-- Work out the path to the file's container folder.
					set containerPath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":"
					-- Also get the entire path to the file itself, less the extension.
					set filePath to containerPath & fileName
					
					try
						-- Get the names of all the files in the folder.
						tell application "Finder" to set allNames to name of files of alias containerPath
						-- Check which of the two relevant names is included and complete the path.
						if (allNames contains (fileName & ".jpg")) then
							set filePath to filePath & ".jpg"
						else if (allNames contains (fileName & ".tif")) then
							set filePath to filePath & ".tif"
						else
							-- No suitable name found.
							error
						end if
						-- Duplicate the relevant file.
						tell application "Finder" to duplicate alias filePath to destinationFolder replacing yes
					on error
						display dialog "There was some problem duplicating the file "" & fileName & "". It may not exist." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
					end try
				end if
			end repeat
		else
			-- If this item doesn't look like a text file, rub the user's nose in it.  ;)
			display dialog "The item "" & thisfile & "" doesn't have a ".txt" extension. It may not be a text file." buttons {"Stop", "Skip"} default button "Skip" cancel button "Stop" with icon caution
		end if
	end repeat
end open

Whatever produces a plain text file containing only the names (no headers, no trailing or leading spaces, no commas, etc.), one per line thus:

1234-5678
1235-6489
1347-9876
2765-4351
etc.

That one worked flawlessly! Thank you.
I was setting up my .txt file as you exemplified. Seems like this one works much better. I tried some of the versions that had issues before and they worked on this new script.

I think the last thing I need to figure out is a second script that does the reverse of this to where it would upload the contents of a folder to the server. You don’t need to help me on this one (unless you REALLY wanted to), you have done more than enough already!

I’ll try and keep you posted on the progress of the reverse script. will be a lot slower than your updates, for sure.

Thanks again

Ok, so this is as far as I got.

property rootPath : "Server File Path"

tell application "Finder"
	set startFolder to (choose folder with prompt "Choose a folder of images you want to move to the server.")
	set fileList to name of every file in startFolder
	
	repeat with fileName in fileList
		--Setting the destination path up. 
		set destinationPath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":"
		--Set the full path of current file to be copied
		set filePath to startFolder & fileName
		-- do the deed (Copy file to server)
		tell application "Finder" to duplicate filePath to destinationPath replacing yes
	end repeat
	
end tell

I get this error: error “Finder got an error: Can’t make "0102-first.txt" into type item.” number -1700 from “0102-first.txt” to item

Currently i’m just using a text file using local folders to test this out before I actually point it to my server.

I am at a complete loss as to what that means. I tried looking it up but i wasn’t able to find helpful examples from other peoples situations.

How can I get this to work? or am I just not even close?
Thanks

You are mixing up literal strings and file specifiers.

rootPath is a literal string representing a (HFS) path. It must start with a disk name and must be colon separated
startFolder is an alias file specifier (an object).
To concatenate a string path you have to coerce the alias to text.
The Finder cannot duplicate literal strings, it expects objects like item, file or folder, so add the appropriate keywords

Try something like this


property rootPath : "ServerName:folder:" -- the trailing colon is important !

set startFolder to (choose folder with prompt "Choose a folder of images you want to move to the server.") as text
tell application "Finder" to set fileList to name of every file in folder startFolder

repeat with fileName in fileList
	--Setting the destination path up. 
	set destinationPath to rootPath & text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":"
	--Set the full path of current file to be copied
	set filePath to startFolder & fileName
	-- do the deed (Copy file to server)
	tell application "Finder" to duplicate file filePath to folder destinationPath replacing yes
end repeat

Ok, I gave that a shot and I’m now getting this error:

does the folder at this location exist?

Macintosh HD:Users:local:ndog:Get Images Script:Send Image Script:FolderTest:01:02:

Oh, ok, file path was missing a folder.
Fixed it and it worked! Awesome! Thank you!

Just to post where I ended up with this. It now sets the root path depending on if it’s a grey scale image (noted in the file name with “gs” at the end" or if it’s color which is the normal set root path.

then if the folder isn’t there it will give a dialog prompt asking if you want to create the folders for that file.

I’m sure it could be written a bit more concise but it works.

set startFolder to (choose folder with prompt "Choose a folder of images you want to move to the server.") as text
tell application "Finder" to set fileList to name of every file in folder startFolder

repeat with fileName in fileList
	--Setting the destination path up. 
	set rootPath to "volume:folders:"
	set fileGS to text 10 thru 11 of fileName as text
	if fileGS is equal to "gs" then set rootPath to "volume:folders:"
	set folderOne to text 1 thru 2 of fileName
	set folderTwo to text 3 thru 4 of fileName
	set folderSet to text 1 thru 2 of fileName & ":" & text 3 thru 4 of fileName & ":"
	set destinationPath to rootPath & folderSet
	--Set the full path of current file to be copied
	set filePath to startFolder & fileName
	-- do the deed (Copy file to server)
	tell application "Finder"
		if not (exists folder destinationPath) then
			
			display dialog "the folder for " & fileName & " does not exist. Do you wan to create it?" buttons {"Stop", "Skip", "Yes"} default button "Yes" cancel button "Stop" with icon caution
			set button_pressed to button returned of the result
			if the button_pressed is "Skip" then
				-- do nothing
			else
				if not (exists folder (rootPath & folderOne)) then make new folder at rootPath with properties {name:folderOne}
				
				if not (exists folder destinationPath) then make new folder at rootPath & folderOne with properties {name:folderTwo}
				
				duplicate file filePath to folder destinationPath replacing yes
			end if
		else
			duplicate file filePath to folder destinationPath replacing yes
			
		end if
	end tell
end repeat

Thanks a gain SefanK and Nigel for the excellent help! I now want to figure out more scripts to write to make my life easier!

Ndog

This is a easier version of the script using the shell command ditto which is able to create intermediate directories on-the-fly


set startFolder to (choose folder with prompt "Choose a folder of images you want to move to the server.") as text
tell application "Finder" to set fileList to name of every file in folder startFolder

repeat with fileName in fileList
	--Setting the destination path up. 
	set rootPath to "volume:folders:"
	tell fileName to set {fileGS, folderA, folderB} to {text 10 thru 11, text 1 thru 2, text 3 thru 4}
	if fileGS is equal to "gs" then set rootPath to "volume:folders:"
	--Set the full path of current file to be copied
	set sourceFile to quoted form of (POSIX path of startFolder & fileName)
	set destinationPath to quoted form of (POSIX path of rootPath & folderA & "/" & folderB & "/" & fileName)
	do shell script "/usr/bin/ditto " & sourceFile & space & destinationPath
end repeat


Oh Awesome, It seems to also not tie up Finder while it runs. Nice bonus!

Now Since it’s hard to tell where it is in the script while it’s running I thought Maybe I"ll just have it change the label color of the file in the source folder so I can visually watch it churn through the images.

set startFolder to (choose folder with prompt "Choose a folder of images you want to move to the server.") as text
tell application "Finder" to set fileList to name of every file in folder startFolder

repeat with fileName in fileList
	--Setting the destination path up. 
	set rootPath to "volume:folder:"
	tell fileName to set {fileGS, folderA, folderB} to {text 10 thru 11, text 1 thru 2, text 3 thru 4}
	if fileGS is equal to "gs" then set rootPath to "volume:folder:"
	--Set the full path of current file to be copied
	set sourceFile to quoted form of (POSIX path of startFolder & fileName)
	set destinationPath to quoted form of (POSIX path of rootPath & folderA & "/" & folderB & "/" & fileName)
	do shell script "/usr/bin/ditto " & sourceFile & space & destinationPath
        -- set the label of sourcefolder file to green
	tell application "Finder" to set label index of file sourceFile to 5
end repeat

I get an error saying It can’t set the file to 5.

thoughts?

the Finder needs an HFS path

tell application "Finder" to set label index of file (startFolder & fileName) to 5

Ah, I figured that’s what the issue was but I didn’t think about formatting it like that I was stuck on trying to make what I had written into something that would work.

And I can confirm that the new script you wrote me works alot faster than what I had written. Thanks again!

I am curious though. A lot of these images will be in groups of 10-100 of the same first 4 numbers (so they will be in the same folder). Would there be a way to move all the files that start with, for example 0024, at one time instead of individually? would that be quicker/better? I mean right now it runs pretty quick moving about 250 images or so in about 10min So, what i’ve got works very well. Just curious is all.

Thanks Again.

Hey guys and gals, I’m back with another question regarding this script.
Here’s the script code:

set startFolder to (choose folder with prompt "Choose a folder of images you want to move to the server.") as text
tell application "Finder" to set fileList to name of every file in folder startFolder

repeat with fileName in fileList
	--Setting the destination path up. 
	set rootPath to "graphics:images:color:"
	tell fileName to set {fileGS, folderA, folderB} to {text 10 thru 14, text 1 thru 2, text 3 thru 4}
	if fileGS contains "gs" then set rootPath to "graphics:images:Lineart:"
	--Set the full path of current file to be copied
	set sourceFile to quoted form of (POSIX path of startFolder & fileName)
	set destinationPath to quoted form of (POSIX path of rootPath & folderA & "/" & folderB & "/" & fileName)
	do shell script "/usr/bin/ditto " & sourceFile & space & destinationPath
	tell application "Finder" to set label index of file (startFolder & fileName) to 6
end repeat


Quick summary of the script since it’s been awhile:

What this does is it takes a folder of images and uploads them to our file server.
the images are named as such: 1234-1234.tif and 1234-1234gs.tif and 1234-1234_01.tif and 1234-1234_01gs.tif
the files that end in gs are the greyscale versions of the images and the images with _01 are the alternate images (different angle of product). The greyscale images get put into a greyscale folder.
The folder structure of the image server uses the filename (which is the product sku number). so file 1234-1234 will be placed into a folder sturcture: 12/34/1234-1234.tif
So the script deconstructs the filename into the file path.

What I need is to add a “delete any old files” that are there in the destination folder. since there are different filetypes of the old images (now they are all tifs). Some of the original images might be jpeg or png or tif.

My assumption is that this delete operation needs to take place before we copy the new image over. That way i’m not deleting the new one. I’m not familiar doing stuff with POSIX paths so i’m not sure what to do here.

Any help would be appreciated.

Secondly, in my description of the file names I mentioned names such as, 1234-1234_01.tif. the _01 is a new addition to our images and so I modifed the script to define the fileGS to text 10 thru 14 of the file name (so it included the _01 if it’s there) and then the if fileGS contains “gs”. it use to be fileGS to text 10 thru 11 and then the if fileGS is equal to “gs”. i’m not sure if the command “contains” does what I am asking it to do. so if that’s wrong i’m not sure what it should be.

Thanks for any help or direction you can give me.