100

I am searching for files which name which contain AAA within their path using following command:

find path_A -name "*AAA*"

Given the output showed by the above command, I want to move those files into another path, say path_B. Instead of moving those file one by one, can I optimize the command by moving those files right after the find command?

6 Answers 6

156

With GNU mv:

find path_A -name '*AAA*' -exec mv -t path_B {} +

That will use find's -exec option which replaces the {} with each find result in turn and runs the command you give it. As explained in man find:

   -exec command ;
          Execute  command;  true  if 0 status is returned.  All following
          arguments to find are taken to be arguments to the command until
          an  argument  consisting of `;' is encountered.  

In this case, we are using the + version of -exec so that we run as few mv operations as possible:

   -exec command {} +
          This  variant  of the -exec action runs the specified command on
          the selected files, but the command line is built  by  appending
          each  selected file name at the end; the total number of invoca‐
          tions of the command will  be  much  less  than  the  number  of
          matched  files.   The command line is built in much the same way
          that xargs builds its command lines.  Only one instance of  `{}'
          is  allowed  within the command.  The command is executed in the
          starting directory.
5
  • 4
    Don't forget the "\;" at the end of the exec! Feb 10, 2018 at 18:08
  • 4
    @Charles Roth: it's + which dows the job, you can read my quote above or man find instead
    – cuonglm
    Feb 11, 2018 at 4:32
  • Thanks for this! I was trying to use -exec mv {} path_b + and it was failing with permissions errors. TBH, I still don't understand why, but -exec mv -t path_b {} + works a treat! Apr 10, 2018 at 0:55
  • 15
    @JeremyDavis When using -exec ... {} +, the {} has to be the last thing before the +. This is why he uses mv -t destdir {} + and not mv {} destdir +. One cold have used -exec mv {} destdir ';' instead, but that would have executed mv once for each file.
    – Kusalananda
    May 6, 2018 at 12:40
  • A small remark: the same works with GNU cp as well.
    – dezso
    Jun 1, 2021 at 7:39
31

You could do something like below as well.

find path_A -name "*AAA*" -print0 | xargs -0 -I {} mv {} path_B

Where,

  1. -0 If there are blank spaces or characters (including newlines) many commands will not work. This option take cares of file names with blank space.
  2. -I Replace occurrences of replace-str in the initial-arguments with names read from standard input. Also, unquoted blanks do not terminate input items; instead the separator is the newline character.

Testing

I created two directories as sourcedir and destdir. Now, I created bunch of files inside sourcedir as file1.bak, file2.bak and file3 with spaces.bak

Now, I executed the command as,

find . -name "*.bak" -print0 | xargs -0 -I {} mv {} /destdir/

Now, inside the destdir, when I do ls, I could see that the files have moved from sourcedir to destdir.

References

http://www.cyberciti.biz/faq/linux-unix-bsd-xargs-construct-argument-lists-utility/

1
  • 2
    Works nicely on macOS (where the -t arg is not available on mv Jan 2, 2021 at 16:59
29

For the benefit of OS X users coming across this question, the syntax in OS X is slightly different. Assuming you do not want to search recursively in subdirectories of path_A:

find path_A -maxdepth 1 -name "*AAA*" -exec mv {} path_B \;

If you want to search all files recursively in path_A:

find path_A -name "*AAA*" -exec mv {} path_B \;
2
  • 2
    This is not a OS X specific syntax, it's the same with any find I've used. Good points: -maxdepth (espeically if path_B is a subdirectory - avoids mv trying to move files already there!) and using \; (so {} doesn't have to be the last parameter and normal mv syntax can be used)
    – drevicko
    Aug 26, 2019 at 1:32
  • I think @mannykary might have been referring to macOS's mv, which doesn't have -t. To work around this, you could install coreutils with Homebrew and then use gmv -t, or there are other solutions using e.g. xargs.
    – chrisk
    Jan 4, 2021 at 20:01
7

The -exec is the best way to do this. If, for whatever reason, this is not an option, you can also read the results in a loop:

find path_A -name "*AAA*" -print0 | 
    while IFS= read -r -d $'\0' file; do mv "$file" path_B; done

That's the safe way, it can deal with file names that contain spaces, newlines or other strange characters. A simpler way, but one that fails unless your file names consist only of simple alphanumeric characters, is

mv $(find path_A -name "*AAA*") path_B

But use the while loop.

2
  • The simpler way can still fail if your file names consist only of simple alphanumeric characters, e.g. if reaching ARG_MAX
    – user60101
    Sep 10, 2014 at 21:30
  • It works BUT it deletes the original folder by moving all the files it contains, even if the extension is not the one you are looking for...
    – Chrstpsln
    May 19, 2020 at 6:41
7

Using only POSIX features of find (and also of mv):

find path_A -name '*AAA*' -exec sh -c 'mv "$@" path_B' find-sh {} +

Further reading:

2

Another way

for f in `find path_A -name "*AAA*"`; do mv $f /destination/dir/; done
1

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