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.