Assuming you want the shallowest directories that don't contain (at any level) non-directory files not older than 180 days (if both ./a/b
and ./a
don't contain recent files, only report ./a
as ./a/b
is part of that), that would be a variation on Listing only shallowest directories containing no files, all the way down, so you could use the same approach as my answer there if on a GNU system:
find . -type d -print0 -o -mtime -180 -printf 'f/%h\0' |
LC_ALL=C sort -zru |
LC_ALL=C awk -F/ -v RS='\0' '
function parent(path) {
sub("/[^/]*$", "", path)
return path
}
$1 == "f" {
sep = path = ""
for (i = 2; i <= NF; i++) {
black[path = path sep $i]
sep = FS
}
next
}
! ($0 in black) && ($0 == "." || parent($0) in black)'
Where instead of painting black the directories that have any non-directory file, we paint black the ones that have any non-directory file not older than 180 days.
Or a variation which removes directories from an array when a recent file is found:
find . -type d -printf '%p/\0' -o -mtime -180 -printf '%h/f\0' |
LC_ALL=C sort -zu |
LC_ALL=C awk -F/ -v RS='\0' '
function parent(path) {
sub("[^/]+/?$", "", path)
return path
}
/\/$/ {dir[$0]; next}
{
path = ""
for (i = 1; i <= NF; i++)
delete dir[path = path $i FS]
}
END {
for (path in dir) if (! (parent(path) in dir)) print path
}'
If you want both ./a
and ./a/b
above (not only the shallowest), it becomes simpler:
find . -type d -printf '%p/\0' -o -mtime -180 -printf '%h/f\0' |
LC_ALL=C sort -zu |
LC_ALL=C awk -F/ -v RS='\0' '
/\/$/ {dir[$0]; next}
{
path = ""
for (i = 1; i <= NF; i++)
delete dir[path = path $i FS]
}
END {
for (path in dir) print path
}'
If you want to delete them, add a -v ORS='\0'
to awk
to print them NUL-delimited, and pipe to xargs -r0 rm -rf
, but beware that if ./
is returned (if there's no recent file anywhere in the current directory), most rm
implementations will refuse to do anything¹.
Since those approaches run find
and traverse the whole directory tree only once, they're more efficient than more naive approaches that crawl the contents of each directory for recent files.
One such less efficient approach in zsh
:
print -rC1 -- **/*(ND/^e['()(($#)) $REPLY/**/*(NDm-180Y1^/)'])
(won't include .
, though you could add it to the list with {.,**/*}(...)
)
If it's only the subdirectories of the current directory that you want to consider, then that approach becomes more efficient (as the Y1
above stops at the first match) as well as being shorter/simpler:
print -rC1 -- *(ND/^e['()(($#)) $REPLY/**/*(NDm-180Y1^/)'])
¹ as a work around for the fact that the expansion of .*
in some shells includes .
and ..
and could be catastrophic with rm -rf .*
. The rm
builtin of zsh
(a shell that doesn't have that misfeature), which you enable with zmodload zsh/files
, will accept rm -rf ./
and empty the current working directory.
mtime
doesn't mean indicate the age of the files. It indicates the modification time. If that is actually what yo want, then you need-mtime +180
.mtime -180
means modified within the last 180 days. I would also recommend against using*
because you could accidentally return data that you don't want. To make sure, simply use the parent directory as the argument.