Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I'm trying to keep a directory full of log files manageable. Nightly, I want to delete all but the 10 most recent. How can I do this in a single command?

share|improve this question

migrated from stackoverflow.com Apr 8 '11 at 14:36

This question came from our site for professional and enthusiast programmers.

3  
have a look at logrotate –  knittl Apr 8 '11 at 15:26

7 Answers 7

The code you'd want to include in your script is

 rm -f $(ls -1t /path/to/your/logs/ | tail -n +11)

The -1 (numeric one) option prints each file on a single line, to be safe. The -f option to rm tells it to ignore non-existent files for when ls returns nothing.

share|improve this answer
    
Original poster, here. I tried this command, but it doesn't work--I get the error "rm: missing operand" –  user75814 Apr 10 '11 at 13:50
2  
Did you use the correct path? Obviously /path/to/your/logs is just made up for you to enter the correct path. To find the bug execute the parts ls -t /path/... and ls -t /path/... | tail -n +11 alone and check if the result is correct. –  Daniel Böhmer Apr 11 '11 at 9:01
1  
@HaukeLaging I am quite sure you got something wrong. Mind the + before the number. It makes tail not print the last n elements but all elements beginning from the n'th. I checked in again and the command should definitely remove only elements older than 10 most recent files under all circumstances. –  Daniel Böhmer Mar 14 '13 at 14:01
    
@halo Indeed, sorry. –  Hauke Laging Mar 14 '13 at 17:41
    
Using $() and ``` `` ``` on the commandline, especially with a command that lists filenames runs the risk of splitting filenames with spaces in them in to separate arguments. See my answer below for a safer and more portable solution. –  Isaac Freeman Aug 4 '13 at 23:38

For a portable and reliable solution, try this:

ls -1tr | head -n -10 | xargs -d '\n' rm -f

The tail -n -10 syntax in one of the other answers doesn't seem to work everywhere (i.e., not on my RHEL5 systems).

And using $() or `` on the command line of rm runs the risk of

  1. splitting file names with whitespace, and
  2. exceeding the maximum commandline character limit.

xargs fixes both of these problems because it'll automatically figure out how many args it can pass within the character limit, and with the -d '\n' it will only split at the line boundary of the input. Technically this can still cause problems for filenames with a newline in them, but that's far less common than filenames with spaces, and the only way around the newlines would be a lot more complicated, probably involving at least awk, if not perl.

If you don't have xargs (old AIX systems, maybe?) you could make it a loop:

ls -1tr | head -n -10 | while read f; do
  rm -f "$f"
done

This will be a bit slower because it spawns a separate rm for each file, but will still avoid caveats 1 and 2 above (but still suffers from newlines in file names).

share|improve this answer
    
@HaukeLaging: From the head(1) man page: -n, --lines=[-]K print the first K lines instead of the first 10; with the leading '-', print all but the last K lines of each file –  Isaac Freeman May 15 '13 at 6:06
    
On Solaris 10 head and xargs give errors. Correct options are head -n 10 and xargs rm -f. Full command is ls -1tr ./ | head -n 10 | xargs rm -rf –  DmitrySandalov Oct 31 at 13:36

A tool like logrotate does this for you. It makes log management much easier. You can also include additional cleanup routines shuch as halo sugggested.

share|improve this answer

bash:

ls -1t | (i=0; while read f; do
  if [ $i -lt 10 ]; then
    ((i++))
    continue
  else
    rm -f "$f"
  fi
done)

This skips the 10 newest als deletes the rest. logrotate may be better, I just want to correct the wrong shell related answers.

share|improve this answer

From here:

#! /bin/sh
# keepnewest
#
# Simple directory trimming tool to handle housekeeping
# Scans a directory and deletes all but the N newest files
#
# Usage: cleanup <dir> <number of files to keep>
#
# v 1.0 Piers Goodhew 1/mar/2007. No rights retained.


if [ $# -ne 2 ]; then
  echo 1>&2 "Usage: $0 <dir> <number of files to keep>"
  exit 1
fi

cd $1
files_in_dir=`ls | wc -l`
files_to_delete=`expr $files_in_dir - $2`
if [ $files_to_delete -gt 0 ]; then
  ls -t | tail -n $files_to_delete | xargs rm
  if [ $? -ne 0 ]; then
    echo "An error ocurred deleting the files"
    exit 1
  else
    echo "$files_to_delete file(s) deleted."
  fi
else
  echo "nothing to delete!"
fi
share|improve this answer
1  
Thanks for providing this answer. While providing code like this is helpful, it also helps to provide some additional information about how it might be used or what the code does. You can use the edit button to make future improvements to your post. –  nhinkle Mar 21 '13 at 5:23

Apparently parsing ls is evil, http://mywiki.wooledge.org/ParsingLs.

If each file is created daily and you want to keep files created within the last 10 days you can do:

find /path/to/files -mtime 10 -delete

Or if each file is created arbitrarily:

find /path/to/files -maxdepth 1 -type f -printf '%Ts\t%P\n' | sort -n | head -n -10 | cut -f 2- | xargs rm -rf
share|improve this answer

I modified Isaac's approach a little bit.

It now works with a desired path:

ls -d -1tr /path/to/folder/* | head -n -10 | xargs -d '\n' rm -f
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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