3

I need to use find and mv together

requirement : Need to find all file *.xml but exclude deposit.xml and move it to another folder ( fairly simple)

My attempt :

mv `find ./*.xml '!' -type d | fgrep -v deposit.xml` ./Archive

This works perfectly fine if there are any files, If no files are present it throws below error

mv: missing destination file operand after `./Archive'
Try `mv --help' for more information.*
Peck
  • 125

4 Answers4

3

It's generally a bad idea to parse the output of ls or find, because you can't distinguish which characters are part of a file name and which characters are separators. If you don't control the file names, this can be a security hole, allowing an adversary to craft file names that cause your script to execute arbitrary code. See Why does my shell script choke on whitespace or other special characters?. It may be ok in your case though, especially as your files seem to be some kind of source code tree where it's common to have file names with no “exotic” characters, in particular no spaces.

Still, it isn't really more difficult to use a robust command. The core idea is to make find invoke mv instead of parsing the output of find. The following command moves .xml from the current directory and subdirectories recursively:

find . ! -type d -name '*.xml' ! -name deposit.xml -exec mv {} ./Archive \;

To speed this up a little, you can group invocations of mv by running an intermediate shell.

find . ! -type d -name '*.xml' ! -name deposit.xml -exec sh -c 'mv "$@" "$0"' ./Archive {} +

If you meant to move only files from the current directory, without traversing subdirectories, then you need to instruct find not to recurse. If your find implementation supports the -maxdepth option:

find . -maxdepth 1 ! -type d -name '*.xml' ! -name deposit.xml -exec sh -c 'mv "$@" "$0"' ./Archive {} +

Otherwise:

find . -name . -o -type d -prune -o -name '*.xml' ! -name deposit.xml -exec sh -c 'mv "$@" "$0"' ./Archive {} +

If your shell is ksh, you can use its extended globs. This is easier than using find, but less portable. To move files in the current directory:

mv !(deposit).xml Archive/

In ksh93, to move files from subdirectories as well:

set -o globstar
mv **/!(deposit).xml Archive/

In bash, you can use the !(…) pattern as well, but you need to run shopt -s extglob first. To use the **/ pattern, you need to run shopt -s globstar first, and beware that up to bash 4.2, **/ traverses symbolic links to directories in addition to directories.

shopt -s extglob globstar
mv **/!(deposit).xml Archive/

In zsh, you can use the !(…) pattern after running setopt ksh_glob. Alternatively, you can use zsh's syntax for that after running setopt extended_glob. The recursive traversal syntax is available either way.

setopt extglob
mv **/(^deposit).xml Archive/
1

Using "find" command:

find ./*.xml -type f ! -name "deposit.xml" -exec mv {} ./Archive ';'

Using "find" with "xargs" command:

find ./*.xml -type f ! -name "deposit.xml" | xargs -I {} mv {} ./Archive

Using "for" loop with "find" command.

for f in $(find ./*.xml -type f ! -name "deposit.xml"); do mv ${f} ./Archive; done

Also, according to the manpage for GNU/Linux systems, the "mv" command has the "-t | --target-directory" option. So you should be able to use it with "xargs" like this:

find ./*.xml -type f ! -name "deposit.xml" | xargs mv -t ./Archive

I have not tested that though as I am on a FreeBSD system.

  • I interpreted your original question as wanting to move all files except "deposit.xml". If you only want to move "deposit.xml" then simply remove the "!" in "! -name" changing it to "-name". Or exclude "-name" completely and do "find ./deposit.xml". – AntumDeluge Mar 16 '15 at 20:24
  • I have multiple files that i needed to neglect hence used this command find ./*.xml -type f ! -name "deposit.xml" ! -name "file2.xml" -exec mv {} ./Archive ';' it worked well – Peck Mar 17 '15 at 09:56
0

And the problem is that you don't want to display the error message or that you don't want the command being run if there are no .xml?

In the first case redirect output 2>/dev/null and no error message will be displayed.

In the second case run

for i in $(find ./*.xml '!' -type d | fgrep -v deposit.xml);do mv $i ./Archive;done

YoMismo
  • 4,015
0
find . ! -name "deposit.xml" -name "*.xml" -exec mv {} ./temp \;

find has -exec option using which we can execute commands with the result file as operand and use '! -name' option to exclude file.

Renjith
  • 241
  • This moves abcdeposit.xmlbla/ghi.xml and abc/def/ghi.xml which the original of the OP doesn't move. And it is inefficient by invoking mv for every file. – Anthon Mar 16 '15 at 17:09
  • @anthon good work!! my answer is good enough input for forming exact command, looks like you have taken input from my answer, btw hows it possible without mv for each file, even in your answer mv will be executed for each file. what is the use of + at the end of your answer, when I execute your answer it gives error invalid argument to -exec, unfortunately I don't have points to down vote your answer. – Renjith Mar 16 '15 at 18:35
  • I am not sure what you are referring to, but if you think it was me who downvoted you, I did not. The use of the + is provide as many arguments as found and can fit on the commandline, just like ; does but that provides only one argument at a time. Use man find and lookup -exec command {} + for details. – Anthon Mar 16 '15 at 19:45
  • I am sorry, with your comment and down vote I mistook. – Renjith Mar 17 '15 at 03:42
  • I didn't know about {} +, thank you for explanation. If my understanding is correct, its not good idea to pass all source argument to mv in one shot. The number of find out put may be so large and big in length, that it cannot handle in a single command. Assuming the command move is implemented in C/c++, the arguments are collected in with argv[] (int main(argc,argv[])), there are limit for argv. (ARG_MAX in limits.h ). – Renjith Mar 17 '15 at 04:01
  • find (and commands like xargs and GNU paralllel) takes into account what is the maximum length allowed and invoke a command multiple times if necessary. See this post for some details. – Anthon Mar 18 '15 at 13:56
  • yes that's right , in fact {} + didn't work on my Linux distro, which has find command version 4.1.20. It was giving error missing argument to exec. That's why I suspected +, later I tried in some other version of Linux and saw it is working, may be this feature is implemented in the later versions of find command. – Renjith Mar 19 '15 at 07:31