18

I asked a question regarding popping the last line of a text file in PHP, and now, is it possible to re-write the logic in shell script?

I tried this to obtain the last line:

tail -n 1 my_log_file.log

but I am not sure how can I remove the last line and save the file.

P.S. given that I use Ubuntu server.

Community
  • 1
  • 1
Raptor
  • 53,206
  • 45
  • 230
  • 366

4 Answers4

34

To get the file content without the last line, you can use

head -n-1 logfile.log

(I am not sure this is supported everywhere)

or

sed '$d' logfile.log
choroba
  • 231,213
  • 25
  • 204
  • 289
  • Perfecto!!!! nice one – Satish Apr 23 '15 at 18:54
  • 1
    The sed one works perfectly! $d is the command that quit before the last line that's why it can skip the last line! See https://www.gnu.org/software/sed/manual/sed.html . – nothize Jan 07 '16 at 06:16
26

What you want is truncate the file just before the last line without having to read the file entirely.

truncate -s -"$(tail -n1 file | wc -c)" file

That's assuming the file is not currently being written to.

truncate is part of the GNU coreutils (so generally found on recent Linux distributions) and is not a standardized Unix or POSIX command. Many "dd" implementations can be used to truncate a file as well.

Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31
  • Using `tail` + `truncate` would indeed be a more efficient solution. Naturally, to allow the OP to retrieve the contents of the last line `tail -n` should be called separately and stored in a var. You can then use `${#VAR} + 1` as the truncation size. – Shawn Chin Aug 29 '12 at 12:39
  • Added a [wiki answer](http://stackoverflow.com/questions/12176492/shell-delete-the-last-line-of-a-huge-text-log-file/12176634#12176634) with proposed changes. – Shawn Chin Aug 29 '12 at 12:53
  • 3
    There seems to be an extra - preceding the tail/wc combo. Also, you could use the `-r` option (use the given file's size) here: `truncate -r <(tail -n1 file) file` if you don't mind the bashism. – chepner Aug 29 '12 at 13:02
  • 1
    Process substitution is a kshism (also supported by zsh and bash). It won't work because <(tail -n1 file) is a pipe and because we want to shave off that many files, not truncate the file to the same size as its last line. – Stephane Chazelas Aug 29 '12 at 15:58
  • 1
    This should be the answer. Quick, easy, and effective. Just did it on a 5.2GB file instantly. – mcsilvio Aug 23 '18 at 15:20
16

(Solution is based on sch's answer so credit should go to him/her)

This approach will allow you to efficiently retrieve the last line of the file and truncate the file to remove that line. This can better deal with large inputs as the file is not read sequentially.

# retrieve last line from file
LAST=$(tail -n 1 my_log_file.log)

# truncate file
let TRUNCATE_SIZE="${#LAST} + 1"
truncate -s -"$TRUNCATE_SIZE" my_log_file.log

# ... $LAST contains 'popped' last line

Note that this will not work as expected if the file is modified between the calls to tail and truncate.

Community
  • 1
  • 1
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
  • 1
    Note that contrary to the "wc" based solution I gave which would work on GNU systems, the above will only work correctly with zsh if the last line contains NUL characters. Also, it won't work correctly if the last line is not NL terminated. – Stephane Chazelas Aug 29 '12 at 15:54
  • Also, `truncate` expects a size in _bytes_, while `${#LAST}` computes the length of `$LAST` in number of characters. You'd want to set the locale to C for that to be equivalent. Otherwise, that wouldn't work properly if the last line contained multi-byte characters. – Stephane Chazelas Nov 29 '17 at 21:16
4

One way is:

sed '$d' < f1 > f2 ; mv f2 f1
codaddict
  • 445,704
  • 82
  • 492
  • 529