AppleScript dead slow in moving files to folder - applescript

I'm trying to move a bunch of files into folders, based on the file creation date. Using the script below, the rate of move is about 1 file per second. 64000 files to go.
Script timed out when I tried
set myFiles to items of myFolder whose name ends with "jpg"
repeat with aFile in myFiles
So now I process file by file and only checking folder existing when the date comparison failed sped up the process only marginally. I've tried the obvious
set myDayBuddies to items of myFolder whose creation date is theFileDate
but this too, did not help really much. I suspect Finder reads the whole folder content time and time again, creating huge overhead.
What is the proper method of speeding up this any further? In PHP I know I can read entries from a directory stream, one by one.
Script:
tell application "Finder"
set myFolder to choose folder
set prevDateString to ""
repeat while true
try
set aFile to first item of myFolder whose name ends with "jpg"
set theFileDate to (the creation date of aFile)
set theDateString to my composedate(theFileDate)
if (theDateString is not prevDateString) then
if not (exists folder ((myFolder & theDateString) as text)) then
make new folder at myFolder with properties {name:theDateString}
end if
set prevDateString to theDateString
set destPath to (((myFolder as text) & theDateString) as alias)
end if
move aFile to destPath
on error
return
end try
end repeat
end tell
on composedate(aDate)
set y to (year of aDate as integer) as string
set m to (month of aDate as integer) as string
set d to (day of aDate as integer) as string
if length of m < 2 then
set m to "0" & m
end if
if length of d < 2 then
set d to "0" & d
end if
set myDateString to y & m & d
return myDateString
end composedate

System Events was created to take much of the load off the Finder. The Finder is slow so use system events when possible. I'm not sure how it will perform on a folder with 64000 files but give it a try.
set myFolder to choose folder
tell application "System Events"
set f to files of myFolder whose name extension is "jpg"
end tell
And if system events is still too slow then applescript alone isn't appropriate for such a large folder. You'll have to go to using the command line even though you said you don't want to. Something like this should be faster...
set myFolder to (choose folder) as text
set jpgFiles to paragraphs of (do shell script "ls " & quoted form of POSIX path of myFolder & " | grep 'jpg$'")
repeat with aFile in jpgFiles
set filePath to myFolder & aFile
tell application "System Events"
set theFileDate to creation date of file filePath
-- do more stuff
end tell
end repeat

Related

Combining two (applescript) scripts into one

I'm trying to simplify my workflow by combining the following two applescripts into one, they work fine separately but it'd be more efficient to combine them.
In short, script A trims a file name into the last 3 characters and Script B adds the folder name (where the files reside) to the file name.
This might be a very simple fix but I'm script-writing challenged so any help is welcomed.
SCRIPT A:
on open whichFile
repeat with aFile in whichFile
tell application "Finder"
set filename to name of aFile
set name of aFile to ((characters -1 thru -7 of filename) as string)
--set name of whichFile to ((characters 1 thru -4 of filename) as string) --trim last 3
end tell
end repeat
end open
SCRIPT B
on open theDroppedItems
repeat with a from 1 to length of theDroppedItems
set theCurrentDroppedItem to item a of theDroppedItems
set theCurrentDroppedItem to theCurrentDroppedItem as string
tell application "System Events"
set folderPath to theCurrentDroppedItem as string
--display dialog (folderPath)
set AppleScript's text item delimiters to ":"
set newFileName to (text item -4 of folderPath as string) & "-" & (text item -2 of folderPath as string) & "-" & (text item -1 of folderPath as string)
--display dialog (newFileName)
--rename file
set fileAlias to (theCurrentDroppedItem) as alias
set the name of fileAlias to newFileName
end tell
end repeat
end open
You can start by opening a new Script Editor document for the combined script. The open handler is passed a list of file items, so you can add a new handler declaration with an empty repeat statement that steps through the dropped items. From there, just identify the statements that perform the various operations (trim name, get folder name, etc), and copy them into the new open handler's repeat statement, editing as needed to use consistent variable names.
Once the new script is running, then you can look at optimizing it by combining and/or rearranging statements that may be doing the same thing in the different scripts. It can also be helpful to organize functions into their own handlers, such as the getNamePieces handler below. I also like to add a run handler with a choose file dialog so that you can test without having to drag items onto a droplet.
Note that the file name includes any extension, so you should break it apart in order to work with just the name part. There is also a bit of unnecessary thrashing about in the script that gets the folder name, so after cleaning it up your script could look something like:
on run
open (choose file with multiple selections allowed)
end run
on open droppedItems
repeat with anItem in droppedItems
set {folderPath, theName, extension} to getNamePieces from anItem
set trimmedName to text -1 thru -3 of theName -- work with just the name part
tell application "System Events"
set folderName to name of disk item folderPath
set newName to folderName & "-" & trimmedName & extension -- assemble the pieces
log newName -- for testing
# set name of anItem to newName -- uncomment to go live
end tell
end repeat
end open
to getNamePieces from someItem -- return the containing folder path, the name, and the extension
tell application "System Events" to tell disk item (someItem as text)
set theContainer to path of the container
set {theName, extension} to {name, name extension}
end tell
if extension is not "" then
set theName to text 1 thru -((count extension) + 2) of theName -- just the name part
set extension to "." & extension
end if
return {theContainer, theName, extension}
end getNamePieces

