Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I am trying to modify the following command in a script that removes directories based on age.

Example directories would be the following:

/a/b/c/2011/11/11
/a/b/c/2011/11/11/12-ACD-1234
/a/b/c/2011/11/14

The current inaccurate attempt is the following:

find /a/b -type d -mindepth 2 -depth ! -name '*ACD*' -mtime +180 -exec rm -rf {} +

Although this script will not remove directories containing "ACD", the parent directory will be removed. I have tried -prune combinations and other tricks but am unable to get the desired result.

Is there a way to hone this find command to preserve subdirectories matching certain criteria? In this example, the parent directories /a/b/c/2011 and /a/b/c/2011/11 would have to be preserved as to not recursively remove the subdirectory.

I am looking to do this with just a find command, if at all possible. Thanks.

share|improve this question
add comment

2 Answers 2

up vote 0 down vote accepted

The chief issue is that find gives you a lovely list of directories as output, but you need a way to evaluate each of those on their own to determine whether they have an *ACD* somewhere in a subdirectory. And piped commands don't seem to work well (or at all?) as an argument to find's -exec.

So one approach would be to create your own command script that -exec can call, which will return 0 if its argument contains no subdirectory *ACD*, and 1 otherwise. This seems to work for me:

#!/bin/sh
# script: /path/to/hasnoACD
list=`find $1 -name '*ACD*'`
[ "x$list" = "x" ] && exit 0 || exit 1

Then your desired find command just uses -exec twice:

find /a/b -type d -mindepth 2 -depth ! -name '*ACD*' -mtime +180 -exec /path/to/hasnoACD {} \;  -exec rm -rf {} +

-exec /path/to/hasnoACD {} \; will evaluate as true only for the cases you want.


Another alternative: I initially approached this in a way similar to @icyrock-com, in that I ended up with a find command piped to a while loop, which handles the directory removals. The 'while' syntax will depend on your shell, and you'll need a way to distinguish directories that contain some *ACD* subdirectory as we did above with the hasnoACD script, but in bash on FreeBSD the following seems to work:

find /a/b -type d -mindepth 2 -depth ! -name '*ACD*' -mtime +180 | while read mydir ; do find $mydir -name '*ACD*' | xargs false && rm -rf $mydir ; done

Note the find within the while loop -- only when that find returns nothing (no *ACD* subdirs) will xargs return true, so that the rm -rf $mydir is executed. This behavior of xargs works on my FreeBSD machine, but on Linux or Solaris the behavior is different, so some other test would be needed.

As @icyrock-com suggests, you may wish to do a mv rather than a rm, just to be safe.

share|improve this answer
    
Thanks. The 1st solution you gave works great. The 2nd solution (while loop) does not work. Piping to xargs false always returns false. –  Linux2012 Feb 22 '12 at 14:26
    
Interesting. On my FreeBSD box, piping an empty result to xargs false returns true -- but this doesn't work on CentOS or Solaris. I'll edit my answer to clarify. –  Lars Rohrbach Feb 22 '12 at 22:23
add comment

Not sure you can do this with find only, but here's a test script you can try - try this in /tmp or somewhere safe:

# Prepare
rm -rf a
mkdir -p a/b/c
for a in c d; do
  for i in 2010 2011; do
    for j in 05 11; do
      mkdir -p a/b/$a/$i/$j/will-delete
      if [ $j != 05 ]; then
        mkdir -p a/b/$a/$i/$j/will-ACD-leave
      fi
    done
  done
done
echo Created

# List
echo ----- Listing
find a

# Remove
echo ----- Removing
find a/b -depth -type d ! -name "*ACD*"|while read l; do
  num_entries=`ls -a "$l"|wc -l`
  if [ $num_entries -eq 2 ]; then
    echo Removing $l
    rm -rf "$l"
  fi
done

# List
echo ----- Listing
find a

Note the above removes the -mtime +180 for testing, so you need to adjust as needed. The output would be this:

Created
----- Listing
a
a/b
a/b/c
a/b/c/2010
a/b/c/2010/05
a/b/c/2010/05/will-delete
a/b/c/2010/11
a/b/c/2010/11/will-delete
a/b/c/2010/11/will-ACD-leave
a/b/c/2011
a/b/c/2011/05
a/b/c/2011/05/will-delete
a/b/c/2011/11
a/b/c/2011/11/will-delete
a/b/c/2011/11/will-ACD-leave
a/b/d
a/b/d/2010
a/b/d/2010/05
a/b/d/2010/05/will-delete
a/b/d/2010/11
a/b/d/2010/11/will-delete
a/b/d/2010/11/will-ACD-leave
a/b/d/2011
a/b/d/2011/05
a/b/d/2011/05/will-delete
a/b/d/2011/11
a/b/d/2011/11/will-delete
a/b/d/2011/11/will-ACD-leave
----- Removing
Removing a/b/c/2010/05/will-delete
Removing a/b/c/2010/05
Removing a/b/c/2010/11/will-delete
Removing a/b/c/2011/05/will-delete
Removing a/b/c/2011/05
Removing a/b/c/2011/11/will-delete
Removing a/b/d/2010/05/will-delete
Removing a/b/d/2010/05
Removing a/b/d/2010/11/will-delete
Removing a/b/d/2011/05/will-delete
Removing a/b/d/2011/05
Removing a/b/d/2011/11/will-delete
----- Listing
a
a/b
a/b/c
a/b/c/2010
a/b/c/2010/11
a/b/c/2010/11/will-ACD-leave
a/b/c/2011
a/b/c/2011/11
a/b/c/2011/11/will-ACD-leave
a/b/d
a/b/d/2010
a/b/d/2010/11
a/b/d/2010/11/will-ACD-leave
a/b/d/2011
a/b/d/2011/11
a/b/d/2011/11/will-ACD-leave

The above will delete empty parent folders, so if that's the problem, you may want to modify the while loop to just make a dummy file after each rm and then do another top-level find to delete these (name them in some way for easier finding).

As a separate note - instead of delete, I suggest doing a mv somewhere instead until you are sure this is what you want. Backing up before is another option.

Hope this helps.

share|improve this answer
    
Thanks for your answer, icyrock. This solution would work if the directories are empty. However, if there are files in those directories, then there would be more than two entries for an ls -a. –  Linux2012 Feb 22 '12 at 14:31
add comment

Your Answer

 
discard

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.