Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm searching a list of directories for certain files and I want to delete the directory that the file is in.

This is what I'm trying:

find /converter/storage/unmatched/ -size -4b -name '*.mp3' -execdir mv -b `dirname {}` /converter/storage/smallfiles/ \;
mv: cannot move `.' to `/converter/storage/smallfiles/.'

I'm attempting to move it first just to prove to myself that it can be done without trashing unintended files. How do I move or delete the containing directory?

share|improve this question
up vote 3 down vote accepted

Your command won't work as expected for several reasons:

  1. bash evaluates ​`dirname {}`​ before the find command gets executed.

    This can be solved by invoking bash to evaluate the output of the dirname command

  2. The -execdir changes to the directory of the found file before executing the command.

    Therefore, even with proper syntax, dirname {} will always be ..

  3. Since find is going through all files inside all directories (rather than the directories itself), moving the directory that's currently being processed may cause unexpected behavior.

    I don't know find well enough to go into details, but the following example should illustrate it.

    $ find data -type f -printf "  %p" ; echo
      data/1/b  data/1/a  data/2/b  data/2/a
    $ find data -type f -exec bash -c ' \
    > echo -n "  "mv `dirname {}` backup/
    > ' \; ; echo
      mv data/1 backup/  mv data/1 backup/  mv data/2 backup/  mv data/2 backup/
    $ find data -type f -exec bash -c '
    > echo -n "  "mv `dirname {}` backup/; mv `dirname "{}"` backup/
    > ' \; ; echo
      mv data/1 backup/  mv data/1 backup/
    mv: cannot stat `data/1': No such file or directory

    After moving the directory data/1, find keeps processing its contents. This causes an error message since find executes the mv for every file in data/1, but works well otherwise. When it finishes processing data/1, find gets "lost" somehow.

  4. While find handles all possible characters with special meaning properly if {} occurs as a plain argument to find, the commands in example 3 will fail to execute properly if, e.g., a space occurs in a path. To avoid this, both the argument output of dirname have to be quoted.

    Using mv "`dirname "{}"`" (or mv "$("{}")", which is more legible) will work well with spaces, but it can lead to disastrous consequences in general1.

  5. mv -b cannot backup directories, only files.

As you found out yourself, problem 3 only occurs if you to try to move the directory, but not if you, e.g., remove it. Therefore, an easy and safe solution to problem 3 (that fixes problem 5 in the process) is moving the matching directories into a zip file.2

Problem 4 can be solved by passing {} as an argument to bash and accessing it as $0.

The command

find /converter/storage/unmatched/ -type f -size -4b -name '*.mp3' \ -exec bash -c ' \
  zip -0mqr "$(dirname "$0")"\
' {} \;

will work as expected, although it will still generate error messages when trying to move a directory more than once.

A somehow cleaner approach (i.e., without error messages) would be to use find -depth -type d to go through the directories themselves.3

For each directory, we can use

find "$0" -maxdepth 1 -type f -size -4b -name '*.mp3' -printf 1 -quit

to print 1 if and only if it should get removed4, and test ([) to check if find printed 1.

The command

find /converter/storage/unmatched/ -depth -type d -exec bash -c ' \
  [ $(find "$0" -maxdepth 1 -type f -size -4b -name '*.mp3' -printf 1 -quit) ] \
  && echo zip -0mqr "$0"\
  ' {} \;

will have to desired effect.

1 If the name of a directory inside unmatched consists, e.g., of a single newline character, "$(dirname "{}")" will evaluate to unmatched and $(dirname {}) will evaluate to . (i.e., the folder your executing the command from)!

2 Even without compressing the files, this will take considerably longer that just (re)moving them if the files are big. Since your destination folder is called smallfiles, I suspect this won't be an issue.

3 The -depth` switch makes find process each directory's contents before processing the directory itself. This avoids the error messages we got with the first approach.

4 The -quit switch makes find stop after finding the first match. It's not strictly necessary, but it might speeds things up a bit.

share|improve this answer
Running with echo it outputs the correct instructions, but running it with mv only moves a single directory then complains No such file or directory. I'm fairly sure this has something to do with my (mis)understanding of find. Are you able to point me in the right direction here? – Josh Smeaton Mar 7 '13 at 2:49
The mv commands can cause a lot of error messages, since find may try to remove a directory once for every matching file in it (or in its subdirectories). This is certainly sub-optimal, but – unless I'm missing something – it should move all matching directories. – Dennis Mar 7 '13 at 3:07
It didn't - only a single directory. Changing to an rm did the trick without errors, which is what I was aiming for anyways. Thanks for the hand. I really need to figure out order of substitution/evaluation. – Josh Smeaton Mar 7 '13 at 3:11
I have an hypothesis (that I'll verify tomorrow): After processing the first matching directory, find – unaware that the directory has been moved – advances to its parent folder (smallfiles). Without any more files to process, it exits immediately. – Dennis Mar 7 '13 at 4:00
no such file or directory occurs for every successive file that matched though. For 100 folders, it moves one, then errors 99 times. – Josh Smeaton Mar 7 '13 at 4:05

You must log in to answer this question.

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