0

Considering the answers for this question about deleting folders by name and this question about stopping searches at the first positive in a path tree, how can I delete all contents of a directory, including sub-directories and files, while leaving the directory intact and stopping at the first match in the path tree?

In other words I might have:

./path/to/foo/again/foo

With the ./path/to/foo folder containing a bar.txt file, and I would be looking to obtain:

./path/to/foo

Containing no files or directories.

0

1 Answer 1

1

Note the second link ("about stopping searches at the first positive in a path tree") is not really relevant because a solution that ignores it may empty ./path/to/foo/again/foo and then empty ./path/to/foo; or it may empty ./path/to/foo first and not get to ./path/to/foo/again/foo at all (because this path does not exist now). In any case the goal will be reached.

Anything under (some) foo matches -path '*/foo/*', but the ./path/to/foo itself does not match. find does not add trailing slashes to paths it creates and examines. An exception would be something like find /path/to/foo/ … where /path/to/foo/ will be processed with a trailing slash and it will match -path '*/foo/*'. You may consider -path '*/foo/?*', but if you start from /path/to/foo// (which is a valid pathname) then it will match.

So maybe -path and -name?

find . -path '*/foo/*' ! -name foo

Will it give you a good list of pathnames to delete? Note ./path/to/foo/again/foo will not be on it. The list will contain ./path/to/foo/again, so if you delete again properly then ./path/to/foo/again/foo will be deleted anyway. Not bad, until you realize there will be no entry on the list that allows you to delete ./path/to/foo/foo/foo (if such path exists).

Another problem with -path '*/foo/*' is when you supply a starting point in a form of /a/foo/starting/point. In such case every path considered by find will match. In effect you may inadvertently remove the entire point, while your intention was to empty /a/foo/starting/point/whatever/foo/.

If you can cd to the starting point then this should be safe:

cd /whatever/the/starting/point
# check if cd succeeded (the check can be automated with cd … && find … )
find . -path '*/foo/?*' -delete

(? is most likely not needed because the specification explicitly states there should be no trailing slash in the portion of the pathname relative to what we supplied as the starting path; but I don't know if all implementations of find in a wild strictly obey; so ? is just in case).

Even if foo appears in the path to the starting/point, it wont matter. In find what -path tests is the pathname build by the tool, not the actual absolute pathname. When find starts from . then all pathnames it builds will start with . and -path will test them as strings.

Note -delete is not portable. A portable solution may be one of these:

find . -depth -path '*/foo/?*' -exec rm -r {}  +
find .        -path '*/foo/?*' -exec rm -r {} \;

The first one works like -delete (which implies -depth): it processes a directory only after all its content has been processed. rm will get pathnames one by one in the right order and it will not really need to work recursively because any directory it tries to remove should already be empty (still -r is needed so it can remove directories). Multiple pathnames will be supplied to a single rm thanks to +, still there will probably be more than one rm because you cannot run rm with arbitrarily many arguments. find -exec … + knows this and can execute as many commands (rm processes in our case) as it needs to process arbitrarily many entries and not hit the limit (for comparison: xargs also knows this).

In many cases the most optimal portable solution would be to run one or few rm -r processes that remove non-directories and directories directly under ./path/to/foo/, so find does not even need to go deeper. This is what the second command does. Note there is ; (escaped from the shell) instead of +. We don't want + because it might happen find decides the content of ./path/to/foo/ is not enough to run rm with many arguments yet, it goes deeper to add more arguments, finds the content of ./path/to/foo/again/foo/, only then it starts rm. So rm -r gets ./path/to/foo/again and (possibly among others) ./path/to/foo/again/foo/whatever, it removes the former (recursively), then it complains the latter does not exist. Therefore when without -depth, it's better to run one rm per pathname, so -exec … \;.

But even then files that are no longer there will probably trigger warnings; not from rm but from find. It's because find learns about the content of a directory before processing the directory. It does this even if it's going to process the directory first (i.e. even if there is no -depth). After rm removes ./path/to/foo/again find will still try to process ./path/to/foo/again/whatever. Any solution with (explicit or implied) -depth is free from this problem.

Use the first portable form, the one with -depth; or better the form with -delete, if only your find supports -delete.


Side note: if you want to empty a specific directory then cd to it and:

# check if in the right directory
find . ! -name . -delete

With common implementations of find you don't even need ! -name . because they don't delete . and it's by design. The command simplifies to:

find . -delete
1
  • I feel like the answer to this question is: do I REALLY need to preserve the foo folders or can I just remove them with everything else as the first link allows me to do? And I guess the answer is: nope. A few warnings for non-existing folders also seem perfectly acceptable when considering all possible ramifications...
    – maja
    May 27, 2021 at 14:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .