Change file name while duplicating

I have situations where I need to duplicate a file and slightly modify the name of the new file. Let’s say I have a file selected in a Finder window named 123MainBWHT.jpg. The BW in the name stands for “Black & White”. I’m writing a script that I can attach to a QuicKey. When I run the script, it would duplicate the file to the same folder but rename it 123Main4cHT.jpg (4c for “Four Color”). Here’s what I have so far, but it gets hung up when I try to rename the duplicated file:

property colorList : {{"BWHT.jpg", "4cHT.jpg"}, {"4cHT.jpg", "BWHT.jpg"}, {"BWHTM.jpg", "4cHTM.jpg"}, {"4cHTM.jpg", "BWHTM.jpg"}, {"DDN4c.jpg", "DDNBW.jpg"}, {"DDNBW.jpg", "DDN4c.jpg"}}

tell application "Finder"
	set ProcessList to selection
	repeat with i from 1 to (count of ProcessList)
		set theItem to (item i of ProcessList) as string
		set currName to theItem
		
		repeat with thisItem in colorList
			if theItem ends with the first item of thisItem then
				set nameChange to second item of thisItem
				set CurrCount to count currName
				set endCount to count characters of nameChange as string
				set minusEnd to CurrCount - endCount
				set shortName to (text 1 thru minusEnd) of currName
				set newName to shortName & nameChange
				duplicate theItem as alias
				set duplicateItem to shortName & " copy.jpg"
				set name of file duplicateItem to newName
			end if
			
		end repeat
	end repeat
end tell

Can anyone offer a fix or a work around? Is there a shell script that could solve this problem?

Thanks - slimjim5811

How about this?


property colorList : {{"BWHT.jpg", "4cHT.jpg"}, {"4cHT.jpg", "BWHT.jpg"}, {"BWHTM.jpg", "4cHTM.jpg"}, {"4cHTM.jpg", "BWHTM.jpg"}, {"DDN4c.jpg", "DDNBW.jpg"}, {"DDNBW.jpg", "DDN4c.jpg"}}

tell application "Finder"
	set ProcessList to selection
	repeat with i from 1 to (count of ProcessList)
		set theFile to (item i of ProcessList) as string
		set ASTID to AppleScript's text item delimiters
		set AppleScript's text item delimiters to ":"
		set fileName to last text item of theFile
		set filePath to text items 1 thru -2 of theFile as string
		set AppleScript's text item delimiters to ASTID

		repeat with thisItem in colorList
			if theFile ends with the first item of thisItem then
				set nameChange to second item of thisItem
				set fileName to (text items 1 thru -9 of fileName)
				do shell script ("cp " & (quoted form of POSIX path of theFile) & " " & quoted form of POSIX path of filePath & ":" & fileName & nameChange)
			end if
			
		end repeat
	end repeat
end tell


Scott -
That sorta worked. It duplicated the file and swapped the name correctly, but it did it in the folder 1 level out. I hope that makes sense. I have a folder called “REPhotos” and in that folder is “Raw” and “Fixed” folders. The file I’m trying to work on is in “Raw”. Your script worked great except the end result was a file in the REPhotos folder named “Raw RE Photos/123Main4cHT.jpg” . It seems like the path is not concatenating correctly.

Ok, I think I know what I did wrong. Try this instead.


property colorList : {{"BWHT.jpg", "4cHT.jpg"}, {"4cHT.jpg", "BWHT.jpg"}, {"BWHTM.jpg", "4cHTM.jpg"}, {"4cHTM.jpg", "BWHTM.jpg"}, {"DDN4c.jpg", "DDNBW.jpg"}, {"DDNBW.jpg", "DDN4c.jpg"}}

tell application "Finder"
	set ProcessList to selection
	repeat with i from 1 to (count of ProcessList)
		set theFile to (item i of ProcessList) as string
		set ASTID to AppleScript's text item delimiters
		set AppleScript's text item delimiters to ":"
		set fileName to last text item of theFile
		set filePath to (text items 1 thru -2 of theFile) as string
		set AppleScript's text item delimiters to ASTID
		set filePath to (filePath & ":")
		
		return filePath
		
		repeat with thisItem in colorList
			if theFile ends with the first item of thisItem then
				set nameChange to second item of thisItem
				set fileName to (text items 1 thru -9 of fileName)
				do shell script ("cp " & (quoted form of POSIX path of theFile) & " " & quoted form of POSIX path of filePath & fileName & nameChange)
			end if
			
		end repeat
	end repeat
