views:

296

answers:

6

I am new to shell scripting, so I need some help here. I have a directory that fills up with backups. If I have more than 10 backup files, I would like to remove the oldest files, so that the 10 newest backup files are the only ones that are left.

So far, I know how to count the files, which seems easy enough, but how do I then remove the oldest files, if the count is over 10?

if [ls /backups | wc -l > 10]
    then
        echo "More than 10"
fi
A: 

Experiment with this, because I'm not 100% sure it'll work:

cd /backups; ls -at | tail -n +10 | xargs -I{} "rm '{}'"
barrycarter
Seems you need to remove the `l` from the `ls` options.
aioobe
Yikes! Telling an inexperienced user to 'experiment' with an incorrect `xargs rm` command? Wearing our BOFH hat today, are we?
Kilian Foth
I'm more of an "idea" man ;)
barrycarter
Ha, I am experimenting with it in a safe location. So, that didn't work, it tells me "No such file or directory" for each file there is...
Nic Hubbard
Also, it chokes when the file names have spaces in them...
Nic Hubbard
OK, I've edited it so that it might work now.
barrycarter
Naw, still says that same thing.
Nic Hubbard
You made me actually test! Try this version.
barrycarter
Ha, that removed all of the files.
Nic Hubbard
I'm just making stuff up now, but it might work
barrycarter
+1  A: 
stat -c "%Y %n" * | sort -rn | head -n +10 | \
        cut -d ' ' -f 1 --complement | xargs -d '\n' rm

Breakdown: Get last-modified times for each file (in the format "time filename"), sort them from oldest to newest, keep all but the last ten entries, and then keep all but the first field (keep only the filename portion).

Edit: Using cut instead of awk since the latter is not always available

Edit 2: Now handles filenames with spaces

bta
I usually use 'cut' for the last step because awk isn't always installed on all machines.
Jay
@Jay- A good point, I'll edit my answer accordingly
bta
+1  A: 

Try this:

ls -t | sed -e '1,10d' | xargs -d '\n' rm

This should handle all characters (except newlines) in a file name.

What's going on here?

  • ls -t lists all files in the current directory in decreasing order of modification time. Ie, the most recently modified files are first, one file name per line.
  • sed -e '1,10d' deletes the first 10 lines, ie, the 10 newest files. I use this instead of tail because I can never remember whether I need tail -n +10 or tail -n +11.
  • xargs -d '\n' rm collects each input line (without the terminating newline) and passes each line as an argument to rm.

As with anything of this sort, please experiment in a safe place.

Dale Hagglund
`+N` is the `Nth` so it would be `tail -n +11`.
Dennis Williamson
Oh, I can always figure it out eventually, I just find something non-intuitive about it every time. `sed -e 1,10d` does exactly what it says: delete the first 10 lines.
Dale Hagglund
+6  A: 

The proper way to do this type of thing is with logrotate.

Dennis Williamson
`logrotate` is a good answer, but it might be a bit heavy-weight: it needs a config file and it's at least somewhat biased toward logfiles semi-official places. Also, doesn't it assume that it should first rotate the logs (ie, rename .N to .N+1) and then delete the oldest? At least as written, the OP's question doesn't imply the rotation of a fixed name.
Dale Hagglund
A: 

Using inode numbers via stat & find command (to avoid pesky-chars-in-file-name issues):

stat -f "%m %i" * | sort -rn -k 1,1 | tail -n +11 | cut -d " " -f 2 | \
   xargs -n 1 -I '{}' find "$(pwd)" -type f -inum '{}' -print

#stat -f "%m %i" * | sort -rn -k 1,1 | tail -n +11 | cut -d " " -f 2 | \
#   xargs -n 1 -I '{}' find "$(pwd)" -type f -inum '{}' -delete 
bashfu
look at find's `-exec +` rather than use xargs and certainly not together.
SiegeX
A: 

I like the answers from @Dennis Williamson and @Dale Hagglund. (+1 to each)

Here's another way to do it using find (with the -newer test) that is similar to what you started with.

This was done in bash on cygwin...

if [[ $(ls /backups | wc -l) > 10 ]]
then
  find /backups ! -newer $(ls -t | sed '11!d') -exec rm {} \;
fi
DevNull