Finder is probably the most-used program on a Mac with a comprehensive scripting dictionary. The examples here illustrate how to get to a file object from a path and how to rename a selection of files or folders using a regular expression.
Get a file object from its name#
File
is of course one of the central elements of Finder. However, you can’t
get a file
object directly from its complete name.
Using an absolute path#
If your file is given by a complete path, i.e. one beginning with ‘/’, you have to drill down through the complete folder hierarchy:
function fileFromPath(fullname) {
const finderApp = Application("Finder");
/* Split the full name to the file at folder separators '/'
Throw away the first, empty element with splice
*/
const components = fullname.split('/').slice(1);
/* The name of the file proper is the last element of
components, i.e. the part following the last '/' in fullname
Remove it from components and save in filename */
const filename = components.pop();
/* components now contain all folders from the root to the one
containing filename. Drill down to find the last folder. */
let container = finderApp.startupDisk();
components.forEach(dir => {
container = container.folders[dir];
})
const file = container.files[filename];
return file;
}
Call this function like const fileĀ = fileFromPath('/Users/me/Desktop/folder1/file.text')
and file
will be a
File
object from Finder. So file.comment()
would give you the Finder comment
for it etc.
Using a relative path#
If your filename is specified relative to your current location, i.e. it does
not begin with ‘/’, you have to first determine the current directory like shown
in
Marrying JXA with ObjC. Then you can simply prepend it to the file name and pass it to fileFromPath
:const file = fileFromPath(`${currentDir}/${filename}`)
Get a folder object from its name#
Getting a folder from its name is very similar to getting a file. In fact, you can modify the code above slightly to work with folders, too:
function pathToFinderObject(fullpath) {
ObjC.import("Foundation");
const fileManager = $.NSFileManager.defaultManager;
const isDir = Ref();
const exists = fileManager.fileExistsAtPathIsDirectory($(fullpath),isDir);
if (!exists) {
throw `File ${fullpath} not found`;
}
const finderApp = Application("Finder");
const components = fullpath.split('/').slice(1);
const filename = isDir[0] ? undefined : components.pop();
let container = finderApp.startupDisk();
components.forEach(dir => {
container = container.folders[dir];
})
const file = isDir[0] ? container.files[filename] :
return file;
}
This function relies on the JXA-Objective-C bridge.
Batch rename files using Regular Expression#
Similarly to a script for DEVONthink 3, the following code allows the user to batch rename the currently selected files using a regular expression.
(() => {
const app = Application("Finder");
app.includeStandardAdditions = true;
const selection = app.selection();
if (selection.length > 0) {
const suffix = selection.length > 1 ? "s" : "";
/* Set title to display # of selected records */
const title =
`Change name${suffix} for ${selection.length} file${suffix}`;
/* Ask user for RegExp, stop if empty string */
const searchFor = app.displayDialog("Search for RE", {
withTitle: title,
defaultAnswer: "",
});
if (searchFor.textReturned === "") return;
/*
* Ask user for replacement,
* showing the RegExp they entered
*/
const replaceWith = app.displayDialog(
`Replace "${searchFor.textReturned}" ` +
`by: (use $1, $2 etc. for groups)`,
{ withTitle: title, defaultAnswer: "" }
);
const re = new RegExp(searchFor.textReturned);
const reText = replaceWith.textReturned;
const inform = selection.map(
(s) => `${s.name()} => ${s.name().replace(re, reText)}`
);
app.displayDialog(inform, { withTitle: "Filename to change" });
selection.forEach((s) => {
s.name = s.name().replace(re, reText);
});
}
})();