multi file copy relative to source location

I have a multiple file copy I need to do many times in my development and need a script to do this. I don’t know if a shell script or applescript would be better for this but here is what i need to do.

I have a file location here:

container/origin/source_file.txt

also in the container folder I have several folders like this:

container/target1/
container/target2/
container/target3/

so the whole thing looks like this:

container/origin/source_file.txt
container/target1/source_file.txt
container/target2/source_file.txt
container/target3/source_file.txt

each of these target folders will also have an origin_file.txt file inside that needs to be overwritten by the file Im copying from the origin folder. The structure will always be the same so I basically need the script to use the copy location relevant to the source file each time. The file name will never change. The structure of the folders and their names will never change either.

So the copy source file location will be:

container/origin/source_file.txt

copied multiple times to these locations relative to the source files location:

…/target1/
…/target2/
…/target3/

How would I do this with a script so I don’t have to manually copy and paste it 3 times?

Thanx

I wrote up three versions: shell, Finder, System Events (though this last one does not work on my 10.4 system).

I call the destination files “cousins” because they are related to the original file in the same way a person is related to their ˜first’ cousins. Likewise, your ‘container’ folder is called “grandparent” in some of the code.

copyToCousins_shell(sourceFileAlias, {"target1", "target2", "target3"})

to copyToCousins_shell(fileRef, cousinParentNames)
	if class of cousinParentNames is not list then error "Second arg must be a list"
	set args to {quoted form of POSIX path of fileRef}
	repeat with folderName in cousinParentNames
		set end of args to quoted form of folderName
	end repeat
	set text item delimiters to {" "}
	set cmds to "
set -u
set -- " & (args as Unicode text) & "
file=\"$1\"
shift
dir=\"$(dirname \"$file\")\"
for dir_name in \"$@\"; do
	dir_path=\"$dir\"/../\"$dir_name\"/
	# comment-out the next line to skip dirs that do not already exist
	test -d \"$dir_path\" || mkdir \"$dir_path\"
	cp -p \"$file\" \"$dir_path\"
done
"
	do shell script cmds
end copyToCousins_shell

to copyToCousins_Finder(fileRef, cousinParentNames)
	if class of cousinParentNames is not list then error "Second arg must be a list"
	tell application "Finder"
		set theFile to fileRef
		set grandParent to container of container of theFile
		repeat with folderName in cousinParentNames
			if not (exists folder folderName of grandParent) then
				-- comment-out the next line to skip folders that do not already exist
				make new folder at grandParent with properties {name:folderName}
			end if
			if exists folder folderName of grandParent then
				duplicate theFile to folder folderName of grandParent with replacing
			end if
		end repeat
	end tell
end copyToCousins_Finder

to copyToCousins_SystemEvents(fileRef, cousinParentNames)
	-- While copying to each folder, Finder makes a sound (just like Command-D or File > Duplicate does).
	-- Also, this creates .DS_Store files in the target folders.
	if class of cousinParentNames is not list then error "Second arg must be a list"
	tell application "System Events"
		set theFile to disk item (POSIX path of fileRef)
		set grandParent to container of container of theFile
		repeat with folderName in cousinParentNames
			if not (exists folder folderName of grandParent) then
				-- comment-out the next line to skip folders that do not already exist
				make new folder at end of grandParent with properties {name:folderName} -- Note, the odd "at end of grandParent" instead of simply "at grandParent"; maybe versions newer than 10.4 won't need this.
			end if
			if exists folder folderName of grandParent then
				-- This refuses to work in 10.4, maybe it works in later versions.
				-- No combination of "the end of" or "end of" with "folder ." or "(get folder .)" works on 10.4.
				duplicate theFile to folder folderName of grandParent
			end if
		end repeat
	end tell
end copyToCousins_SystemEvents

Model: iBook G4 933
AppleScript: 1.10.7
Browser: Safari 4.0.3 (4531.9)
Operating System: Mac OS X (10.4)

Wow Chrys - thanx so much for actually typing these out for me. You are a god.

Ive been working a bit with the Applescript version as it seems to be the easiest for me to understand. My scripting knowledge is very limited and I mostly work in XML, PHP and even with these I work very lightly in them. Im mostly a visual designer and graphic design guy but I often have to dabble in the coding world to make things work in my development. With your applescript it seems as if the script is testing to see whether or not the folders exist where the file is to be copied. The folders will always exist and will always have the same names - the script will never need to create a folder where one doesn’t exist. The grandfather folder will always have the same name as well. Am I interpreting the code correctly? Im also a bit confused as to where I create the list of folders or the code in order to copy to all the different folders. Where do I name the folders specifically in the code?

I got another reply elsewhere for this bit of code:

set basePath to (choose folder with prompt "Choose the container folder") as text
set targetFolders to {"target1", "target2", "target3"}
set theSourceFile to basePath & "origin:source_file.txt"

tell application "Finder"
	repeat with thisFolder in targetFolders
		duplicate file theSourceFile to folder (basePath & thisFolder)
	end repeat
end tell

It doesn’t work unless the target folders are empty though. This is a problem. What is the missing code that would allow automatic overwriting of existing files? Also how could I make this code allow me to drag the folder onto it to establish the basePath rather than having to select it in the choose file/folder dialog box each time?