end tell

If the file names really are of the kind given, then perhaps I could suggest a simpler version:


property colorList : {{"BWHT.jpg", "4cHT.jpg"}, {"4cHT.jpg", "BWHT.jpg"}, {"BWHTM.jpg", "4cHTM.jpg"}, {"4cHTM.jpg", "BWHTM.jpg"}, {"DDN4c.jpg", "DDNBW.jpg"}, {"DDNBW.jpg", "DDN4c.jpg"}}
set tDest to ((path to desktop folder as text) & "Dups")

tell application "Finder"
	set ProcessList to (get selection)
	repeat with P in ProcessList
		set fPath to P as alias
		set iName to name of fPath
		repeat with i in colorList
			if iName contains item 1 of i then
				set O to (offset of (item 1 of i) in iName)
				set newName to (text 1 thru (O - 1) of iName) & item 2 of i
				set D to duplicate fPath to alias tDest
				set name of D to newName
			end if
		end repeat
	end repeat
end tell

Adam,

What is the offset command doing for you?

And why did you set a destination folder? I thought slim was trying to save directly into the same directory?

A slight variation on this type of routine is to use a pair of lists, rather than a list of pairs:


property origList : {"BWHT.jpg", "4cHT.jpg", "BWHTM.jpg", "4cHTM.jpg", "DDN4c.jpg", "DDNBW.jpg"}
property dupeList : {"4cHT.jpg", "BWHT.jpg", "4cHTM.jpg", "BWHTM.jpg", "DDNBW.jpg", "DDN4c.jpg"}

tell application "Finder" to repeat with theItem in (get selection)
	set currName to theItem's name
	tell origList to repeat with i from 1 to count
		tell item i to if currName ends with it then
			set (duplicate theItem)'s name to currName's text 1 through -((count) + 1) & dupeList's item i
			exit repeat
		end if
	end repeat
end repeat

Offset of txt1 in txt2 finds the location of the first character of the string txt1 in the string txt2 if it exists, returning 0 if it is not found, and the integer position if found. Thus “text 1 thru ((offset of txt1 in txt2) - 1) of txt 2” is all the text of txt2 (the original name) before the piece containing txt1 (the color code string) so I can just concatinate the new code string to that and have the new name.

I chose a destination folder because duplicating a file in its own folder adds the word “copy” to it’s name, and I didn’t want to have to remove it. If I had noticed the requirement for “same folder” (which I had missed), I would have created the new folder, done the duplications to it, changed the names (using what I posted), and then moved them back to the original folder (having first found their container from the alias), and deleted the scratch folder (I could have duplicated to the desktop and then moved back, and the file would have flashed on the screen). Kai’s method is better in this regard because the unix function cp allows him to change the name in the process of copying the file, which the Finder can’t do.

Clearer?

Adam

Scott - Your revision worked great! I had to tweak it a little, since most of the items in “colorList” are 8 characters but some are 9 (it still needed a way to count the characters of “nameChange”).

I’m very pleased with the results. Thank you all for assisting me.

Here’s my final script that works exactly how I wanted it to. However, it seems a little slow if multiple items are selected…any thoughts by looking at it?

property colorList : {{"BWHT.jpg", "4cHT.jpg"}, {"4cHT.jpg", "BWHT.jpg"}, {"BWHTM.jpg", "4cHTM.jpg"}, {"4cHTM.jpg", "BWHTM.jpg"}, {"DDN4c.jpg", "DDNBW.jpg"}, {"DDNBW.jpg", "DDN4c.jpg"}}

tell application "Finder"
	set ProcessList to selection
	repeat with i from 1 to (count of ProcessList)
		set theFile to (item i of ProcessList) as string
		set ASTID to AppleScript's text item delimiters
		set AppleScript's text item delimiters to ":"
		set fileName to last text item of theFile
		set filePath to (text items 1 thru -2 of theFile) as string
		set AppleScript's text item delimiters to ASTID
		set filePath to (filePath & ":")
		set currName to theFile
		
		repeat with thisItem in colorList
			if theFile ends with the first item of thisItem then
				set nameChange to second item of thisItem
				set CurrCount to count currName
				set endCount to count characters of nameChange as string
				set minusEnd to CurrCount - endCount
				set shortName to (text 1 thru minusEnd) of currName as string
				set newName to shortName & nameChange
				do shell script ("cp " & (quoted form of POSIX path of theFile) & " " & quoted form of POSIX path of newName)
			end if
			
		end repeat
	end repeat
