Extract a Range of Lines from a Text File

Posted On Mon, 27 Aug 2012

Filed under Batch
Tags: , , ,

Comments Dropped leave a response

Below is a little Batch program to extract a range of lines from a text file and output them to the screen. A simple enough task, or so I thought!

Foreword

Please note that some knowledge of Batch is assumed. It is beyond the scope of this blog to teach programming. That said, see the Related Links section below for some pointers.

And if you are a Batch newbie, the best way to learn is as you go along. The simplest way to get help “on the fly” is to enter help | more /e from the command line which will spit out a list of commands that have built-in help.

For instance, if you want to know more about argument references (%0, %1, etc), type call /?. For some commands, the built-in help must be piped through more (eg: findstr /? | more /e, and ironically: more /? | more /e).

Slice

First, a big thank you to Batch-guru Jeb for sharing his wisdom about echo(, the !line:*:=! technique, and how to display characters like the exclamation mark even when delayed expansion is switched on.

Here is my bare-bones version. I named it slice.cmd, but feel free to call it whatever you like.

@echo off & setlocal enableextensions
set /a begin=%1-1,end=%2

if %begin% gtr 0 (set "skip=skip=%begin% ") else set skip=
for /f "%skip%delims=" %%a in ('findstr /n "^" "%~3"') do (
    set "line=%%a"
    set /a begin+=1
    setlocal enabledelayedexpansion
    if !begin! gtr %end% goto end
    set "line=!line:*:=!"
    echo(!line!
    endlocal
)

:end
endlocal & exit /b 0

Discussion

The problem is more complicated than you might think at first glance. The for /f loop suppresses blank lines and lines beginning with the badly-named “end-of-line” comment character (semi-colon by default). And there's the problem of echo outputting the infamous “poison characters” (! ` ^ & | < > ( ) = ~ etc).

The program gets around these problems by using findstr's /n switch to generate numbered output lines (line number followed by a colon). This tricks for /f into passing every line in the specified range—even blank lines or lines beginning with a semi-colon—to the body of the loop.

But now every line has an unwanted number and colon stuck to the front of it. To get rid of them, the program copies the value stored in the loop variable (%%a) into another variable called line. Only then is it safe to turn on delayed expansion. Next, set "line=!line:*:=!" truncates the line to the first colon found. And lastly, echo(!line! outputs the line to the screen.

By the way, you might be wondering why all the smoke and mirrors around the for /f loop's skip option? It's to avoid skip=0 which causes an error.

Caveats

No checking is done on the parameters whatsoever! The first and second arguments should be unsigned integers. The first argument should be less than or equal to the second. The final argument is a filename. No test is done to check if it's a folder or a file, or if it's empty, or if it even exists. And, as per usual, no usage info. These tasks are left as exercises to be completed by the reader 😉

Seriously, these are all topics for future posts. Subscribe to the RSS Posts feed or sign up for email notifications (see footer) to be kept up to date.

Related Links

This post began life as a thread on the alt.msdos.batch.nt newsgroup in September, 2011. See also Q69 of Prof. Timo Salmi's Batch FAQ for his treatment of a similar topic.

If you need a more substantive software solution, try head and tail which are included in the highly-recommended CoreUtils for Windows package. You can do cool stuff like head -50 file.txt | tail -20 which will output the 31st to 50th lines of file.txt.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s