How to read n-th line from the file?

I am using this to read line by line, what if I need to read a specific line?

Just use a variable as a counter, and quit when you get to the right line.

If this is too slow for you, let us know, and one of us can write you a script for it.

Maybe I misunderstood. I thought he was asking how to go straight to a specific line? @rover233 can you clarify?

Yes, I want a script to show me the content of nth line, say 5th or 10th.

What @DanThomas means, I think, is that I should run a counter within the loop and once the counter is equal to the desired line number, the script shows the contents.

1 Like

Ah I misunderstood Dan, then. That’s what I would do, too, since I can’t do much scripting.

The macro discussed here does read specific lines.

You can choose the JXA or the AppleScript solution.

Here is a simple way to do it in Keyboard Maestro. Basically just iterate through the lines (using the For Each action and the Lines In collection). Decrement a line counter for each line, and when it gets to zero, stop. The loop variable will retain the line in question.

Keyboard Maestro Actions.kmactions (2.4 KB)

The shell is your friend.

sed -n '3p' ~/test_directory/test

When used in an Execute a Shell Script action this will read the 3rd line of the designated file.

Change the number to change the line returned.

-Chris

1 Like

Thanks for that simple, great solution, Chris.

Since accessing KM Variables from the Shell can sometimes be a challenge, would you mind giving us a example of how to do that, with the line number coming from a KM Variable?

Hey JM,

No problem.

Get Numbered Line from a File v1.00.kmmacros (3.6 KB)

NOTE: I'm not doing any error-handling in this.

-Chris

2 Likes

Or getting an index into lines in the clipboard with some JS:

// Copy-pasted boiler-plate for 'Standard Additions'
// which give access to clipboard
var a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true, a),

   // Clipboard contents split on linefeeds or carriage returns
    lstLines = sa.theClipboard().split(/[\n\r]/),

   // KM variable string read as base 10 integer
    index = parseInt(
        Application('Keyboard Maestro Engine').getvariable('Nth'),
        10
    );

// JavaScript indexing starts at 0
lstLines[index - 1];

Tho, FWIW, I personally find it a bit clearer and more readable at a glance like this:

var a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true, a);

// Reading from innermost outwards:

// The string value of the KM variable 'Nth',
// parsed as a base 10 integer,
// and reduced by one to serve as a zero-based index,
// into a series of lines
// creating by splitting a text on each linefeed or carriage return.
// ( Where the text is acquired from the clipboard )

sa.theClipboard()
    .split(/[\n\r]/)[
        parseInt(
            Application('Keyboard Maestro Engine').getvariable('Nth'),
            10
        ) - 1
    ];

In my script above I chose sed, because I could do the job with a 1-liner, very-low-overhead script that will work very quickly with very large files.

If I was going with an AppleScript solution I’d do this:

set lineIndex to 3
set fileAlias to alias ((path to home folder as text) & "test_directory:test")
set lineText to item lineIndex of (read fileAlias using delimiter linefeed)

-Chris

2 Likes

What is the reason that you are not using AS’s paragraph here?

i.e.

[…]
set lineText to paragraph lineIndex of (read fileAlias)

Works with LFs, CRs and CRLFs.

Hey Tom,

Because I'm going for maximum efficiency, and the OP most likely knows his file-format already.

If I was uncertain of the given file's line-endings I'd use paragraphs if using vanilla AppleScript.

On my system I'd use the Satimage.osax:

set fileAlias to alias ((path to home folder as text) & "test_directory:test")
set lineText to item 3 of (find text ".+" in fileAlias with regexp, all occurrences and string result)

OR

set fileAlias to alias ((path to home folder as text) & "test_directory:Large_Doc_Test.txt")
set lineText to find text "\\A(?:.+$\\s?){99999}(.+)" using "\\1" in fileAlias with regexp and string result

For a really big file I'd still use sed, awk or Perl, or I'd figure out how to do it with AppleScriptObjC or JXA (provided there was a method for reading a specific line instead of reading all lines into an array).

-Chris

1 Like

Thanks, Chris. I wasn’t aware that paragraph is slower. (I rather thought the contrary, since it’s a “predefined” set.)

Hey Tom,

Not by much with a smallish number of paragraphs.

But to count the lines of a 110,000 line file:

Read with delim:  0.65 seconds
Paragraphs:      11.33 seconds

Viva la difference!

-Chris

2 Likes

Yep, that’s a difference! Never thought.

There is certainly something aesthetically pleasing about run-time efficiency and speed.

In the context of desktop scripting for personal work, though, I personally try to avoid it, if I can.

(10 seconds of machine run time is sometimes saved at the cost of 30 mins of human life :slight_smile:)

Something very weird:

I did several tests (with a ~1,500,000 lines file) and got the opposite results:

paragraph: almost instantaneous
using delimiters: ~5s in SE, KM; ~10s in SD (stopwatch)

The lines contain various, but non-unique, 5-digit numbers, in repeating patterns.

It would be interesting to see how the JXA .split() would compare with this.
Of course, JXA (JavaScript) can also easily use RegEx, so that would be another option.