Sort filename by last characters prefixed by #

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:

Screenshot 2021-02-25 at 19.58.37

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

1 Like

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

1 Like

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 :slight_smile:

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

1 Like

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.

1 Like

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