I want a shell script that keep all file with time-stamp of the same month as the current time + the latest file before that, and delete the rest of the files in the directory.

All filename stored in the directory are structured as name$timestamp.extension where

timestamp=`date "+%Y%m%d-%H%M%S"`

So, this means that if there is the following file in the directory:


The remaining file in the directory after the execution of this code will be:


PS. Extremely new to shell. Would love to not only have a working code, but also to learn. So, if you would be so kind, please explain the code as well. Thank you!

  • Can we rely on the mtime of the files and not just the filenames?
    – jayhendren
    Dec 14, 2016 at 23:14
  • @jayhendren I wish I could use this: find filename -mtime +31 -type f -delete But, there are months that are 28, 30, 31 days as well. So, this method would not be inclusive enough keep "all files of the same month as the current time" + "the latest file before that".
    – Perth
    Dec 14, 2016 at 23:22
  • that's ok, i was just wondering if the mtime is safe to key off of or if you can only use the name of the file to determine which ones to delete.
    – jayhendren
    Dec 14, 2016 at 23:31
  • @jayhendren I see what you mean. mtime is safe.
    – Perth
    Dec 14, 2016 at 23:57
  • just a off-topic hint: you might want to use 'backintime' as backup program which does what you require with default unix tools and data deduplication of your backups
    – Jaleks
    Dec 15, 2016 at 0:24

3 Answers 3


With zsh you could do something like

# get current date (YYMM) in a variable 
crd=$(date "+%y%m")
# use a function to extract the 13 chars that make up the timestamp
dtts() REPLY=${${REPLY%%.*}: -13}
# sort file names by the timestamp in descending order, exclude the ones with the
# same YYMM as the current date and set the remaining file names as arguments
set -- *(.O+dtts^e_'[[ "${${REPLY%%.*}: -13:4}" == "$crd" ]]'_)
# remove the first item from the list (i.e. the last one before current month)
# print the remaining file names
print -rl -- "$@"

This uses parameter expansion and glob qualifiers:
it first selects regular files (.) sorts in descending order (O) by the timestamp using the dtts function, then the negated estring ^e_'[[ "${${REPLY%%.*}: -13:4}" == "$crd" ]]'_ deselects files if the timestamp matches current year and month (i.e. if the expression inside quotes returns true); shift then removes the first item from the list (since the names were sorted in descending order that would be the latest timestamp before current month)
Replace print -rl with rm if you're happy with the result.

  • zsh code was run on terminal platform. The function to extract 13 chars has an unexpected token - precisely at : -13
    – Perth
    Dec 16, 2016 at 16:44
  • @Perth - my post is pretty clear... it says with zsh. What you did was to open a terminal and simply run the code (and your shell is bash not zsh) hence the error bash: syntax error near unexpected token 'REPLY=${${REPLY%%.*}: -13}' Dec 16, 2016 at 17:45
  • I see. So, bash and zsh cannot work together -Thanks for the clarification.
    – Perth
    Dec 16, 2016 at 18:32
  • @Perth - no, your either run one or the other. Just install zsh (if not already installed on your system) and then open a terminal and execute zsh and then my code... Dec 16, 2016 at 18:34

a bash solution would be:


keep=$(date '+%y%m')
rm `find . -name "name*" -and -not -name "name$keep*" | sort | head -n-1`
  • set variable $keep to current year (2-digit) and month.
  • remove the result of the backticks-enclosed code which is
    1. find all filenames in current directory (and subdirectories) that start with "name" and don't start with "name$keep"
    1. sort the results
    1. remove last line

however, i would not use a pure shell script in such cases, because the code might get complicated (and thus less maintainable) very fast.

you could use perl (or python) instead:

#!/usr/bin/env perl

use strict;
use warnings;
use POSIX qw(strftime);

# collect the names of all files older than current month.
# we assume that there are no files from future.
my $keep_date = strftime("%y%m", localtime);
my @files = sort grep {!/^name$keep_date/} glob "*.gz";

# the newest file from the collected files shall not be deleted
pop @files; 

# delete all collected files
map {unlink} @files;

or directly from the command line:

perl -We 'use POSIX qw(strftime); my $keep_date = strftime("%y%m", localtime); my @files = sort grep {!/^name$keep/} glob "*.gz"; pop @files; map {unlink} @files;'

Another zsh approach:

# today as YYMMDD using prompt expansion

# construct patterns
month="name<$now[1,4]01-$now>-<->.gz" all="name<-$now>-<->.gz"

# perform globbing (expand patterns to list of matching files)
month=($~month(N))                    all=($~all)
() (rm -f -- $argv[1,-2]) ${all:|month}

That's using ${all:|month} array subtraction where the $all array is built from the list of files in the any date to now range and $month built from the list of files in the first day of month to now range.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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