List or drop matching lines

Can be done with grep in the shell, of course, but I do it quite a lot, and find it a little quicker to experiment with the help of the simple options of a Keyboard Maestro action. ( Saves me checking man grep for simple things at the command line, and lets me keep to the familiar Javascript dialect of Regular Expressions :- )

List or drop matching lines.zip (10.8 KB)

Custom Keyboard Maestro Plug-in

NAME

  • List or drop matching lines

VERSION

  • 0.1

SYNOPSIS

  • Returns a subset of the input lines

REQUIREMENTS

  • Yosemite
    • The core script listOrDropMatchingLines.sh is mainly written in Javascript for Applications

OPTIONS

  • Source clipboard or variable:
  • Include:
    • ONLY lines matching pattern
    • All EXCEPT matching lines
    • ALL lines
  • Matched line
    • contains
    • is
    • is not
    • begins with
    • ends with
    • matches Regex
  • Pattern
    • ( the literal text or regex pattern to be matched )
  • Ignore case ?
  • Remove blank lines from output ?
    • Can be used in combination with the Include: ALL lines option to simply strip out blank lines from a text
    • Also allows blank lines to pass through regardless of matching

INSTALLATION

  • Drag the .zip file onto the Keyboard Maestro icon in the OS X toolbar.
  • (if updating a previous version of the action, first manually remove the previous copy from the custom actions folder)
    • ~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Actions

CONTACT

7 Likes

Sorry post in an old thread, but would it be possible to have the PATTERN be dynamic? it is currently hardcoded. Ideally I'd like to be the result of a Prompt Action

1 Like

Haven't looked at this for a while – have you tried placing a %Variable%name% token in the pattern field ?

Hi. Find this could be very useful.

Is it possible just to output TRUE or FALSE instead of the matches?

I'll try to describe my problem:

I just need to know if a particular set of words is present in a line (or sentence)...

Can you help me accomplish that?

I think you'd have to show, as usual, a couple of samples of:

  • typical inputs
  • expected outputs

and explain the broader context of what you are trying to do, and where this fits into it.

Hello, thank you for your reply. It does not work with the %Variable%pattern%, but neither does it work with regular text. Given the macro is 7 years old it might not be compatible with the latest KM

aaaa
bbbb
cccc
aaa
aaaaaa
11111

when typing aaa, the expected output would be just the lines containing aaa, but nothing happens

I probably won't be able to look into that this week, but in the meanwhile, perhaps a partitioning macro would do what you need ?

e.g.
Partitioned lines.kmmacros (5.5 KB)

1 Like

FWIW, inspired by Rob's solution, a Haskell example:

Partitioned lines.kmmacros (3.3 KB)

Haskell Code:

import Data.Bifunctor
import Data.List

main :: IO ()
main =
  interact $
    unlines
      . uncurry (<>)
      . bimap ("Hits:" :) ("Misses:" :)
      . partition (isInfixOf "aaa")
      . lines
1 Like

Thank you so much for these macros guys! Thanks for taking the time to write and reply to this post :heart:

1 Like

Thank you Rob!

I used this technique to "remove" non-matching lines from the clipboard:

EDIT: I haven't added anything new here, but I thought I should post here anyway to help other users who are looking for a macro that removes (keeps/maintains) lines that meet certain conditions. Kind of SEO :).

1 Like

Hey Rob - (@complexpoint),

I know this is an old plugin but can you make the pattern area”variababale”? Asking for some friends.

:grin:

Thanks,

KC

P.S. - if you don’t want to be bothered with it, I get it. You are still my favorite “ stay away from Regex if possible guy”.

Happy holidays

@kcwhat

Could you try this draft ?

(Apart from replacing the incumbent at ~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Actions, I think you may also need to stop and restart the Keyboard Maestro engine, and/or reimport the custom action to your macro)

List or drop matching lines.zip (7,8 Ko)

FWIW this version (small edit), just aims to replace the supplied pattern string with the result of applying Application("Keyboard Maestro Engine").processTokens to it (with the current instance identifier)

2 Likes

@ComplexPoint!!!

You came through! Thank you, thank you, thank you! You made this super useful plugin way more powerful.

It works perfectly!

Happy New Year!

KC

1 Like

PS @kcwhat if you want to experiment with writing your match predicates as JS x => x.match("??") lambdas, then you may get more flexibility out of a subroutine like this (test macro followed by subroutine below)

Here, for example, partitioning the input list into a pair of JSON output lists (matching and non-matching lines) where the test is for lines which include "o" but not "e".

You can use JS string prototype functions like .matches, which accepts JS regular expressions, or things like .startsWith, .endsWith, or .includes

Test partition.kmmacros (3.9 KB)


SUBROUTINE

PARTITION of input lines.kmmacros (3,1 Ko)


Expand disclosure triangle to view JS source
const main = () => {
    const p = Function(
        `return ( ${kmvar.local_Predicate} )(arguments[0])`
    );

    return JSON.stringify(
        partition(p)(
            kmvar.local_Source.split("\n")
        )
    );
};

// first :: (a -> b) -> ((a, c) -> (b, c))
const first = f =>
    ([x, y]) => [f(x), y];

// second :: (a -> b) -> ((c, a) -> (c, b))
const second = f =>
    ([x, y]) => [x, f(y)];

// partition :: (a -> Bool) -> [a] -> ([a], [a])
const partition = p =>
    // A tuple of two lists - those elements in
    // xs which match p, and those which do not.
    xs => [...xs].reduce(
        (a, x) => (
            p(x)
                ? first
                : second
        )(ys => [...ys, x])(a),
        [[], []]
    );

return main();
1 Like

Thank you @ComplexPoint!

While you are a hero of mine, you may be giving me WAAAAAAY too much credit. I still parse and catch up from posts, of yours and the other km gurus, from years past. I don’t know JSON or any other programming languages but Keyboard Maestro and this forum’s resources give me “Jedi-like”powers. I cut and paste the code with the best of them. I will bookmark this and try to understand. From time to time, the lights do flash though!

Thank you for the lesson and lessons! Keep posting your brilliant plugins. I still use this one and others from a decade ago !

KC

1 Like

In case you do experiment with JS, and you are partitioning very long lists of lines, then a faster implementation of partition might be something like:

// partition :: (a -> Bool) -> [a] -> ([a], [a])
const partition = p =>
    // A tuple of two lists - those elements in
    // xs which match p, and those which do not.
    xs => {
        const
            matches = [],
            nons = [];

        xs.forEach(x => {
            if (p(x)) {
                matches.push(x)
            } else {
                nons.push(x)
            }
        });

        return [matches, nons];
    };