How Do I Delete Files With a Specific File Extenstion [i.e .pdf]

Hi,

I'm looking a way to delete files with specific file extenstion like .PDF in the selected folder.

The provided marco "Delete a File" only allow a specific named folder to be deleted, which really limits my options due to me dealing with a variety of names.

There are many examples of this which you can find if you simply search this forum for “delete files”. Here’s an example of one of them:

In addition to the approaches to which @tiffle points you, you could also:

  1. Obtain a list (by script) of the file paths of all files which a given extension which descend from the folder(s) selected in Finder
  2. Use a KM For Each action to work through the collection of lines in that list, doing whatever you like with each in turn, including deleting them, if that is what you need.

Here is an illustrative draft which just works through the list of matching filepaths, without deleting them:

Descendants of selected folders with given extension.kmmacros (29.4 KB)

JS Source
(() => {
    'use strict';

    ObjC.import('AppKit');

    // Rob Trew @2020

    // List of all filepaths which both:
    // 1. Descend from the folder(s) selected in Finder
    // 2. and have the specified extension.

    // --------------------- OPTIONS ---------------------
    const
        extension = Application(
            'Keyboard Maestro Engine'
        ).getvariable('targetExtension');


    // ---------------------- MAIN -----------------------

    // main :: IO ()
    const main = () => {
        // Include all visible files ?
        // or just folders ?
        const
            ext = extension.startsWith('.') ? (
                extension
            ) : `.${extension}`,
            pFilter = Boolean(ext) ? (
                fp => ext === takeExtension(fp)
            ) : fp => !takeFileName(fp).startsWith('.');

        const
            selectedFolders = Application(
                'Finder'
            ).selection();

        return either(msg => msg)(unlines)(
            bindLR(
                undefined !== selectedFolders ? (
                    Right(
                        selectedFolders.flatMap(
                            x => filePathForest(
                                decodeURI(x.url()).slice(7)
                            )
                        )
                    )
                ) : Left('Nothing selected in Finder.')
            )(
                compose(
                    Right,
                    concatMap(filterTree(pFilter))
                )
            )
        );
    };


    // filePathForest :: FilePath -> [Tree FilePath]
    const filePathForest = fp => {
        const go = fp =>
            Node(fp)(
                doesDirectoryExist(fp) ? (
                    map(compose(go, combine(fp)))(
                        getDirectoryContents(fp)
                    )
                ) : []
            );
        return doesPathExist ? [go(fp)] : [];
    };


    // outlineFromForest :: String ->
    // (a -> String) -> [Tree a] -> [String]
    const outlineFromForest = unitIndent =>
        // Indented text representation of a list of 
        // Trees.
        // f is an (a -> String) function defining
        // the string representation of tree nodes.
        f => trees => {
            const go = indent => x => {
                const
                    s = indent + f(x.root),
                    xs = x.nest,
                    nextDepth = unitIndent + indent;
                return 0 < xs.length ? (
                    [s].concat(
                        xs.flatMap(go(nextDepth))
                    )
                ) : s;
            };
            return unlines(trees.flatMap(go('')));
        };


    // --------------------- JXA ---------------------


    // doesDirectoryExist :: FilePath -> IO Bool
    const doesDirectoryExist = fp => {
        const ref = Ref();
        return $.NSFileManager.defaultManager
            .fileExistsAtPathIsDirectory(
                $(fp)
                .stringByStandardizingPath, ref
            ) && ref[0];
    };


    // doesPathExist :: FilePath -> IO Bool
    const doesPathExist = fp =>
        $.NSFileManager.defaultManager
        .fileExistsAtPath(
            $(fp).stringByStandardizingPath
        );

    // ------------------- GENERIC -------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });


    // Node :: a -> [Tree a] -> Tree a
    const Node = v =>
        // Constructor for a Tree node which connects a
        // value of some kind to a list of zero or
        // more child trees.
        xs => ({
            type: 'Node',
            root: v,
            nest: xs || []
        });


    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });


    // bindLR (>>=) :: Either a -> 
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.Right);


    // combine (</>) :: FilePath -> FilePath -> FilePath
    const combine = fp =>
        // Two paths combined with a path separator. 
        // Just the second path if that starts 
        // with a path separator.
        fp1 => Boolean(fp) && Boolean(fp1) ? (
            '/' === fp1.slice(0, 1) ? (
                fp1
            ) : '/' === fp.slice(-1) ? (
                fp + fp1
            ) : fp + '/' + fp1
        ) : fp + fp1;


    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        // A function defined by the right-to-left
        // composition of all the functions in fs.
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );


    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs => (
        ys => 0 < ys.length ? (
            ys.every(Array.isArray) ? (
                []
            ) : ''
        ).concat(...ys) : ys
    )(list(xs));


    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = f =>
        // Concatenated results of a map of f over xs.
        // f is any function which returns a list value.
        // Any empty lists returned are filtered out by
        // the concatenation.
        xs => xs.flatMap(f);


    // 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 => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;


    // filterTree (a -> Bool) -> Tree a -> [a]
    const filterTree = p =>
        // List of all values in the tree
        // which match the predicate p.
        foldTree(x => xs => concat(
            p(x) ? [
                [x], ...xs
            ] : xs
        ));


    // foldTree :: (a -> [b] -> b) -> Tree a -> b
    const foldTree = f => {
        // The catamorphism on trees. A summary
        // value obtained by a depth-first fold.
        const go = tree => f(tree.root)(
            tree.nest.map(go)
        );
        return go;
    };


    // getDirectoryContents :: FilePath -> IO [FilePath]
    const getDirectoryContents = fp =>
        ObjC.deepUnwrap(
            $.NSFileManager.defaultManager
            .contentsOfDirectoryAtPathError(
                $(fp)
                .stringByStandardizingPath, null
            )
        );


    // last :: [a] -> a
    const last = xs => (
        // The last item of a list.
        ys => 0 < ys.length ? (
            ys.slice(-1)[0]
        ) : undefined
    )(list(xs));


    // list :: StringOrArrayLike b => b -> [a]
    const list = xs =>
        // xs itself, if it is an Array,
        // or an Array derived from xs.
        Array.isArray(xs) ? (
            xs
        ) : Array.from(xs || []);


    // map :: (a -> b) -> [a] -> [b]
    const map = f =>
        // The list obtained by applying f
        // to each element of xs.
        // (The image of xs under f).
        xs => [...xs].map(f);


    // nest :: Tree a -> [a]
    const nest = tree => {
        // Allowing for lazy (on-demand) evaluation.
        // If the nest turns out to be a function –
        // rather than a list – that function is applied
        // here to the root, and returns a list.
        const xs = tree.nest;
        return 'function' !== typeof xs ? (
            xs
        ) : xs(root(x));
    };


    // root :: Tree a -> a
    const root = tree =>
        tree.root;


    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        xs => list(xs).slice()
        .sort((a, b) => f(a)(b));


    // takeExtension :: FilePath -> String
    const takeExtension = fp => (
        fs => {
            const fn = last(fs);
            return fn.includes('.') ? (
                '.' + last(fn.split('.'))
            ) : '';
        }
    )(fp.split('/'));


    // takeFileName :: FilePath -> FilePath
    const takeFileName = fp =>
        '' !== fp ? (
            '/' !== fp[fp.length - 1] ? (
                fp.split('/').slice(-1)[0]
            ) : ''
        ) : '';


    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join('\n');


    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = p =>
        f => x => {
            let v = x;
            while (!p(v)) v = f(v);
            return v;
        };

    // MAIN ---
    return main();
})();

