Changing AppleScript-created date-based folder structure
I have an AppleScript that has been written and it works as such, but I need to change the way it creates the folder structure. The script does the following:
- Folder is selected which contains files (in my case it will be photos).
- It will then look at the pictures created date.
- Create a YYYY (Year folder if not already created).
- Create a MM (Month folder if not already created).
- Create a DD (Day folder if not already created).
- It will then move the photo into this folder and repeat for the next photo and repeat until completed.
The current folder structure is created as follows:
2018 "YYYY"
├── 2018-01 "MM"
├── 2018-02
This is great and works as designed, but I have changed my mind on how I would like the folders to look like. I would like the following structure (which is almost the same just a different naming structure:
2018
├── 001 January
│ ├── 20180101
│ └── 20180102
├── 002 February
│ ├── 20180201
│ └── 20180202
└── 003 March
├── 20180301
└── 20180302
Now I have tried to work out where the script generates this but I have failed, so now I am turning to this great place for some help.
on run
SortFiles(POSIX path of (choose folder))
end run
on open (DroppedFolder)
set DroppedFolder to POSIX path of DroppedFolder
if text (length of text of DroppedFolder) of DroppedFolder is not "/" then quit
SortFiles(DroppedFolder)
end open
on SortFiles(SortFolder)
set AppleScript's text item delimiters to return
set SortFolderContents to the text items of (do shell script "find '" & SortFolder & "' -type f")
set FolderMakeList to {}
repeat with ThisItem in SortFolderContents
set ThisFile to ThisItem as string
if ThisFile does not contain "/." then
tell application "Finder"
set DateString to text 1 thru 7 of ((creation date of ((POSIX file ThisFile) as alias)) as «class isot» as string)
set ThisFilesFolder to SortFolder & text 1 thru 4 of DateString & "/"
set ThisFilesSubfolder to ThisFilesFolder & text 1 thru 7 of DateString & "/"
end tell
if ThisFilesFolder is not in FolderMakeList then
try
do shell script ("mkdir '" & ThisFilesFolder & "'")
end try
set FolderMakeList to FolderMakeList & ThisFilesFolder
end if
if ThisFilesSubfolder is not in FolderMakeList then
try
do shell script ("mkdir '" & ThisFilesSubfolder & "'")
end try
set FolderMakeList to FolderMakeList & ThisFilesSubfolder
end if
try
do shell script ("mv '" & ThisFile & "' '" & ThisFilesSubfolder & "'")
end try
end if
end repeat
return FolderMakeList
end SortFiles
The part of your script that names the folders is this line:
set ThisFilesFolder to SortFolder & (do shell script ("date -r " & ThisFilesEpoch & " \"+%b-%Y\"")) & "/"
However, in my brief test of your script, it doesn't appear to create the folder structure you described at all, which isn't a surprise to me given that the shell command used to generate the date string from which the folder is named is this:
date -r %time% "+%b-%Y"
where %time%
is inserted by way of the variable ThisFilesEpoch
and represents the number of seconds since the Epoch (January 1, 1970) that the file (or, rather, its inode) was created. This shell function, date
, takes this value (a large integer), which it understands as being a value in "Unix time", and re-formats it using a canonical representation as given by the tokens %b
(representing a three-letter abbreviation for the month) and %Y
(representing a four-digit number for the year).
So, for instance:
date -r 1534615274 "+%b-%Y"
returns:
Aug-2018
into which the files from August 2018 I had in my chosen folder were moved. It did not create separate day-of-the-month folders, nor even separate year folders; only "month-year"
folders. So I am confused that you appear to have described what might be a different script entirely.
The script also has a number of qualities about it that irk me a great deal, such as:
Its lazy usage of
try
...end try
;-
The insertion of literal single quotes surrounding a variable being used as part of a string, e.g.
do shell script "find '" & SortFolder & "' -type f"
instead of
do shell script "find " & quoted form of SortFolder & " -type f"
-
This bizarre formulation:
if text (length of text of DroppedFolder) of DroppedFolder is not "/" then...
which could be written as
if text -1 of DroppedFolder is not "/" then...
or as
if the last character of DroppedFolder is not "/" then...
or even
if DroppedFolder does not end with "/" then...
And repeated use of
do shell script
, which is fine as a command tool to be invoked thoughtfully when needed, but when it populates the script as densely as it does this one, one wonders why the script wasn't just written as a shell/bash script to begin with. It's also very costly in terms of overhead to create and destroy shell processes one after another like this script does.
Given these stylistic and functional drawbacks, plus the confusion over what the script is supposed to be doing currently versus what it actually appears to do, I felt it warranted a re-write:
use Finder : application "Finder"
property rootdir : missing value
# The default run handler when run within Script Editor
on run
using terms from scripting additions
set root to choose folder
end using terms from
set rootdir to Finder's folder root -- '
sortFiles()
end run
# Processes items dropped onto the applet. The handler expects one or more
# file references in the form of an alias list, and will process each item in
# the list in turn. If an item is not a folder reference, it is ignored.
# For the rest, the contents of each folder is reorganised.
on open droppedItems as list
repeat with drop in droppedItems
set rootdir to Finder's item drop
if rootdir's class = folder then sortFiles()
end repeat
end open
# Obtains every file in the root directory and all subfolders, moving them
# to new folders nested within the root directory and organised by year, month,
# and date of each file's creation
to sortFiles()
repeat with f in the list of fileitems()
set [yyyy, m, dd] to [year, month, day] of (get ¬
the creation date of Finder's file f) -- '
set mmm to text -3 thru -1 of ("00" & (m * 1)) -- e.g. "008"
set mm to text -2 thru -1 of mmm -- e.g. "08"
set dd to text -2 thru -1 of ("0" & dd) -- e.g. "01"
(my newFolderNamed:yyyy inFolder:rootdir)
(my newFolderNamed:[mmm, space, m] inFolder:result)
move Finder's file f to my newFolderNamed:[yyyy, mm, dd] ¬
inFolder:result -- '
end repeat
end sortFiles
# A handler to house a script object that enumerates the contents of a
# directory to its full depth
on fileitems()
script
property list : every document file in the ¬
entire contents of the rootdir ¬
as alias list
end script
end fileitems
# Creates a new folder with the supplied name in the specified directory,
# checking first to see whether a folder with that name already exists.
# In either case, a Finder reference to the folder is returned.
to newFolderNamed:(dirname as text) inFolder:dir
local dirname, dir
tell (a reference to folder dirname in the dir) ¬
to if it exists then return it
make new folder at the dir with properties {name:dirname}
end newFolderNamed:inFolder:
I would urge you to test this script initially on a test directory containing sample files. In fact, the worst case scenario is the same as with your current script, which is the situation where you choose the wrong directory by mistake. Assuming the correct directory is selected, then the worst case scenario is that files don't get moved, so it's generally a very safe script.
I re-wrote the open
handler too, to (hopefully) allow it to cater for multiple items being dropped onto the applet. This means you could, for example, drop two folders onto it and have both folders' contents re-structured. However, I haven't tested this. Again, the worst case scenario here is that it doesn't do anything, which will be the case if I am wrong about what type of file reference gets passed to the open
handler (I assume it will be alias
objects).