How to delete all directories in a directory older than 2 weeks except the latest one that match a file pattern?
Introduction
The question has been modified.
My first alternative (the oneliner) does not match the new specification, but saves the newest directory among the directories that are old enough to be deleted (older than 14 days).
I made a second alternative, (the shellscript) that uses
@ seconds since Jan. 1, 1970, 00:00 GMT, with fractional part.
and subtracting the seconds corresponding to 14 days to get a timestamp for the 'limit-in-seconds' at
seclim
in the sorted list of directories.
1. Oneliner
The previous answers are clean and nice, but they do not preserve the newest follower
directory. The following command line will do it (and can manage names with spaces but names with newlines create problems),
find . -type d -name "follower*" -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r
tested on this directory structure,
$ find -printf "%T+ %p\n"|sort
2019-01-10+13:11:40.6279621810 ./follower1
2019-01-10+13:11:40.6279621810 ./follower1/2/3
2019-01-10+13:11:40.6279621810 ./follower1/2/dirnam with spaces
2019-01-10+13:11:40.6279621810 ./follower1/2/name with spaces
2019-01-10+13:11:56.5968732640 ./follower1/2/file
2019-01-10+13:13:18.3975675510 ./follower2
2019-01-10+13:13:19.4016254340 ./follower3
2019-01-10+13:13:20.4056833250 ./follower4
2019-01-10+13:13:21.4097412230 ./follower5
2019-01-10+13:13:22.4137991260 ./follower6
2019-01-10+13:13:23.4138568040 ./follower7
2019-01-10+13:13:24.4219149500 ./follower8
2019-01-10+13:13:25.4259728780 ./follower9
2019-01-10+13:15:34.4094596830 ./leader1
2019-01-10+13:15:36.8336011960 .
2019-01-10+13:15:36.8336011960 ./leader2
2019-01-10+13:25:03.0751878450 ./follower1/2
like so,
$ find . -type d -name "follower*" -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r
rm -r ./follower1 ./follower2 ./follower3 ./follower4 ./follower5 ./follower6 ./follower7 ./follower8
So follower9
is excluded because it is the newest follower
directory (directories with names, that do not start with follower
(leader1
, leader2
and 2
are not in the game).
Now we add the time criterion, -mtime +14
and do another 'dry run' to check that it works like it should, when we have changed directory to where there are real follower
directories,
find . -type d -name "follower*" -mtime +14 -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r
Finally we remove echo
and have a command line that can do what we want,
find . -type d -name "follower*" -mtime +14 -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs rm -r
find
in the current directory, directories with names beginning withfollower
, that are not modified since 14 days ago.- After printing and sorting
head -n -1
will exclude the newestfollower
directory. - The time stamps are cut away, and double quotes are added at the head end and tail end of each directory name.
- Finally the result is piped via
xargs
as parameters torm -r
in order to remove the directories, that we want to remove.
2. Shellscript
I made a second alternative, (the shellscript) that uses
@ seconds since Jan. 1, 1970, 00:00 GMT, with fractional part.
It has also two options,
-n
dry run-v
verboseI modified the shellscript according to what the OP wants: enter the pattern as a parameter within single quotes e.g. 'follower*'.
I suggest that the name of the shellscript is
prune-dirs
because it is more general now (no longer onlyprune-followers
to prune directoriesfollower*
).
You are recommended to run the shellscript with both options the first time in order to 'see' what you will do, and when it looks correct, remove the -n
to make the shellscript remove the directories that are old enough to be removed. So let us call it prune-dirs
and make it executable.
#!/bin/bash
# date sign comment
# 2019-01-11 sudodus version 1.1
# 2019-01-11 sudodus enter the pattern as a parameter
# 2019-01-11 sudodus add usage
# 2019-01-14 sudodus version 1.2
# 2019-01-14 sudodus check if any parameter to the command to be performed
# Usage
usage () {
echo "Remove directories found via the pattern (older than 'datint')
Usage: $0 [options] <pattern>
Examples: $0 'follower*'
$0 -v -n 'follower*' # 'verbose' and 'dry run'
The 'single quotes' around the pattern are important to avoid that the shell expands
the wild card (for example the star, '*') before it reaches the shellscript"
exit
}
# Manage options and parameters
verbose=false
dryrun=false
for i in in "$@"
do
if [ "$1" == "-v" ]
then
verbose=true
shift
elif [ "$1" == "-n" ]
then
dryrun=true
shift
fi
done
if [ $# -eq 1 ]
then
pattern="$1"
else
usage
fi
# Command to be performed on the selected directories
cmd () {
echo rm -r "$@"
}
# Pattern to search for and limit between directories to remove and keep
#pattern='follower*'
datint=14 # days
tmpdir=$(mktemp -d)
tmpfil1="$tmpdir"/fil1
tmpfil2="$tmpdir"/fil2
secint=$((60*60*24*datint))
seclim=$(date '+%s')
seclim=$((seclim - secint))
printf "%s limit-in-seconds\n" $seclim > "$tmpfil1"
if $verbose
then
echo "----------------- excluding newest match:"
find . -type d -name "$pattern" -printf "%T@ %p\n" | sort |tail -n1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/'
fi
# exclude the newest match with 'head -n -1'
find . -type d -name "$pattern" -printf "%T@ %p\n" | sort |head -n -1 >> "$tmpfil1"
# put 'limit-in-seconds' in the correct place in the sorted list and remove the timestamps
sort "$tmpfil1" | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' > "$tmpfil2"
if $verbose
then
echo "----------------- listing matches with 'limit-in-seconds' in the sorted list:"
cat "$tmpfil2"
echo "-----------------"
fi
# create 'remove task' for the directories older than 'limit-in-seconds'
params=
while read filnam
do
if [ "${filnam/limit-in-seconds}" != "$filnam" ]
then
break
else
params="$params $filnam"
fi
done < "$tmpfil2"
cmd $params > "$tmpfil1"
cat "$tmpfil1"
if ! $dryrun && ! test -z "$params"
then
bash "$tmpfil1"
fi
rm -r $tmpdir
- Change current directory to the directory with the
follower
subdirectories - create the file
prune-dirs
- make it executable
and run with the two options
-v -n
cd directory-with-subdirectories-to-be-pruned/ nano prune-dirs # copy and paste into the editor and save the file chmod +x prune-dirs ./prune-dirs -v -n
Test
I tested prune-dirs
in a directory with the following sub-directories, as seen with find
$ find . -type d -printf "%T+ %p\n"|sort
2018-12-01+02:03:04.0000000000 ./follower1234
2018-12-02+03:04:05.0000000000 ./follower3456
2018-12-03+04:05:06.0000000000 ./follower4567
2018-12-04+05:06:07.0000000000 ./leader8765
2018-12-05+06:07:08.0000000000 ./bystander6789
2018-12-06+07:08:09.0000000000 ./follower with spaces old
2019-01-09+10:11:12.0000000000 ./follower7890
2019-01-10+11:12:13.0000000000 ./follower8901
2019-01-10+13:15:34.4094596830 ./leader1
2019-01-10+13:15:36.8336011960 ./leader2
2019-01-10+14:08:36.2606738580 ./2
2019-01-10+14:08:36.2606738580 ./2/follower with spaces
2019-01-10+17:33:01.7615641290 ./follower with spaces new
2019-01-10+19:47:19.6519169270 .
Usage
$ ./prune-dirs
Remove directories found via the pattern (older than 'datint')
Usage: ./prune-dirs [options] <pattern>
Examples: ./prune-dirs 'follower*'
./prune-dirs -v -n 'follower*' # 'verbose' and 'dry run'
The 'single quotes' around the pattern are important to avoid that the shell expands
the wild card (for example the star, '*') before it reaches the shellscript
Run with -v -n
(a verbose dry run)
$ ./prune-dirs -v -n 'follower*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"./follower1234"
"./follower3456"
"./follower4567"
"./follower with spaces old"
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./2/follower with spaces"
-----------------
rm -r "./follower1234" "./follower3456" "./follower4567" "./follower with spaces old"
A verbose dry run with a more general pattern
$ LANG=C ./prune-dirs -v -n '*er*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"./follower1234"
"./follower3456"
"./follower4567"
"./leader8765"
"./bystander6789"
"./follower with spaces old"
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./leader1"
"./leader2"
"./2/follower with spaces"
-----------------
rm -r "./follower1234" "./follower3456" "./follower4567" "./leader8765" "./bystander6789" "./follower with spaces old"
Run without any options (a real case removing directories)
$ ./prune-dirs 'follower*'
rm -r "./follower1234" "./follower3456" "./follower4567" "./follower with spaces old"
Run with -v
'trying again'
$ LANG=C ./prune-dirs -v 'follower*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./2/follower with spaces"
-----------------
rm -r
The shellscript lists no directory 'above' "limit-in-seconds", and there are no files listed for the rm -r
command line, so the work was done already (which is the correct result). But if you run the shellscript again several days later, some new directory may be found 'above' "limit-in-seconds" and be removed.
With zsh
:
(){ n=$#; } follower<->(/) # count the number of follower<n> dirs
to_remove=(follower<->(/m+13om)) # assumes the dir list is not changed
# since the previous command
(($#to_remove < n)) || to_remove[1]=() # keep the youngest if they're
# all over 2 weeks old
echo rm -rf $to_remove
(remove echo
when happy)
<->
any sequence of decimal digits (a short form of<1-20>
be without bound).(){code} args
: anonymous function which here stores its number of arguments in$n
.(/omm+13)
: glob qualifier/
: only select files of type directory (equivalent offind
's-type d
)m+13
: files whose age in whole days is strictly greater than 13 days, so files that are 14 days old or older (equivalent offind
's-mtime +13
).om
: order by modification time (likels -t
younger files first)
Note that it's dangerous to rely on directory modification time. directories are modified when files are added, removed or renamed in them (or when they're touch
ed). Since those directories are numbered, you may want to rely on that numbering instead, so replace om
with nOn
(n
umerically O
rder in reverse (capital O
) by n
ame).
To have the pattern in a variable, replace follower<->
with $~pattern
and set pattern='follower<->'
or any other value.
Complementing the rowan's answer. You can change the dot by the path to directories
find . -type d -name follower* -mtime +14 -exec rm -rf {} +;