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.
In addition to the approaches to which @tiffle points you, you could also:
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
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:
(() => {
'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();
})();
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:
Select the FOLDER in the Finder Containing the Files to Delete
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.)
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.