Wednesday, March 11, 2009

Episode #9 - Stupid Shell Tricks: Display the Nth Line

Hal Says:

Here's a stupid little shell idiom. How do you print only the nth line of a file? There are only about a dozen ways to do this in the Unix shell, but the one programmed into my wetware is:
 $ head -<n> <file> | tail -1

Paul Responds:

I'm kind of partial to awk, awk is my friend, its quick, dirty, and powerful (and I seem to learn about new techniques all the time, which makes it fun!):

 $ awk 'FNR == 42' file

Also, I like this command because its shorter, and despite popular belief UNIX people don't really like to type :)

Ed adds a little Windows perspective:

Ahhh... what I wouldn't give for head or tail in Windows. That sounds like a new motto for a Pauldotcom T-shirt or bumper sticker.

You can get most of the functionality you are describing here in Windows using the following construct:

C:\> find /v /n "" <file> | findstr /b /L [<n>] 

This may look crazy, but without head or tail, we need to trick Windows into doing what we want, as usual. What I'm doing here is using the find command to prepend line numbers (/n) to lines in the file that do not (/v) contain the string "". As we saw in Episode #3, searching for lines that do not have nothing shows all lines. Thus, the first portion of this command is actually prepending line numbers (in the form of [N], with the brackets) to each line in the file and sending them to standard out. I then pipe the result to the findstr command. The /b option tells findstr to display lines that have the string we are searching for at the beginning of a line. That way we won't get accidental collisions if [<n>] shows up inside of the file anywhere. We'll just be looking for the [<n>] that the find command prepended. I use a /L to indicate a literal string match. Otherwise, the double bracket around the n will confuse findstr.

The output is almost just what we want. There is one downside, though. There will be a [n] prepended to our line. But, that's pretty close, no?

Well, if you insist, you can remove that [n] with a FOR /F loop to do some parsing, but that starts to get really ugly if you just want to see the contents of the line. Anyway, because I luv ya, here goes:

C:\> find /v /n "" <file> | findstr /b /L [<n>] > temp.txt &
for /F "delims=[] tokens=2" %i in (temp.txt) do @echo %i & del temp.txt

Told you it was ugly. But, when you only have FOR /F loops to parse, you sometimes have to do this kind of thing.