linux - How can I use the find command to remove directories without eliminating certain subdirectories?
2014-07
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.
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.
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.
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
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
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
...