Hey @Blues

Will these files all be in the root level of the selected folder?

Or might they be in nested folders in the selected folder?

If the former then the appended macro should do the job.

If the latter then I'll have to rework it a bit.

-Chris


Delete Files in the Selected Finder Folder by File-Extension v1.00.kmmacros (6.1 KB)

This can be done using only native, non-scripting, KM Actions.

This example macro will Trash or Delete ONLY the files in the Target FOLDER selected in Finder.

Below is just an example written in response to your request. You will need to use as an example and/or change to meet your workflow automation needs.

Please let us know if it meets your needs.


MACRO:   Delete (Trash) Files Files with Ext in Selected Folder [Example]

--- VER: 1.0    2020-12-05 ---
Requires: KM 8.2.4+   macOS 10.11 (El Capitan)+
(Macro was written & tested using KM 9.0+ on macOS 10.14.5 (Mojave))

DOWNLOAD Macro File:

Delete (Trash) Files Files with Ext in Selected Folder [Example].kmmacros
Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.


Example Input

image

Example Output


ReleaseNotes

Author.@JMichaelTX

PURPOSE:

  • Delete (Trash) Files Files with Ext in Selected Folder [Example]

NOTICE: This macro/script is just an Example

  • It has had very limited testing.
  • You need to test further before using in a production environment.
  • It does not have extensive error checking/handling.
  • It may not be complete. It is provided as an example to show you one approach to solving a problem.

