I have an automated system for creating FTP accounts (vsftpd with jailed virtual users) that works well, but I still rely on manual cleanup of stale accounts.

My definition of a stale account is one in which no files or folders exist that are less than 30 days old.

So far, I can run this:

find /home/vsftpd -ctime +30 |awk -F'/' '{print $4}' |sort -u 

Which will give me a list of folder containing at least one file older than 30 days. I could also run with -ctime -30 and, with some heavy looping, negate that list to get me the information I need.

I'm hoping there's a way to do this as a (moderately sensible) one-liner, but my find-foo fails me at this point. Can anyone help?

2 Answers 2


I'd do (with GNU find) something like:

cd /home/vsftpd &&
for i in */; do
  if ! find "./$i" -ctime -30 -print -quit | grep -q .
    echo no file touched in the past 30 days in "$i"

(assuming the accounts are one level down /home/vsftpd)

With zsh:

stale() (: $REPLY/**/*(NDm-30e:'exit 1':))
cd /home/vsftpd &&
  echo those are stale: *(/+stale)
  • I like this. It gets around having to cross-reference two lists and also having to for through lists of directory strings. It's clear now I went wrong by trying to generate the list first, rather than simply iterating through the top-level folders. Thanks! Commented Sep 18, 2012 at 16:49

If you want a one-liner for that, use zsh. It's a bit cryptic, like most advanced uses of glob qualifiers.

echo *(/e\''set -- $REPLY/**/*(Nm-30[1]); ((!$#))'\')

The outer glob matches only directories (/) such that the expression (e) inside the singlequotes is true. The expression uses the matching directory ($REPLY) and looks under it (**/) for files modified in the last 30 days (m-30), without causing an error if no file matches (N), and only retaining the first match ([1]). The expression is true if there is no match.

Another method is to list the directories with recent files, then take the complement of the list.

comm -13 <(find . -mtime -30 | sed -e '/^\.$/ d' -e 's~^\./~~' -e 's~/.*~~' | sort -u) \
         <(find . -type d -name . -o -prune -print | sed -e 's~^\./~~')
  • I'm in awe. I had no idea zsh was capable of such gymnastics, and comm is my new favourite tool. Commented Sep 19, 2012 at 6:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .