[bash] Remove the last line from a file in Bash

I have a file, foo.txt, containing the following lines:

a
b
c

I want a simple command that results in the contents of foo.txt being:

a
b

This question is related to bash command-line scripting truncate

The answer is


I had trouble with all the answers here because I was working with a HUGE file (~300Gb) and none of the solutions scaled. Here's my solution:

dd if=/dev/null of=<filename> bs=1 seek=$(echo $(stat --format=%s <filename> ) - $( tail -n1 <filename> | wc -c) | bc )

In words: Find out the length of the file you want to end up with (length of file minus length of length of its last line, using bc) and, set that position to be the end of the file (by dding one byte of /dev/null onto it).

This is fast because tail starts reading from the end, and dd will overwrite the file in place rather than copy (and parse) every line of the file, which is what the other solutions do.

NOTE: This removes the line from the file in place! Make a backup or test on a dummy file before trying it out on your own file!


awk 'NR>1{print buf}{buf = $0}'

Essentially, this code says the following:

For each line after the first, print the buffered line

for each line, reset the buffer

The buffer is lagged by one line, hence you end up printing lines 1 to n-1


Ruby(1.9+)

ruby -ne 'BEGIN{prv=""};print prv ; prv=$_;' file

You can try this method also : example of removing last n number of lines.

a=0 ; while [ $a -lt 4 ];do sed -i '$ d' output.txt; a=expr $a + 1;done

Removing last 4 lines from file(output.txt).


To remove the last line from a file without reading the whole file or rewriting anything, you can use

tail -n 1 "$file" | wc -c | xargs -I {} truncate "$file" -s -{}

To remove the last line and also print it on stdout ("pop" it), you can combine that command with tee:

tail -n 1 "$file" | tee >(wc -c | xargs -I {} truncate "$file" -s -{})

These commands can efficiently process a very large file. This is similar to, and inspired by, Yossi's answer, but it avoids using a few extra functions.

If you're going to use these repeatedly and want error handling and some other features, you can use the poptail command here: https://github.com/donm/evenmoreutils


Here is a solution using sponge (from the moreutils package):

head -n -1 foo.txt | sponge foo.txt

Summary of solutions:

  1. If you want a fast solution for large files, use the efficient tail or dd approach.

  2. If you want something easy to extend/tweak and portable, use the redirect and move approach.

  3. If you want something easy to extend/tweak, the file is not too large, portability (i.e., depending on moreutils package) is not an issue, and you are a fan of square pants, consider the sponge approach.

A nice benefit of the sponge approach, compared to "redirect and move" approaches, is that sponge preserves file permissions.

Sponge uses considerably more RAM compared to the "redirect and move" approach. This gains a bit of speed (only about 20%), but if you're interested in speed the "efficient tail" and dd approaches are the way to go.


echo -e '$d\nw\nq'| ed foo.txt

Both of these solutions are here in other forms. I found these a little more practical, clear, and useful:

Using dd:

BADLINESCOUNT=1
ORIGINALFILE=/tmp/whatever
dd if=${ORIGINALFILE} of=${ORIGINALFILE}.tmp status=none bs=1 count=$(printf "$(stat --format=%s ${ORIGINALFILE}) - $(tail -n${BADLINESCOUNT} ${ORIGINALFILE} | wc -c)\n" | bc )
/bin/mv -f ${ORIGINALFILE}.tmp ${ORIGINALFILE}

Using truncate:

BADLINESCOUNT=1
ORIGINALFILE=/tmp/whatever
truncate -s $(printf "$(stat --format=%s ${ORIGINALFILE}) - $(tail -n${BADLINESCOUNT} ${ORIGINALFILE} | wc -c)\n" | bc ) ${ORIGINALFILE}

Here's how you can do it manually (I personally use this method a lot when I need to quickly remove the last line in a file):

vim + [FILE]

That + sign there means that when the file is opened in the vim text editor, the cursor will be positioned on the last line of the file.

Now just press d twice on your keyboard. This will do exactly what you want—remove the last line. After that, press : followed by x and then press Enter. This will save the changes and bring you back to the shell. Now, the last line has been successfully removed.


Mac Users

if you only want the last line deleted output without changing the file itself do

sed -e '$ d' foo.txt

if you want to delete the last line of the input file itself do

sed -i '' -e '$ d' foo.txt


OK processing a good amount of data and the output was OK, but had one junk line.

If I piped the output of the script to:

| sed -i '$ d' I would get the following error and finally no output at all sed: no input files

But | head -n -1 worked!


awk "NR != `wc -l < text.file`" text.file |> text.file

This snippet does the trick.


This is by far the fastest and simplest solution, especially on big files:

head -n -1 foo.txt > temp.txt ; mv temp.txt foo.txt

if You want to delete the top line use this:

tail -n +2 foo.txt

which means output lines starting at line 2.

Do not use sed for deleting lines from the top or bottom of a file -- it's very very slow if the file is large.


Linux

$ is the last line, d for delete:

sed '$d' ~/path/to/your/file/name

MacOS

Equivalent of the sed -i

sed -i '' -e '$ d' ~/path/to/your/file/name

For Mac Users :

On Mac, head -n -1 wont work. And, I was trying to find a simple solution [ without worrying about processing time ] to solve this problem only using "head" and/or "tail" commands.

I tried the following sequence of commands and was happy that I could solve it just using "tail" command [ with the options available on Mac ]. So, if you are on Mac, and want to use only "tail" to solve this problem, you can use this command :

cat file.txt | tail -r | tail -n +2 | tail -r

Explanation :

1> tail -r : simply reverses the order of lines in its input

2> tail -n +2 : this prints all the lines starting from the second line in its input


Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to command-line

Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) Flutter command not found Angular - ng: command not found how to run python files in windows command prompt? How to run .NET Core console app from the command line Copy Paste in Bash on Ubuntu on Windows How to find which version of TensorFlow is installed in my system? How to install JQ on Mac by command-line? Python not working in the command line of git bash Run function in script from command line (Node JS)

Examples related to scripting

What does `set -x` do? Creating an array from a text file in Bash Windows batch - concatenate multiple text files into one Raise error in a Bash script How do I assign a null value to a variable in PowerShell? Difference between ${} and $() in Bash Using a batch to copy from network drive to C: or D: drive Check if a string matches a regex in Bash script How to run a script at a certain time on Linux? How to make an "alias" for a long path?

Examples related to truncate

Scala Doubles, and Precision Truncating Text in PHP? How can I truncate a double to only two decimal places in Java? SQL TRUNCATE DATABASE ? How to TRUNCATE ALL TABLES How to truncate a foreign key constrained table? Remove the last line from a file in Bash I want to truncate a text or line with ellipsis using JavaScript Limiting double to 3 decimal places I got error "The DELETE statement conflicted with the REFERENCE constraint" Truncate with condition