Applescript - Recursively searching through directories

I have a script that was generously created by another user here, but I've found that it doesn't read recursively through directories. The goal of the script is to read through all sub directories of a directory selected in the Finder, and to write out any absolute paths of files that are 255 characters or longer (path not filename). This is to find files with absolute path lengths that are too long on OSX for a Windows machine with the 255 character path limit, before transferring them from one to the other.
I've tried referencing this post to make it recursive but to no avail as the approach here appears quite different: AppleScript Processing Files in Folders recursively
on run
set longPaths to {}
tell application "Finder" to set theSel to selection
repeat with aFile in theSel
set aFile to aFile as string
set pathLength to count of characters in aFile
if pathLength > 255 then
set end of longPaths to aFile
end if
end repeat
if longPaths is not equal to {} then
-- do something with your list of long paths, write them to a text file or whatever you want
set pathToYourTextFile to (path to desktop folder as string)&"SampleTextFile.txt"
set tFile to open for access file (pathToYourTextFile as string) with write permission
repeat with filePath in longPaths
write (filePath & return as string) to tFile starting at eof
end repeat
close access tFile
end if
end run
Does anyone know the best way to add a recursive element to this script so that theSel includes all files in the subdirectories of the selected directory?
Here's a script which read recursively through directories:
property theOpenFile : missing value
tell application "Finder" to set theSel to selection
if theSel is not {} then
set pathToYourTextFile to (path to desktop folder as string) & "SampleTextFile2.txt"
set theOpenFile to open for access file (pathToYourTextFile as string) with write permission
repeat with aItem in theSel
tell application "Finder" to class of aItem is folder
if the result then my getFilesIn(aItem) -- aItem is a folder
end repeat
close access theOpenFile
end if
on getFilesIn(thisFolder)
tell application "Finder" to set theseFiles to files of thisFolder as alias list
repeat with thisFile in theseFiles
set f to thisFile as string
set pathLength to length of f
if pathLength > 255 then my writeToFile(f)
end repeat
tell application "Finder" to set theseSubFolders to folders of thisFolder
repeat with tSubF in theseSubFolders
my getFilesIn(tSubF) -- call this handler (recursively through this folder)
end repeat
end getFilesIn
on writeToFile(t)
write (t & return) to theOpenFile starting at eof
end writeToFile
Try:
tell application "Finder" to set theSel to every file in (get entire contents of selection)
Or something like that. Sorry, I am without a Mac to test the precise code. Whenever you ask for entire contents, you get everything in all subfolders of your chosen folder.

(Automator/AppleScript) Rename files to a folder name, save to different folder & add prefix

