linux - How can I use the find command to remove directories without eliminating certain subdirectories?

07
2014-07
  • Linux2012

    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.

  • Answers
  • Lars Rohrbach

    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.

  • icyrock.com

    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.


  • Related Question

    linux - Unix - List all directories and subdirectories, excluding directories without files
  • ftiaronsem

    I would like to list all the directories and sub directories in and below the current path. Since I only wanted to display directories I came up with the follwing command:

    find -type d -exec ls -d1 {} \; | cut -c 3-
    

    This prints out for example

    webphone
    music
    finance
    finance/banking
    finance/realestate
    finance/trading
    finance/other
    finance/moneylending
    finance/insurance
    webradio
    webtv
    

    The problem I have right now is, that the directory finance is listed. finance contains no files yust the sub directories you see above. What I want to achieve is the following output:

    webphone
    music
    finance/banking
    finance/realestate
    finance/trading
    finance/other
    finance/moneylending
    finance/insurance
    webradio
    webtv
    

    In this list the directory finance is not listed. Therefore I need your adive of how to filter directories which contain no files (only subdirectories).

    Thanks in advance

    ftiaronsem


  • Related Answers
  • Gilles

    Here's one way: list all regular files, strip away the file basenames, and remove duplicates.

    find . -type f | sed 's!/[^/]*$!!' | sort -u
    

    If you want to strip the leading ./:

    find . -type f | sed -e 's!/[^/]*$!!' -e 's!^\./!!' | sort -u
    
  • udo

    I consider installing tree:

    • sudo apt-get install tree

    and then run

    • tree -d /path/to/start/dir

    to display directories only.

    Example:

    root@X100e:~# tree -d /var/cache/
    /var/cache/
    ├── apache2
    │   └── mod_disk_cache
    ├── apt
    │   └── archives
    │       └── partial
    ├── binfmts
    ├── cups
    │   └── rss
    ├── debconf
    ├── dictionaries-common
    ├── flashplugin-installer
    ...