I have several thousand files in one folder, most filenames have the format xxxxx #nnnnn.pdf (or whatever). I want to sort the files based on the #nnnnn bit at the end. It is always prefixed by the # and always has 5 digits as the 'n'. Is there any way to sort these using only the #nnnnn part of the filename.
I use the #nnnnn as a quick way to identify a file when I am writing.
Many thanks
Don Spady
What is the form of the output there ?
- A string report listing sorted file names ?
- An Excel spread-sheet ?
- a change in the state of a Finder window ?
- a Terminal.app listing ?
Something else ?
Thaanks for your reply.
The files will be sorted in the finder so that the numbers are sequential.
Don
Ah ... you may have to pick another of those options ...
I don't think that the Finder can display files in a custom sort order.
All it can do is View > SortBy
, with these hard-baked options:
I don't know if it would be useful to get a sorted markdown text listing of those files in the folder that contain a #
in their name.
Displayed in a suitable Markdown previewer or text editor, the url part of each markdown link would be clickable ...
Possibly something like this, which copies a sorted markdown listing to the clipboard ?
Markdown listing of sorted files in chosen folder.kmmacros (23.8 KB)
I downloaded the macro and tried it out but it did not seem to work. Part of the problem is that I am very naive when it comes to stuff like this. I will try a different approach to files management that might obviate somewhat the need to sort.
Thanks very much for your help.
Don Spady
As @ComplexPoint noted, one way to sort files in the Finder is Tags.
Would you be open to setting a Finder Tag for each file that is the #number
that you have referred to?
If so, you could do this using the KM For Each action using the Folder Contents Collection
. The For Each
Action loop variable, let's call it "Local__FilePath", provides the POSIX file path to each file. You could then use Search using Regular Expression action on the Local__FilePath
to extract the #number
. Then set the Finder Tag for that file using Set File Attribute action.
If you need more help, just let us know.
EDIT:
The Regex for this can be a bit tricky. So here's the Regex to extract the #number
at the end of the file path, like this:
Given a Path like this:
/Users/YourUserName/Documents/TEST/KM Tests #12345.xxx/Test File with Ref ID in name #12345.pdf
Search For:
(#\d+)\.[^\/]+$
Set the KM First Capture Field to:
\1
Example Results:
#12345
I don't think that would help; it would just tag each filename with a single number, and hopefully no other file has that number, so it would just be as easy to search for the number. My purpose in doing this was to be able to know which was the highest number in the list of filenames. As I obtained files, I would append a number #nnnnn to the end of the filename. I used this number as a quick way to identify it when writing (e.g. Spady et al (#12345) says 'this' and Jones (#34567) says 'that'). Ultimately it would be replaced by a proper literature citation. By using the #nnnnn I could enter a lot of files into my system without them first being put into a citation manager, such as Bookends. It is a good app, but when you have 20000 files to enter to start with, it becomes an unmanageable issue. So, the reason I used numbers was as a quick reference, and when I add a new file I want to know the next available number. By sorting the files, I was hoping to find that number. But it does not seem to be easily done. I will just muddle through, or reconsider how to do my numbers.
At any rate, thanks very much for your help. It is nice to know that there are people who go out of their way to help others just because they want to.
Don
If you just want the largest number, then you can get KM to find that for you, using the regex Michael gave you but with one small adjustment so as not to capture the #
#(\d+)\.[^\/]+$
You could, eg, compare the output to the previously highest number, and if it's higher put the result of that in a global variable. (And add one, and you have your next number.)
Rather than running this over all of your files every time, you might want to store the largest number permanently. &/or only check the most recent files added.
Ah ... well that's actually quite easy ...
Highest 5 digit value after # in files of chosen folder.kmmacros (23.5 KB)
Or, possibly a little faster for a large body of files:
( maximumBy
, rather than sortOn
)
Ver 2 of Highest 5 digit value after # in files of chosen folder .kmmacros (22.8 KB)
If you:
- run the macro, and
- choose a folder in response to the prompt
- it should show you the name of the file with the highest
#nnnnn
string.
JS Source
(() => {
"use strict";
// If some files in the chosen folder contain a #
// followed by a five digit sequence,
// This macro will show the filename with the highest
// #NNNNN value in that folder.
// Rob Trew @2021
// Ver 0.01
// main :: IO ()
const main = () => {
const
fpFolder = Application(
"Keyboard Maestro Engine"
).getvariable("folderPathForSortedListing");
const fpFullPath = filePath(fpFolder);
return either(
msg => alert(msg)
)(
xs => xs
)(
bindLR(
getDirectoryContentsLR(
filePath(fpFullPath)
)
)(fileNames => Right(
maximumBy(
comparing(x => {
const
parts = takeBaseName(x)
.split("#");
return 1 < parts.length ? (() => {
const s = parts[1];
return (5 !== s.length) || isNaN(s) ? (
0
) : parseInt(s, 10);
})() : 0;
})
)(fileNames)
))
);
};
// -------- JAVASCRIPT FOR AUTOMATION LIBRARY --------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => m.Left ? (
m
) : mf(m.Right);
// comparing :: (a -> b) -> (a -> a -> Ordering)
const comparing = f =>
x => y => {
const
a = f(x),
b = f(y);
return a < b ? -1 : (a > b ? 1 : 0);
};
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// filePath :: String -> FilePath
const filePath = s =>
// The given file path with any tilde expanded
// to the full user directory path.
ObjC.unwrap(ObjC.wrap(s)
.stringByStandardizingPath);
// getDirectoryContentsLR :: FilePath ->
// Either String IO [FilePath]
const getDirectoryContentsLR = fp => {
const
error = $(),
xs = $.NSFileManager.defaultManager
.contentsOfDirectoryAtPathError(
$(fp).stringByStandardizingPath,
error
);
return xs.isNil() ? (
Left(ObjC.unwrap(error.localizedDescription))
) : Right(ObjC.deepUnwrap(xs));
};
// maximumBy :: (a -> a -> Ordering) -> [a] -> a
const maximumBy = f =>
xs => 0 < xs.length ? (
xs.slice(1).reduce(
(a, y) => 0 < f(y)(a) ? (
y
) : a,
xs[0]
)
) : undefined;
// takeBaseName :: FilePath -> String
const takeBaseName = strPath =>
("" !== strPath) ? (
("/" !== strPath[strPath.length - 1]) ? (() => {
const fn = strPath.split("/").slice(-1)[0];
return fn.includes(".") ? (
fn.split(".").slice(0, -1)
.join(".")
) : fn;
})() : ""
) : "";
return main();
})();
That works, tells me exactly what I want. Many thanks
Don
I'm glad that worked.
A good shortcut to help, incidentally, turns out to be to go straight to showing:
- an example of the input
- a corresponding desired output.
Explanations take more time to write, and are usually quite hard for anyone but oneself to fully grasp
That would not be the same at all.
You can easily set the sort in the finder to "Tags" just by clicking on the "Tags" column header, just like you click on "Name", "Modification Date", etc.
That is completely different. You should have stated that from the beginning.
You can easily use KM to keep track of last number assigned.
In fact, you could easily create a KM macro that renames the file selected in Finder to append your #number
.
KM can find the file based on the #number
and return the file name without the number.
The Mac Spotlight will also quickly find the file when you type in the #number
.
I gave you some good options above.
Hey Don,
When I want to give files a serial number I use the date down to the second:
20210227031312
It gives me huge granularity and sorts properly if I use it as a prefix.
Rather than having to fool with searching for the greatest number I can search for the last created file.
-Chris
I had thought of that early on. I agree that it is an effective solution but did not want a numerical filename prefix. In the end I may return to it.
Thanks for the ideal.
I agree and I should have done that. It makes the issue much clearer for all.
Thanks
Don
Yes, you did, and I thank you for it. I now have a solution that works, thanks to everyone's help.
Don
If you are using a solution that has to search all files in a folder, extract the #number
, and then get the largest value, that might start running slow when you have thousands of files in the folder.
Using a KM Macro with a Global Variable to keep track of the last number used would be much faster.
Thanks. I have now worked out a system that is simple and works for me. I appreciate everyone's comments, ideas, and suggestions. Thanks.
Don