I really need you help and will be very grateful for it.
I think this should be no problem for you.
So I have folder with different subfolders in it. In subfolders are images.
What I need is:
Change each image name to it’s subfolder name + 001 and so on («1stSubfolder001, 1stSubfolder002, …», «2ndSubfolder001, 2ndSubfolder002, …»)
Move all images from all subfolders to one folder (or at least to root folder)
Add random prefix number to names.
I have script to third task:
tell application "Finder"
repeat with this_item in (get items of window 1)
set name of this_item to ((random number from 0 to 99999) & name of this_item) as string
end repeat
end tell
But it only works with opened folder on the foreground. Maybe there is a way to make one script to run it on the background?
Huge thank you in advance.
I found cool automator app here, but I need to correct it a little bit.
How do I use the current folder name without a path as a variable in automator in Mac?
This script does everything I need BUT it renames all files to ONE folder name, not each different.
Here is another applescript that does rename files to folders, but it uses 2 folders name (folder + subfolder), and I need only subfolder prefix.
set myFolder to do shell script "sed 's/\\/$//' <<< " & quoted form of POSIX path of (choose folder)
set myFiles to paragraphs of (do shell script "find " & quoted form of myFolder & " \\! -name \".*\" -type f -maxdepth 2 -mindepth 2")
repeat with aFile in myFiles
tell application "System Events" to set file aFile's name to (do shell script "sed 's/.*\\/\\([^/]*\\)\\/\\([^/]*\\)\\/\\([^/]*$\\)/\\1_\\2_\\3/' <<< " & quoted form of aFile)
end repeat
(MacBook Pro Late 2013, OS X Yosemite 10.10.1)
This accomplishes what you want. You should be able to adjust from here. There are no checks to ignore non-image files.
property top_folder : alias "Macintosh HD:Users:MyName:Downloads:Images:"
property save_folder : ""
set save_folder to choose folder with prompt "Select the folder to save the images in."
process_folder(top_folder)
on process_folder(this_folder)
set these_items to list folder this_folder without invisibles
set container_name to name of (info for this_folder)
repeat with i from 1 to the count of these_items
set this_item to alias ((this_folder as Unicode text) & (item i of these_items))
if folder of (info for this_item) is true then
process_folder(this_item)
else
process_item(this_item, container_name, i)
end if
end repeat
end process_folder
-- this sub-routine processes files
on process_item(this_item, c, i)
-- make the integer a 3 digit string
if i < 10 then
set i to "00" & i
else if i < 100 then
set i to "0" & i
end if
-- set a random number
set r to (random number from 0 to 99999) as string
tell application "System Events"
-- get file extension so not overwritten
set e to name extension of this_item
set new_name to "" & r & "_" & c & "_" & i & "." & e
set name of this_item to new_name
move this_item to save_folder
end tell
end process_item

Applescript - move files to folder which name begins with first 2 characters of filename

I'd like a little help fine tuning this script. Is it possible to have this script match subfolders based on the first two letters of file? Parent folder would be "Shots" which contains subfolders with unique two letter prefix "BA_Bikini_Atol" which contains subfolders within those folders for the specific shots ba_0020, ba_0030, etc. Would like to move a file, ba_0020_v0002 to the ba_0020 folder by selecting "shots" as the target and the script looks through all the sub folders for a match. Thoughts?
set mgFilesFolder to (choose folder with prompt "Where are the files stored which you would like to move to the destination?")
set mgDestFolder to (choose folder with prompt "Where is the destination folder?")
tell application "System Events"
set folderList to name of folders of mgDestFolder
set fileList to name of files of mgFilesFolder
end tell
repeat with i from 1 to (count folderList)
set folderName to item i of folderList
set filesToMove to {}
repeat with j from 1 to (count fileList)
set filename to item j of fileList
if filename begins with folderName then
set end of filesToMove to alias ((mgFilesFolder as string) & filename)
end if
end repeat
tell application "Finder"
move filesToMove to alias ((mgDestFolder as string) & folderName & ":")
end tell
end repeat
This works in my tests
set mgFilesFolder to (choose folder with prompt "Where are the files stored which you would like to move to the destination?")
set mgDestFolder to (choose folder with prompt "Where is the destination folder?")
(* get the files of the mgFilesFolder folder *)
tell application "Finder" to set fileList to files of mgFilesFolder
(* iterate over every file *)
repeat with i from 1 to number of items in fileList
set this_item to item i of fileList
set this_file_Name to name of this_item as string
(* get the file name code
Using the delimiter "_" break the name into fields of text and returning fields 1 thru 2 . i.e ba_0030 *)
set thisFileCode to (do shell script "echo " & quoted form of this_file_Name & " |cut -d _ -f 1-2")
log thisFileCode
tell application "Finder"
set folderList to ((folders of entire contents of mgDestFolder) whose name starts with thisFileCode)
if folderList is not {} then
move this_item to item 1 of folderList
end if
end tell
end repeat
FROM:
TO:
I've made (with the help from users of this site) the script you're referring to.
I've made similar (more efficient) scripts since then. Most of them depend on ASObjC Runner which you can get here: Download ASObjC Runner
If you have it installed I think the following script will work:
set mgFilesFolder to (choose folder with prompt "Where are the file stored which you would like to move?")
set mgDestFolder to (choose folder with prompt "Where are destination folders?")
tell application "ASObjC Runner"
set mgFiles to enumerate folder mgFilesFolder with recursion without including folders
repeat with mgFile in mgFiles
set mgPrefix to text 1 thru 7 of name of (about file mgFile include only "name")
set mgDest to enumerate folder mgDestFolder match prefix mgPrefix with recursion
manage file mgFile moving into mgDest
end repeat
end tell

