7

This question already has an answer here:

I have a poem with an unknown number of rows and I want to display only the penultimate one. What command should I use?

marked as duplicate by don_crissti, Stéphane Chazelas text-processing May 9 '18 at 11:48

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

19

There are many ways to do that, but this is the fastest one I've found -- and is the cleanest in my opinion.

Assuming that the poem is written in a file named poem, you can use:

tail -n 2 poem | head -n 1

tail -n 2 poem will write the last 2 lines of the file poem.

head -n 1 will write the first line of the output provided by the previous tail command.

6

Use ed, man!

ed -s poems <<< $'$-1\n'

This tells ed to open the poems file in script mode (-s) (so that it doesn't print extra messages), then sends an addressing command in a here-string that says "go to the last line of the file ($), minus 1", which prints that line.

Given an input poem file of:

A is for awk, which runs like a snail, and
B is for biff, which reads all your mail.

C is for cc, as hackers recall, while
D is for dd, the command that does all.

E is for emacs, which rebinds your keys, and
F is for fsck, which rebuilds your trees.

G is for grep, a clever detective, while
H is for halt, which may seem defective.

I is for indent, which rarely amuses, and
J is for join, which nobody uses.

K is for kill, which makes you the boss, while
L is for lex, which is missing from DOS.

M is for more, from which Less was begot, and
N is for nice, which it really is not.

O is for od, which prints out things nice, while
P is for passwd, which reads in strings twice.

Q is for quota, a Berkeley-type fable, and
R is for ranlib, for sorting ar sic table.

S is for spell, which attempts to belittle, while
T is for true, which does very little.

U is for uniq, which is used after Sort, and
V is for vi, which is hard to abort.

W is for whoami, which tells you your name, while
X is, well, X, of dubious fame.

Y is for yes, which makes an impression, and
Z is for zcat, which handles compression.

... the result output is:

Y is for yes, which makes an impression, and
  • With GNU ed and ed on OpenBSD, it's actually enough to run the ed script -1. I believe that the POSIX spec. for ed requires this to be an error though. – Kusalananda May 7 '18 at 17:15
5

You would do

sed '2q;d' <(tac infile)

tac well print the infile file in reverse order unlike cat and pass it as input to sed and that will delete everyline ( here only first line) except the second and then quit immediately.

or alternatively:

tail -n2 infile | sed '2d'

Or with sed only

sed 'x;$!d' <infile

The sed is reading one line at a time and with hold-space x we are saving the current line processing and will print it !d (don't delete) once sed read all lines (or it's in last line) and since sed only can have one hold-space, so when it's last line the hold-space contains second last line; this is same as:

sed -n 'x;$p' <infile
3

Assuming your poem is in the poem text document:

< poem tail -n 2 | head -n 1
  • Why the redirection when you give the filename as argument to tail? Also it is much more common to append the redirection that to write it in front of the command. This way it looks more cryptic than it needs to be. – BlackJack May 7 '18 at 16:09
  • 5
    @BlackJack, there are many advantages. The first of which is that it allows you to but the filename at the beginning of the pipeline (which IMO is very common and not cryptic and shows better the flow of data), it avoids running tail if the file can't be opened and works with arbitrary file names (tail -n 2 "$file" would fail for $files that start with -. tail -n 2 -- "$file" would still fail for a file called -) – Stéphane Chazelas May 7 '18 at 16:21
  • 1
    I've seen this the very first time so IMO it's not common. ☺ The flow is IMO better shown by tail -n 2 < poem because the < symbolises an arrow depicting the flow from poem into tail. – BlackJack May 7 '18 at 22:47
1

awk

This works with GNU awk (Linux) and BSD awk (Mac).

You might want to ignore blank lines. In that case, you could use awk 'NF' file.txt, and then pipe the output through one of the other methods described on this page.

You could also do it all in one pass through awk:

awk 'NF { a=b ; b=$0 } END { print a }' file.txt
  • NF
    Only process lines that contain data. NF stands for the number of fields in the line; values greater than 0 are treated as "true".
    • { a=b ; b=$0 }
      Store the current non-blank line as b, and the previous non-blank line as a.
  • END { print a }
    After examining the entire file, print the final value of a (the penultimate non-blank line).

If you don't want to omit the blank lines, just remove the NF:

awk '{ a=b ; b=$0 } END { print a }' file.txt

Not the answer you're looking for? Browse other questions tagged or ask your own question.