Numerically sort clipboard contents

Hi all,

I’m hoping to copy some cells in Numbers (each cell contains a number). I’m wanting to convert the clipboard to plain text, convert the carriage returns into a comma space, and then sort the numbers into ascending order. This is for an index for a book.

The clipboard with plain text values would change from
77
74
32

to
32, 74, 77

Any thoughts? Thanks.

One approach would be to use an Execute JavaScript for Automation action, and direct its output to the clipboard for pasting:

Paste copied col of numbers as comma-delimited sorted row.kmmacros (20.2 KB)

pasteSortedNumRow

JS Source

(() => {
    'use strict';

    const main = () =>
        sort(
            concatMap(
                x => {
                    const v = parseInt(strip(x), 10);
                    return isNaN(v) ? (
                        []
                    ) : [v]
                },
                lines(standardAdditions().theClipboard())
            )
        ).join(', ');

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

    // standardAdditions :: () -> Application
    const standardAdditions = () =>
        Object.assign(Application.currentApplication(), {
            includeStandardAdditions: true
        });

    // GENERIC FUNCTIONS ----------------------------------
    // https://github.com/RobTrew/prelude-jxa

    // lines :: String -> [String]
    const lines = s => s.split(/[\r\n]/);

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs.map(f))
        })() : [];

    // sort :: Ord a => [a] -> [a]
    const sort = xs => xs.slice()
        .sort((a, b) => a < b ? -1 : (a > b ? 1 : 0));

    // strip :: String -> String
    const strip = s => s.trim();

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

Holy cow your magic is unbelievable – works beautifully – I grovel in admiration!

Haha ! generous of you – I'm glad it's serviceable.

This works fairly easily:

Execute a Shell Script.kmactions (644 B)

1 Like

Very neat use of sort -n and input from ...

I think the OP is looking for a 32, 74, 77 output rather than

32
74
77

(perhaps with any selected gaps and non-numerics pruned out, for the purposes of a book index ?)

but I'm sure there are some elegant Bash routes to the horizontal comma-separation, and the filtering too.

Ahh, yes, I only saw the “sort” (as in the topic title), and not the adjusted layout.

Certainly, it can:

egrep '^\d+$'  | sort -n | paste -d ', ' -s -

But it is less simple at that point.

1 Like

Peter, thanks for the cool script.
It sorts fine, but the format is a bit off.
How can I adjust to make it consistent, with a ", " (comma space) after every number?

image

image

If the space is not essential, you can just do this (no need for egrep):

sort -n | paste -sd, -

If the space following the comma is essential, that's a bit trickier. Maybe something like this:

csv=$(cat | sort -n | paste -sd, -)
osascript <<< "set my text item delimiters to {\", \", \",\"}
text items of \"$csv\" as text"

or:

csv=$(cat | sort -n | paste -sd, -)
osascript -l JavaScript <<< "var csv=\"$csv\"; csv.replace(/,/g,\", \");"

but maybe that's cheating.

Surely there is a simple Bash command to replace "\n" with ",␣" ?
(what's the best symbol for SPACE?)
EDIT: "␣" is according to What character can I use to represent the space bar? - User Experience Stack Exchange

Sorry, my mistake, paste -d option does not work like I thought, it rotates through the characters.

So you'd have to follow the paste -d, with yet another pipe and replace the commas with comma-space.

You would think. Part of the issue is not wanting to include the trailing , which is why paste works well.

So a good solution would be:

paste -sd, - | sed 's/,/, /g'

Alternatively, perl has no problem changing the trailing \n into a comma-space.

perl -pe 's/\n/, /'

And some of this comes back to the discussion about a line ending character at the end of the text. I contend that normal multiline text ends with a linefeed. But either way you need to consider the end of the text and how that behaves as regards to replacing text. \n will only match a linefeed, but $ or \z or \Z will match at the end of the last line even if it does not have a trailing linefeed, although they are all zero width assertions so can't replace the linefeed.

Traditionally I have used a triangle (eg △), and word processors with “show invisibles” typically use a grey middle dot (eg ·) (BBEdit uses this for example). But realistically if you don't write it out no one is going to know what you mean.

This is harder than it looks.
This
image

produces this:
image

Perfect. . . except for the last comma.

Being ignorant in Bash, I resorted to JavaScript:

image

image

I am generally not a fan of compacting code just for the sake of doing so, but if we limit the input to the clipboard, the JavaScript could be:

image

var app = Application.currentApplication()
app.includeStandardAdditions = true

var sourceList = app.theClipboard().split(/[\r\n]/).map(e => {return parseInt(e, 10)});
sourceList.sort(sortNumber).join(', ');

function sortNumber(a,b) { return a - b; }

In case anyone is following the RegEx from the other thread, JavaScript does NOT support \R. So we have to use workarounds like [\r\n], which really should be:
\r?\n|\r

Where is @Tom? He is always really good at these games. :smile:

Yes, that was why paste was good, because it avoids that.

You can also remove the trailing “, ” with:

perl -pe 's/, $//'

so

perl -pe 's/\n/, /' | perl -pe 's/, $//'

But yes, a surprisingly complicated problem to get right. And a good reminder that you really have to know what the limitations on the input and what the desired output is.

1 Like

Not simple, but not hideous either, I finally came up with this:

printf '%s, ' $(cat | sort -n) | rev | cut -c 3- | rev
2 Likes

Bingo! that did the trick. The complete script is:

sort -n | perl -pe 's/\n/, /' | perl -pe 's/, $//'

image

image

Actually, that's simple enough. :+1:

I've got to learn Perl RegEx.


Bingo! Nice job.

Using Peter's Bash script from above, this should do the trick:

Example Output:

image


MACRO:   Sort Numbers on Clipboard and Format Output [Example] @Bash


#### DOWNLOAD:
<a class="attachment" href="/uploads/default/original/3X/c/5/c54e84508977cf0674da8ff602de29cd17ca4ab9.kmmacros">Sort Numbers on Clipboard and Format Output [Example] @Bash.kmmacros</a> (3.0 KB)
**Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.**

---



![image|489x563](upload://d1kgozmfslt2XfWyQYeJDFWZWSl.jpg)

I would also use "␣". Or, maybe easier to understand: "<space>"

Treating the stuff in-between the numbers as field separators (what they actually are), you can use Awk instead of regex replacements:

sort -n | awk  '$1=$1'  RS="" FS="\n" OFS=", "

This also eliminates the problem with the trailing comma, since awk automatically ignores any (superfluous) field separator at the end of the record.


Explanation:

With the variables at the end you set the different separators:

  • RS ‘Record Separator’: empty, because we don’t have any (it’s one record that ends with the end of text)
  • FS ‘[the original] Field Separator’: line feed
  • OFS ‘Output Field Separator’: the desired comma plus space (", ")

$1=$1 seems to be needed to force awk to reevaluate the fields.

A print is not needed since by default awk prints all fields. So, what awk actually does, is simply this:

  1. Learning the existing separators (RS, FS)
  2. Returning the whole record using the new field separator (OFS)

More info

2 Likes

awk is my favourite instrument in the shell cabinet – always repays a little experimentation.

See - Effective Awk Programming – 4th edition

1 Like

Wow! I have rarely, if ever, used awk, but it is evidently a major tool, with over 500 pages in its PDF manual:

GNU implementation: gawk

If you are like many computer users, you would frequently like to make changes in various text files wherever certain patterns appear, or extract data from parts of certain lines while discarding the rest. To write a program to do this in a language such as C or Pascal is a time-consuming inconvenience that may take many lines of code. The job is easy with awk, especially the GNU implementation: gawk.

Does the macOS use "GNU awk"?