Applescript - move files to folder which name begins with first 7 characters of filename

I've got 2 folders
the first folder contains all the files which I'd like to move to a subfolder of the second folder. I'd like to this based on the first 7 characters of both the file & folder names.
So if the first 7 characters of the subfolder in FOLDER 2 matches the first 7 characters of the file in FOLDER 1. The file get's moved into the subfolder of FOLDER 2
Right now I've got an Applescript which works, but it's terribly slow.
Here's the script so far:
set mgFileList to ""
set mgDestFolder to ""
set mgFilesFolder to (choose folder with prompt "Where are the files stored which you would like to move to the destination?")
set mgDestFolder to (choose folder with prompt "Where is the destination folder?")
set folderList to contents of mgDestFolder
tell application "Finder" to set fileList to files of mgFilesFolder
repeat with aFile in fileList
set prefix to getPrefix(name of aFile)
tell application "Finder"
try
set destinationFolder to (1st folder of mgDestFolder whose name begins with prefix)
move aFile to destinationFolder
end try
end tell
end repeat
on getPrefix(aName)
set prefix to text 1 thru 7 of aName
return prefix
end getPrefix
I've only recently got into Applescripting. Can this be done more efficiently within Applescript? I've searched around for a solution which works with a shell script (which I think will probably be a lot faster), but haven't been able to get any of them working.
I'm working on OSX Mountain Lion 10.8.4
--
edit
I see that I've uploaded the wrong version of the script I've managed to put together so far. Above is the script that is working, but it's really slow. Probably because it doesn't use any shell script.
---------------update-------------------
Here is the final script that is working pretty fast right now:
set mgFilesFolder to choose folder with prompt "Where are the files stored which you would like to move to the destination?"
set mgDestFolder to choose folder with prompt "Where is the destination folder?"
tell application "System Events"
set fileList to files of mgFilesFolder whose name does not start with "."
repeat with aFile in fileList
set prefix to my getPrefix(name of aFile)
try
set destinationFolder to (1st folder of mgDestFolder whose name begins with prefix)
move aFile to POSIX path of destinationFolder
end try
end repeat
end tell
on getPrefix(aName)
try
set prefix to text 1 thru 7 of aName
return prefix
on error
return aName
end try
end getPrefix
The Finder is generally a slow program because it does a lot of things on your computer. Therefore you should avoid using it if possible. In your case these tasks can be done using System Events. Try this, maybe it will be faster. Note that you had a couple mistakes in your code which are fixed in this code.
set mgFilesFolder to choose folder with prompt "Where are the files stored which you would like to move to the destination?"
set mgDestFolder to choose folder with prompt "Where is the destination folder?"
tell application "System Events"
set fileList to files of mgFilesFolder whose name does not start with "."
repeat with aFile in fileList
set prefix to my getPrefix(name of aFile)
try
set destinationFolder to (1st folder of mgDestFolder whose name begins with prefix)
move aFile to POSIX path of destinationFolder
end try
end repeat
end tell
on getPrefix(aName)
try
set prefix to text 1 thru 7 of aName
return prefix
on error
return aName
end try
end getPrefix
Not sure how many files you're working with, but I tested this with 10 folders and ~100 files, and it took less than 4 seconds to run. Working with strings is much faster than working with files, so the script gets the files/folders as strings and builds aliases when they're needed.
set mgFilesFolder to (choose folder with prompt "Where are the files stored which you would like to move to the destination?")
set mgDestFolder to (choose folder with prompt "Where is the destination folder?")
--Always use System Events whenever possible. It's WAY faster than Finder.
tell application "System Events"
set folderList to name of folders of mgDestFolder
set fileList to name of files of mgFilesFolder
end tell
repeat with i from 1 to (count folderList)
set folderName to item i of folderList
set filesToMove to {}
repeat with j from 1 to (count fileList)
set filename to item j of fileList
if filename begins with folderName then
set end of filesToMove to alias ((mgFilesFolder as string) & filename)
end if
end repeat
--Can't use system events for moving files. You have to use Finder.
tell application "Finder"
move filesToMove to alias ((mgDestFolder as string) & folderName & ":")
end tell
end repeat
It might be easier to use shell scripting:
cd FOLDER1; for f in *; do d=../FOLDER2/"${f:0:7}"*; [[ -d $d ]] && mv "$f" "$d"; done

Resources