[Gllug] quick question - exclusive rm -r

Philip Hands phil at hands.com
Mon Nov 27 11:31:37 UTC 2006


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Luis M. Cruz wrote:
> On 11/27/06, salsaman <salsaman at xs4all.nl> wrote:
>> Is it possible to say "delete all subdirectories of ., _except_ those
>> containing the file foo" in one line of shell script ? If so, how ?
> 
>  Hi,
> 
>  something like this should work:
> 
> for A in `find -type d`; do echo `find -type f -name foo` | grep -q $A
> || rm -fr $A ; done

The "echo `command` |" construct is an odd way of doing something similar
to: "command |" with the added bonus that it'll break if the output of the
command is too large.

Also, this is liable to generate lot's of whining about the fact that
directories found by the first find are liable to have been deleted by an
early rm -rf, and so won't be there when you try to run the later find
- -name bit as you descend the hierarchy.

To deal with that, you can add the -depth option to the first find, so that
it lists the directories from the deepest ones first, and so doesn't try to
delete the top ones before checking the lower ones.

Oh, and you can avoid repeatedly hunting the whole hierarchy for foos too.

So, this is perhaps an improvement:

  for A in $(find . -depth -type d) ; do [ -z "$(find $A -type f -name
foo)" ] && rm -fr $A ; done

Also, I've switched to using $( ) because it's generally considered more
readable (and you can nest them without going insane, which is nice).

If you're doing this somewhere where the first find is liable to produce a
lot of output, or on an old system that has depressingly short command line
limits, you can instead go for a loop like:

  find . -depth -type d | while read A ; do ... ; done

since that doesn't expand the find into a long command line, but then you
have the problem of some scroat creating files with carriage-returns in
their names, so it's better still to go for something like:

  find . -depth -type d -print0 | xargs -0 -I{} \
    sh -c '[ -z "$(find "{}" -type f -name foo)" ] && rm -fr "{}"'

N.B. the quotes round the {}'s make sure that naughty filenames (with
<CR>'s ";"'s etc. in them) don't result in you treating them as separate
commands or directories.  The quotes around the $() are not needed in bash,
but they do no harm, and IIRC there is some shell that is willing to break
up the results of $() into separate args, so it might be a good idea anyway.

>  Anyway, use it with care. Make a copy of your files before trying it.

If you run it first with an echo in front of the rm -rf bit, it should just
list the damage it's about to inflict, which will give you a hint about
whether it's a good idea to remove the echo and do it for real.

Cheers, Phil.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFasyWYgOKS92bmRARArotAKCTTNEL3FpFzNTB06I+w17cXJmNyQCfS9EC
JVQ/7MHxzCS2BprKt9Tpzp4=
=SiAe
-----END PGP SIGNATURE-----
-- 
Gllug mailing list  -  Gllug at gllug.org.uk
http://lists.gllug.org.uk/mailman/listinfo/gllug




More information about the GLLUG mailing list