end tell

slimjim5811

Why all the work with Text Item Delimiters? I doubt this would speed it up, but to me it looks cleaner…

set ProcessList to selection
repeat with aFile in ProcessList
	set theProps to properties of aFile
	set {fileName, filePath} to {name, container} of theProps
	--yadda yadda
end repeat

Mostly, because I was thinking in strings and not aliases.

:expressionless:

slim: Here’s my two cents…


on open theFiles
	set TID to AppleScript's text item delimiters
	tell application "Finder"
		(*
		Optionally you can have the ability to specify the parameters---------------------
		set textToReplace to text returned of (display dialog "Enter the characters to change:" default answer "BW")
		set replacingText to text returned of (display dialog "Enter the characters to change:" default answer "4c")
		 *)
		repeat with thisFile in theFiles
			set whatToDoNext to ""
			set {fileContainer, fileName} to {container of thisFile, (name of thisFile as Unicode text)}
			considering case
				if fileName contains "BW" then
					set {textToReplace, replacingText} to {"BW", "4c"}
				else if fileName contains "4c" then
					set {textToReplace, replacingText} to {"4c", "BW"}
				else
					set {textToReplace, replacingText} to {"", ""}
				end if
			end considering
			if textToReplace ≠ "" then
				set AppleScript's text item delimiters to textToReplace
				set fileName to every text item of fileName
				set AppleScript's text item delimiters to replacingText
				if not (exists (file (fileName as Unicode text) of (fileContainer as alias))) then
					set dupeFile to duplicate thisFile to fileContainer
					set name of dupeFile to fileName as Unicode text
				else
					set whatToDoNext to button returned of (display dialog "A file with the name " & fileName & " already exists here." buttons {"Continue", "Quit"} default button "Continue")
				end if
			else -- Strings not found...
				set whatToDoNext to button returned of (display dialog "This file: " & fileName & " does not have the requires strings to replace." buttons {"Continue", "Quit"} default button "Continue")
			end if
			if whatToDoNext = "Quit" then return
		end repeat
	end tell
	set AppleScript's text item delimiters to TID
end open

Save it as an Application and you have a quick little name changer droplet. It’s not fully error trapped but it does prompt you if it has a name collision with an existing file or doesn’t have the character to replace. Any of this could be easily changed to allow for more “automation”. Optionally, there’s code for you to specify the parameters for each drop you make.

Cheers,
Jim Neumann
BLUEFROG

BLUEFROG -
Thanks for the ideas. I think I’m set on my final script. Your way is cool, but i don’t need all the Dialog boxes. There’s only 1 choice really: if it’s BW, change it to 4c and vice versa.

I’m acutally a huge fan of QuicKeys, more so than droplets (just a personal preference). I have about 11 or 12 QuicKeys set up as a part of my workflow, which run various applescripts to make my daily work easier. Love 'em, love 'em, love 'em.

Thanks - slimjim5811

Not a problem slim. I actually gave you a little more than I intended to (I can be a bit of a showoff:P) The main point I was trying to make was that the script shown…

is terribly inefficient in the way it gets the filename and the shell script seems like overkill for a simple rename. (No offense intended.)

The crux of my script is this…


set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to "BW"
tell application "Finder" to set fileName to every text item of (name of (selection as alias) as Unicode text) -- this is for a single object, not multiple files
set AppleScript's text item delimiters to "4c"
set fileName to (fileName as Unicode text)
set AppleScript's text item delimiters to TID

fileName

Select a single file with “BW” in its name and run this. You can see from the results in Script Editor that it returns the name with “4c” in place of “BW”. Very small, very simple. (And it would be child’s play to rename that file.)

I hope I didn’t offend you (or any other contributor to this thread). I’m just offering a simpler way so if you have to do something similar again you’ll have another option to try.

Best Regards,
Jim Neumann
BLUEFROG

