27

I am trying to pass an array of file paths to xargs to move them all to a new location. My script is currently working as follows:

FILES=( /path/to/files/*identifier* )
if [ -f ${FILES[0]} ]
  then
    mv ${FILES[@]} /path/to/destination
fi

The reason for having FILES as a array was because the if [ -f /path/to/files/*identifier* ] fails if the wildcard search returns multiple files. Only the first file is checked, because the move will be executed if any files exist.

I want to replace mv ${FILES[@]} /path/to/destination with a line that passes ${FILES[@]} to xargs to move each file. I need to use xargs as I expect to have enough files to overload a single mv. Through research I have only been able to find the methods of moving files that I already know which search for the files again.

#Method 1
ls /path/to/files/*identifier* | xargs -i mv '{}' /path/to/destination

#Method 2
find /path/to/files/*identifier* | xargs -i mv '{}' /path/to/destination

Is there a way to can pass all elements in an existing array ${FILES[@]} to xargs?

Below are methods I've tried and their errors.

Attempt 1:

echo ${FILES[@]} | xargs -i mv '{}' /path/to/destination

Error:

mv: cannot stat `/path/to/files/file1.zip /path/to/files/file2.zip /path/to/files/file3.zip /path/to/files/file4.zip': No such file or directory

Attempt 2: I wasn't sure if xargs can be executed directly or not.

xargs -i mv ${FILES[@]} /path/to/destination

Error: No error message was output, but it hung after that line until I stopped it manually.

Edit: Find works

I tried the following and it moved all the files. Is this the best way to do it? And is it moving the files one by one, so the terminal is not overloaded?

find ${FILES[@]} | xargs -i mv '{}' /path/to/destination

Edit 2:

For future reference, I tested the accepted answer method versus the method in my first edit using time(). After running both methods 4 times, my method had an average of 0.659s and the accepted answer was 0.667s. So neither method works any faster than the other.

Matt
  • 1,792
  • 5
  • 21
  • 33
  • Why don't you just do `mv /path/to/files/*identifier* /path/to/destination`? – fedorqui Oct 18 '13 at 15:43
  • The wildcard result will often return thousands of files that `mv` can't handle. As I said in the question, "I need to use `xargs` as I expect to have enough files to overload a single `mv`" – Matt Oct 18 '13 at 15:45
  • 1
    Oh sorry, I missed that part. Then I guess the best is to use `find . -name "..." -exec mv {} \;` having the pattern of the file in the `name`. – fedorqui Oct 18 '13 at 15:47
  • `find -exec` will call mv once for each file. That could be slow. – Nicolai S Jul 07 '15 at 21:17
  • "find -exec will call mv once for each file." -- not if you use the `{} +` option. But anyway, the actual topic of this question was: how to avoid the redundant invocation of `find`, `ls` etc. when you already possess a list of file paths. – Amit Naidu Jun 15 '20 at 15:41

1 Answers1

49

When you do

echo ${FILES[@]} | xargs -I {} mv '{}' /path/to/destination

xargs treats the entire line as a singe argument. You should split each element of the array to a new line, and then xargs should work as expected:

printf "%s\n" "${FILES[@]}" | xargs -I {} mv '{}' /path/to/destination

Or if your filenames can contain newlines, you can do

printf "%s\0" "${FILES[@]}" | xargs -0 -I {} mv '{}' /path/to/destination
GcL
  • 501
  • 6
  • 16
user000001
  • 32,226
  • 12
  • 81
  • 108
  • Why doesn't `printf "%s " "${FILES[@]}" | xargs -i mv '{}' /path/to/destination` work? Space instead of `\n`, `mv` accepts several files for moving to a directory. – Roland Mar 18 '20 at 09:26
  • 2
    @Roland: `mv` does accept multiple arguments, but with your command you pass a *single* argument (including spaces). It looks like `-i` and `-I` cause `-L 1` to be set, so you can only get one item per invocation. There are some workarounds available however, if you look at this question for example: https://superuser.com/a/705651/301351 – user000001 Mar 18 '20 at 13:10