Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
up vote 2 down vote accepted

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)
share|improve this answer
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! – SmallClanger Sep 18 '12 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~^\./~~')
share|improve this answer
I'm in awe. I had no idea zsh was capable of such gymnastics, and comm is my new favourite tool. – SmallClanger Sep 19 '12 at 6:31

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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