0
votes

What I want to do is read a list of filenames from an index file and then copy each listed file to another directory, but I'm having a little trouble properly stringing xargs and cp together.

head -n 150 ../indexes/index.txt | xargs cp ~/path/to/destination/

This gives the following error:

cp: target 'foo.txt': Not a directory

where foo.txt is the 150th filename listed in index.txt. Clearly cp should be taking the input from xargs as its first operand and not the second, but how do I get it to do this?

2

2 Answers

2
votes

xargs has a -I parameter, which essentially allows you to specify a substitution pattern and write a substitution pattern in a command (cp, in your case). Then, when the command is executed, all instances of that substitution pattern are replaced with the next line of input. So, your command string should look something like this:

head -n 150 ../indexes/index.txt | xargs -I {} cp {} ~/path/to/destination/

Without the -I, cp just sees the second line of input as the destination, which is not what's intended.

Update: So, a breakdown of what's happening: As each line of input is read from head, since you've set up a pipe, the output of head goes to the pipe. When xargs gets a new line from the pipe, because you've specified ... -I {} cp {} ..., instead of trying to create one large argument string composed of each line of input and trying to pass that to cp, it executes cp using each line of input as the source operand.

To explain a little further, consider this scenario. We have a file called test.txt that contains the following:

some_file.txt
another_file.txt
a_third_file.txt

Now, suppose we want to write a command to copy all of those files to a given destination. If we simply write...

cat test.txt | xargs cp /path/to/destination

...then what cp effectively sees is this:

cp some_file.txt another_file.txt a_third_file.txt

In this form, it tries to treat a_third_file.txt as a directory and copy the previous two files to it (which is why in your case cp says the 150th path is not a directory). xargs -I {} fixes that:

cat test.txt | xargs -I {} cp {} ~/path/to/destination

Now, instead of cp only being executed once, cp gets executed three times:

cp some_file.txt ~/path/to/destination
cp another_file.txt ~/path/to/destination
cp a_third_file.txt ~/path/to/destination
1
votes

If using the GNU version of cp, it has a -t directory option to specify the destination to copy all the filenames given to, instead of having that be the last argument:

head -n 150 ../indexes/index.txt | xargs cp -t ~/path/to/destination/