Yes, I think you understand. The version I wrote is very generic because it was fairly easy to make it so, and because it seems unlikely that you would never have to make any changes.

There are two inputs to any of the handers that I wrote: the file to copy (usually as an AppleScript alias) and a list of the target folder names. To copy a file to target1, target2, and target3, you could use the following:


-- Define the target folder names
set targets to {"target1", "target2", "target3"}

-- Let the user choose the source file (and folder)
set targets_string to first item of targets
repeat with target in rest of targets
	set targets_string to targets_string & ", " & target
end repeat
set sourceFileAlias to choose file with prompt "Choose a file (in the source folder) to copy to the target folders: " & targets_string

-- Copy the chosen file to the target folders
copyToCousins_Finder(sourceFileAlias, targets)

It needs with replacing on the Finder duplicate command (my Finder variant has it).

Instead of using choose folder, you would put the code in an open handler. When a script with an open handler is saved as an application, it is saved as a “droplet” that lets the user drop files onto it.

on open someItems
	repeat with someItem in someItems
		if not folder of (info for someItem) then
			display dialog "Dropped item: " & POSIX path of someItem & " is not a folder. Skipping it."
		else
			set basePath to someItem as Unicode text
			set targetFolders to {"target1", "target2", "target3"}
			set theSourceFile to basePath & "origin:source_file.txt"
			
			tell application "Finder"
				repeat with thisFolder in targetFolders
					duplicate file theSourceFile to folder (basePath & thisFolder) with replacing
				end repeat
			end tell
		end if
	end repeat
end open

Thanx Chris - I may have to abandon the Applescript version of this and use the shell script version instead. The problem Im having is that I use Path Finder to work in all my file operations so when I drag files and folders onto a script from within Path Finder, it calls the finder to carry out an action and I often get “connection errors” due to Path Finder trying to communicate with the Finder. Path Finder apparently doesn’t have a very polished up Applescript implementation yet so shell scripting may be my only option outside of doing everything inside the finder itself.

So going back to your shell script - how would I implement that into an automator action and plug in my folder and file names from the example?

Thanx very much for all your time, talent and patience with this Chris - it is appreciated.

That is one reason I tried to get System Events to work. It is too bad about the lack of full file manipulation support in 10.4. The AppleScript Release Notes for 10.5 and 10.6 do not seem to mention any improvement in this area, but it is possible that some slight variation on my System Events handler might work on those systems.

I have never really used Automator, so I do not have any advice to offer there.

If you can live with a ˜droplet’, the same thing can be accomplished with AppleScript saved as an application (application bundle prior to Snow Leopard). This might work for you (the shell code has been updated slightly to handle copying ‘packages’ as well as plain files:

(*
 * Assume a folder structure like this:
 *
 * container/
 * container/origin/
 * container/origin/source_file.txt
 * container/target1/
 * container/target2/
 * container/target3/
 *
 * This open handler will copy container/origin/source_file.txt to target{1,2,3}/source_file.txt.
 * If a folder is dropped, it is assumed to be the container folder. The file origin/source_file.txt will be copied to the targets.
 * If a file (or package) is dropped, it is assumed to be the source file. It will be copied to the target folders next to the dropped item's folder.
 *)
on open someItems
	
	-- Customize these variables
	set path_from_container_to_source_file to "origin/source_file.txt"
	set targets to {"target1", "target2", "target3"}
	-- End of customizable variables
	
	repeat with someItem in someItems
		set info to info for someItem
		if folder of info and not package folder of info then
			-- Dropped a folder: assume it is the "container" folder.
			set sourceFileAlias to missing value
			try
				set sourceFileAlias to ¬
					alias POSIX file ((POSIX path of someItem) & path_from_container_to_source_file)
			on error
				display dialog "Unable to find " & path_from_container_to_source_file & " in " & POSIX path of someItem
			end try
			if sourceFileAlias is not missing value then ¬
				try
					copyToCousins_shell(sourceFileAlias, targets)
				on error m number n
					display dialog "Error number " & n & " (" & m & ") while copying files (dropped " & POSIX path of someItem & ")." with title "Error During Copy"
				end try
		else
			-- Dropped a file: assume it is the source file.
			set sourceFileAlias to someItem
			try
				copyToCousins_shell(sourceFileAlias, targets)
			on error m number n
				display dialog "Error number " & n & " (" & m & ") while copying files (dropped " & POSIX path of someItem & ")." with title "Error During Copy"
			end try
		end if
	end repeat
end open

to copyToCousins_shell(fileRef, cousinParentNames)
	if class of cousinParentNames is not list then error "Second arg must be a list"
	set args to {quoted form of POSIX path of fileRef}
	repeat with folderName in cousinParentNames
		set end of args to quoted form of folderName
	end repeat
	set text item delimiters to {" "}
	set cmds to "
set -u
set -- " & (args as Unicode text) & "
file=\"$1\"
shift
dir=\"$(dirname \"$file\")\"
for dir_name in \"$@\"; do
   dir_path=\"$dir\"/../\"$dir_name\"/
   # comment-out the next line to skip dirs that do not already exist
   test -d \"$dir_path\" || mkdir \"$dir_path\"
   cp -pr \"${file%/}\" \"$dir_path\"
done
"
	do shell script cmds
end copyToCousins_shell

Awesome!:slight_smile:

Works like a charm. Thanks very much for your time Chrys - much appreciated.