2

By definition a line must end with newline character (\n) (ref.). But for the purpose of this post, I will consider any series of characters as a line whether or not it finishes with \n.

The command tail -n 1 returns the last line whether or not it ends with \n. How can one get from a file the last line that ends with \n whether or not this line is the last line or the second-to-last line of the file?

Community
  • 1
  • 1
Remi.b
  • 17,389
  • 28
  • 87
  • 168
  • A possible strategy would be to split on the newline character and check if the last group is empty (last line ends with a new line) or not (last line does not end with a new line). Based on that condition, you can select either the last group or the previous to last group. – HashPsi Jul 27 '15 at 22:10
  • I have tried that. The issue is that `tail` does not return the terminating \n whether or not it was present in the file. So the information is lost. – Remi.b Jul 27 '15 at 22:12
  • You have to rely on something other than tail to read the file. – HashPsi Jul 27 '15 at 22:14
  • @HashPsi Oh.. so something like `nblines=$(cat $file | wc -l); cat $file | cut -d \n nblines`? Yeah, I guess that would be a solution. – Remi.b Jul 27 '15 at 22:19
  • @EugeniuRosca I think your code always returns the second-to-last line whether or not the last one contains a \n (I haven't tried it though and I am not good at Bash!) – Remi.b Jul 27 '15 at 22:21
  • 1
    `(echo a; echo b; echo -n c) |tail -n1` returns "c"; `(echo a; echo b; echo c) |tail -n1` returns "c\n". Where's the problem? – Petr Skocik Jul 27 '15 at 22:24
  • @PSkocik hum indeed... hum...confusion in progress... Well, I guess I did something wrong before. Ok, so an easy solution is the one suggested by HashPsi, that is checking if the last line contains a newline and if not, just read the second-to-last line. I welcome one of you guys to post it as an answer. – Remi.b Jul 27 '15 at 22:33
  • 1
    @Remi.b Turns out it's when you save the output into bash variable that the last line gets trimmed if present. In any case, any solution that'll start with `tail -2` should have great performance (tail should seek right to the end if it can). – Petr Skocik Jul 27 '15 at 23:22

2 Answers2

6

Here's one way you could do it using Perl:

perl -ne '$s = $_ if /\n$/ }{ print $s' file

The script reads each line of the file one by one and assigns it to the variable $s if it ends with \n. Once the file has been read, $s is printed. If the last line didn't end with a newline, then the penultimate line will be printed, as shown below:

$ cat file
first line
second
third$ perl -ne '$s = $_ if /\n$/ }{ print $s' file
second

note that I intentionally left in the $ to show the prompt, which is at the end of the last line of the file due to the absence of the newline character.

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
5
cat -vte file|grep "\$$"|tail -1

What about this? Or some other way with cat -vte

This way the extra $ will be removed:

echo -en "Enter\nEnter again\nNo enter this time"|cat -vte|grep "\$$"|sed 's/\$$//g'|tail -1

+1 variant for linux (Perl regexp, positive look-ahead assertion, show matched part only):

echo -en "Enter\nEnter again\nNo enter this time"|cat -vte|grep -Po ".*(?=\\\$$)"|tail -1
zolo
  • 444
  • 2
  • 6
  • Yep that seems to work fine +1. It just adds an extra $ at the end of the line but it is not a big issue. I have no idea how it works though. I would very much appreciate if you wanted to explain a little bit more about how it works – Remi.b Jul 27 '15 at 22:25
  • There are several case (and file) when there are invisible control characters missing with you and you would like to see what's going on, so you try to make visible all non-visible characters by cat -vte, which will replace control characters (and end of lines as well) to something else, like ^M or $. cat -vte can use even later in a pipe chain: echo "...."|cat -vte |grep Other solution would be to use tr command if you can specify one exact character which surely wont be in your file, grep to that character then change things back. – zolo Jul 27 '15 at 22:28
  • If your shell able to handle nicely the special characters like ÷ or Ł then those could be the best choice for the tr. – zolo Jul 27 '15 at 22:31