I’ll bite, because I want to learn.

“Inefficent”, how?

And “overkill”? What’s the difference if the rename is done as a shell script or in the finder?

I really started a heated debate with my crappy little script.

Bluefrog - No offense taken from me. I’m here to learn. I tried out your script and it seemed to work fine.

Not heated, slim.

I’m always looking to learn to improve my script writing.

Scott:

I hope I didn’t get your hackles up… I’m just offering suggestions. (I come in peace :D)

The inefficiency is in this: The way you have structured finding the filename involves parsing the alias as a string. Finder offers the simpler route of just asking for the filename. (Note: There are times when I have found this method valid. I just don’t in this particular case.)


tell application "Finder" to set fileName to (name of (selection as alias))

The Finder also lets you rename the file just as simply.


tell application "Finder" to set (name of (selection as alias)) to "ScottsNewFile.jpg"

You’re also parsing the container from the string but you don’t really need it. As you can see above, you can rename the file in place without ever needing to know the container because the container is implicit with (selection as alias). Run this:

tell application "Finder" to get (selection as alias)

and you’ll see that the Finder knows what you’re selecting and where it is. Even if you were duplicating the file (which I thought we were?!) you still do not need the container. And if you did, it’s as simple as…


tell application "Finder" to set fileContainer to container of (selection as alias)

as a folder (or add as alias to the end to get the container as an alias).

So having to use “do shell script” and “POSIX paths”, etc. seems like more work than it needs to be when simpler methods are provided through Finder.

As far as how to change the name, your method is also valid. My suggestion is just a quick delimiter trick that doesn’t require separating the extension from the name or any such thing.

Once again, no offense intended.
-Jim

P.S. My examples aren’t meant to be condescending either. They’re just snippets.

Slim: what do you mean “it seemed to work fine” !?! Just kidding. :stuck_out_tongue:

No offense taken, at all.

Thank you for the breakdown. A little education goes along way.

In the words of Stan from South Park; “You know… I learned something today.”

:cool:

Hi guys.

If you’re still up for it, I’ll throw in a couple of additional thoughts.

Another point to watch out for is that of introducing unnecessary operations, which can reduce performance “ and might even, under certain circumstances, produce spurious results.

Take this code, for example:


set nameChange to "4cHT.jpg"
set endCount to count characters of nameChange as string
--> 8

It might look innocuous enough, but let’s take a look at what it actually does:


"4cHT.jpg" (* define a string value - or get it from some source *)
--> "4cHT.jpg"

set nameChange to result (* assign the string value to a variable *)
--> "4cHT.jpg"

characters of nameChange (* itemise the string as a list *)
--> {"4", "c", "H", "T", ".", "j", "p", "g"}

result as string (* coerce the list to string *)
--> "4cHT.jpg"

count result (* count the string *)
--> 8

set endCount to result (* assign the integer value to a variable *)
--> 8

Compare that with…


set nameChange to "4cHT.jpg"
set endCount to count nameChange
--> 8

… which breaks down to:


"4cHT.jpg" (* define a string value - or get it from some source *)
--> "4cHT.jpg"

set nameChange to result (* assign the string value to a variable *)
--> "4cHT.jpg"

count nameChange (* count the string *)
--> 8

set endCount to result (* assign the integer value to a variable *)
--> 8

Apart from taking longer to execute, the intervening conversions could give unpredictable results. If the text being processed is encoded (such as Unicode text), a string coercion would strip out much of the encoding (which may or may not matter). And if AppleScript’s text item delimiters happened to be set to some value other than {“”} (or “”), then this kind of confusion might result:


set text item delimiters to "a mystery string..."

set nameChange to "4cHT.jpg"
set endCount to count characters of nameChange as string
--> 141

Similarly, something like this also performs text-to-list-to-string manipulations…

set filePath to (text items 1 thru -2 of theFile) as string

… while this variation extracts the relevant text directly, using the positional references purely as markers:

set filePath to text 1 thru text item -2 of theFile

Just one further thought, which can make a significant difference to the time it takes to iterate through a list. Once the relevant item in a single-item search has been found, don’t forget to exit the repeat loop.

Details like these are pretty common gotchas, and there’s no shame in falling into such traps. (We’ve all done it at one time or another). But they might be worth bearing in mind for future use…

:slight_smile: