Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

I am trying to write a script that will search a directory for a folder, and move the folders contents one directory up from their location. this is what i have so far:

find ./localFolders -name 'file' -type d -exec mv {}/* .. \;

I think my problem lies in the destination directory of ... perhaps I am expressing this wrong?

Any help is useful.

*edit**

to simplify this ill go into more info

I am with an organization that it attempting to move from MS Exchange to Zimbra. As part of our migration we have found great utilities that transfer mailboxes but transferring PST files to Zimbra usable format is proving challenging.

I have found some solutions though to make a long story short, I have been able to spin out pst files to a usable format with one exception –

instead of putting the mail in that folder a sub directory is created called “mbox” presumably resembling the mbox file that the .eml files are being broken out from. Because each user is different and uses different folder structures from other users, each directory is unknown until the script runs. Additionally the contents of the mbox folder are also unique.

Because the folder named mbox is the only constant in these directories I am trying to use find to locate those folders take that folders contents and move them up one directory, then delete the folder named mbox. Here is the actual command as it appears in my script:

find localFolders -name mbox -type d -exec mv {}/* .. \;

or in the example you just gave:

find localFolders -name mbox -type d -execdir sh -c 'echo mv {}/* ..' \;

I am trying to use the * as a wildcard to select everything in the mbox folder . As best as I can tell it is instead being treated as part of the directory, and not the wildcard as intended

thoughts?

share|improve this question
    
And have you seen my answer? –  gniourf_gniourf Jun 29 '13 at 8:08
add comment

2 Answers

up vote 1 down vote accepted

How about this?

find ./localFolders -name 'file' -type d -execdir bash -c 'mv -vnt . -- "$0"/*' {} \; -prune

Safe regarding spaces and funny symbols in filenames. Enjoy!

share|improve this answer
    
cool this seems to have worked! curious, what was the point behind using prune? played around and experimented with it, i didn't see any change when removing that argument. –  Don Jul 1 '13 at 19:51
    
-prune might not be very useful in your case, but it prevents find to go inside the directory file and try to further find other directories named file. It's a good optimization in case you have a big dir tree inside file. (I hope this is clear enough). –  gniourf_gniourf Jul 1 '13 at 20:07
add comment

In order to do what you want correctly using find you have to understand the following concepts:

  1. The working directory of a process.
  2. The glob feature of bash (or other shells).

Every process in linux has a working directory as part of its internal state. You can imagine it as the "location" of the process in the filesystem. The shell is also a process and also has a working directory. The working directory of the shell is the directory (usually) displayed in your prompt.

When a process is created it will inherit the working directory from its parent. If you start a process from the shell then the shell is the parent. Every process can change its own working directory at will. You can change the working directory of the shell using the builtin command cd.

The working directory is used when dealing with relative filenames. Relative filenames are filenames that do not start with /. For example when you do cat foo it will look for the file foo in the current working directory, but when you do cat /tmp/foo it will look for the file foo in /tmp.

When dealing with find -exec you always have to think about the working directory. The working directory of -exec is the working directory of find. The working directory of find is the working directory of the shell.

In your case:

find ./localFolders -name 'file' -type d -exec mv {}/* .. \;

The .. will reference to the parent directory of the working directory of mv which is the working directory of -exec which is the the working directory of find which is the working directory of the shell by the time you executed the command. That is not what you wanted.

There is an -execdir which is like -exec but it first switches the working directory to the directory where the matching item is found. -execdir is a huge step towards the solution to your question, but there is another problem in your find command: the * a.k.a. glob.

A glob is something like a wildcard. For example foo* will match foobar and foobaz (and foo because * also matches the null string). So far so good. The interesting part of globbing is that it is done by the shell, not by the executed command. When you execute rm foo* then the shell will first expand the * to foobar and foobaz and then execute rm foobar foobaz. rm never sees the *.

What happens when there are no files starting with foo in the current directory? Then the shell (usually) will not expand the * and will pass the * to the command in verbatim. That means rm foo* will be executed as rm foo*. rm will look for a file named foo* and it will (most likely) not find one and abort with an error. In case you are wondering: yes it is possible for a filename to contain *, no don't try that at home.

In your case:

find ./localFolders -name 'file' -type d -exec mv {}/* .. \;

Before executing the find command the shell will first try to expand the glob {}/*. There are no files matching the pattern {}/* so the glob will be passed verbatim.

Let's see what the find command does. Suppose you are executing the find command from /home/username/somedir and there exists a directory /home/username/somedir/localFolders/foodir/file with some files in it. find will start searching in /home/username/somedir/localFolders because it is told so in the parameters but the working directory of find is /home/username/somedir because it was executed from there.

Now find found the directory /home/username/somedir/localFolders/foodir/file. Before it executes the -exec it will replace the {} with the found item. That means mv {}/* .. will become mv /home/username/somedir/localFolders/foodir/file/* ... mv will fail because it will (most likely) not find a file named *.

Suppose there is a file named * then mv will move the file to ... Remember that the working directory is /home/username/somedir. That means mv will move the file to /home/username/somedir/.. which is /home/username. That is not what you wanted.

The "fix" for your find command depends on several factors. For example:

  • Are there "dotfiles" in the file directory? Dotfiles are files with names starting with a dot. The * glob will (usually) not expand to filenames starting with a dot.
  • Are there other directories in the file directory? find will try to recurse in to them but fail because they have been moved.
  • Are there directories named file in the file directory? Where should those files go?
  • Are there a gazillion files in the file directory? That might mess up the * glob expansion.
  • Are there filename collisions with files out of the file directory? Should they be overwritten?
  • Are there spaces in the filenames? That will mess everything up.

To fix your find command I will happily ignore all those factors.

Here is a fix using -execdir and sh -c to delay the expansion of *:

find ./localFolders -name file -type d -execdir sh -c 'mv {}/* ..' \;

Since the * is in quotes it will not get expanded ahead of time. When -execdir executes the command then sh -c will expand the * in the working directory.

Here is another fix without sh -c:

find ./localFolders -path "*/file/*" -execdir mv {} .. \;

I used -path to find all items under directories with the name file. The * are in quotes so they will not get expanded ahead of time. This time find takes care of the interpretation of the *. Note that this will also match something like ./localFolders/foodir/file/bardir/file/somefile. As said I happily ignored that factor.

And for the sake of completeness another fix without -execdir:

find ./localFolders -name file -type d -exec sh -c 'mv {}/* {}/..' \;
share|improve this answer
    
Thank you for your response. Both of these commands failed -execdir failed with mv: cannot stat `./file/*' : No such file or directory The full command that I used was: find ./localFolders -name 'file' -type d -execdir mv {}/* . \; –  Don Jun 28 '13 at 14:45
    
see updated answer –  lesmana Jun 28 '13 at 19:53
    
please see the edits to my original post –  Don Jun 29 '13 at 7:30
    
@Don, I made a mistake earlier. I forgot to remove the echo in the sh -c commands. I now removed the echo. Please try again without the echo. –  lesmana Jun 29 '13 at 21:31
    
after reattempting this, the issue remains unaffected. the terminal apears to complete, but the mbox folders remain with all of the email still inside. FYI right now this has become a point of curiosity, as I have been able to solve this issue with a php script. please post if anything is found that might work. I would rather keep a uniform CLI solution w/out using different programming languages, but im finding this issue to be a stubborn one –  Don Jul 1 '13 at 17:50
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.