HOW TO USE:

  1. Select the FOLDER in the Finder Containing the Files to Delete
  2. Trigger this macro

MACRO SETUP

  • Carefully review the Release Notes and the Macro Actions
    • Make sure you understand what the Macro will do.
    • You are responsible for running the Macro, not me. :wink:
      .
  • Assign a Trigger to this maro.
  • Move this macro to a Macro Group that is only Active when you need this Macro.
  • ENABLE this Macro.
    .
  • REVIEW/CHANGE THE FOLLOWING MACRO ACTIONS:
    (all shown in the magenta color)
    • Set Action to Take: TRASH or DELETE

TAGS: @Search @Files @Folder @Move @Finder

USER SETTINGS:

  • Any Action in magenta color is designed to be changed by end-user

REQUIRES:

  1. Keyboard Maestro Ver 8.0.3+
  2. macOS Sierra+

USE AT YOUR OWN RISK

  • While I have given this limited testing, and to the best of my knowledge will do no harm, I cannot guarantee it.
  • If you have any doubts or questions:
    • Ask first
    • Turn on the KM Debugger from the KM Status Menu, and step through the macro, making sure you understand what it is doing with each Action.

That's incredibly complicated. Why can't we delete *.pdf in a folder without all the looping??

Hey Bobby,

Because that's the way Keyboard Maestro works.

If you want a simpler method then look at my AppleScript in Post #4, or use an Execute a Shell Script Action with the appropriate shell script.

-Chris

I'd already found a Terminal script solution. It's much simpler and extremely useful.

Hello, sorry if I resume an old thread. And please sorry about my bad English.

I need to move to trash all the MOV files in a directory. Usually, I convert a group of .mov videos with Handbrake, then I have both mov and mp4 files in a folder. So, I would like to trash all the .mov files with a hotkey.

I used the @ccstone macro, but I'm not a great coder. When I try to use it (I changed PDF to mov) I get this error: The item selected in the Finder is NOT a folder! Num: -2700

May someone help me?

Thanks in advance.

My MacOS version is 11.6.2 and the Keyboard Maestro version is 10.0.2

I'm no expert here, but that error message comes from the macro itself, suggesting that you neglected to select a folder in the Finder App before you ran the macro. If you read the comments to the macro it says this: (I'm guessing you missed step #1.)

HOW TO USE:

  1. Select the FOLDER in the Finder Containing the Files to Delete
  2. Trigger this macro
1 Like

I select the folder before triggering the macro, but I get this message...

Did you try a different folder? Or is that the only folder you tried?

Hi @stirner - I just tried ccstone's macro and it works for me.

However, I got the same error as you if I opened the folder first, so the Finder window was showing the contents of the folder.

I suggest you just click on the name of the folder and then run the macro - do not open the folder first! Give that a try and let us know how you get on.

(BTW - Sleepy is talking about JMichaelTX's macro, not the one you're using.)

1 Like

Hi @tiffle this is what I do:

rs

I use a hotkey inside the folder. How do you suggest me to do?

Good catch. Also, good catch about the "open folder" idea. I'll let you drive from here.

1 Like

OK - so now I've got egg on my face :slightly_frowning_face: as I can't for the life of me get the macro to work. Why it worked before but not now I have absolutely no idea.

Anyway - here's a new version of the macro in which I've changed the AppleScript to use a delete rather than a move.

Delete Files in the Selected Finder Folder by File-Extension v1.01.kmmacros (6.6 KB)

All you need do now is delete the previous version of the macro and install this one. I have already specified "mov" for you so there's no need to change anything else.

Let me know how you get on!

1 Like

@tiffle First of all, thank you very much for your help.

The bad news is I had the exact same problem. When I press the hotkey I get the message "The item selected in the Finder is NOT a folder!"

The good news is that I managed to get it to work - without knowing why, of course :slight_smile:

I deleted this part from the script:

if finderSelection's kind ≠ "Folder" then error "The item selected in the Finder is NOT a folder!"

and now it works :slight_smile:

ss

1 Like

Great - I’m glad you now have it working for you!

1 Like