4

I've a problem copying many files in different directories. Let me explain better:

Let's say I have the following in a dir:

$ ls
file1 file2 file3 file4 file5 dir1 dir2 dir3

and I want to copy every file* in every dir* with a single command. I've tried with:

 echo dir{1..3} | xargs cp file{1..5} '{}' \;

My intent was to tell xargs to process every single dir* from echo and copy all the files in the input processed but this and similar didn't work. I would like to avoid the use of a script because it's a task I have to repeat for about 20 directories and the names of the files are slightly different so I'd prefer modifying a command rather than a script.

1
  • 1
    do you want each directory to get a copy of all the files? (that's something for which you could iterate over the directories, doing the cp for each one (for x in dir*; do cp file* $x; done with GNU bash)).
    – njsg
    Jun 7, 2012 at 21:24

4 Answers 4

3

echo dir[1-3] | xargs -r -n1 cp file[1-5]

1
  • Cool answer, have to try it as I don't know the [1-5] syntax...
    – Huygens
    Jun 9, 2012 at 22:49
3

You are making a small mistake in your usage of xargs. Without the -n parameter, xargs append all parameters in input to the command and echo dir{1..3} will not add the necessary carriage return between each entry, so xargs will consider the result as one parameter.

xargs has an option -n which can limit the number of entry, and you need to list the directories with either a \n or \0 as separator:

find . -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 cp file{1..5}

The find command will look for all directories in the current one and use the \0 as separator (which works better if you have directories with spaces) and xargs uses the \0 as separator and launch the command each time with only 1 parameter.

5
  • Thank you! That was exactly what I was looking for: a solution with xargs. Although the syntax is really ugly it's what I wanted Jun 7, 2012 at 23:12
  • @LuigiTiburzi, actually you can keep find for another task, GNU's cp has -t key which stands for --target-directory=…
    – poige
    Jun 8, 2012 at 1:47
  • I just recommend using -r of xargs always (get a habit) since otherwise you can get rather unwanted things when there weren't input lines for xargs. Sometimes it would be just errors due to sanity checks built inside programs you running, but sometimes blunders.
    – poige
    Jun 8, 2012 at 1:54
  • Actually, I thought a bit and found that there's no need even to `-t': unix.stackexchange.com/a/40283/6622
    – poige
    Jun 8, 2012 at 2:03
  • @poige cool about the -r option. I did not know it. And yep you are right about the -t for cp, I thought of it too, but it does not really apply in this use case.
    – Huygens
    Jun 9, 2012 at 22:48
2

Something like the following should work if they truly have such names:

#!/bin/bash
shopt -s nullglob
for dir in dir*/; do
    cp file* "$dir"
done

If you want to copy all files to all directories regardless of name:

#!/bin/bash
shopt -s nullglob dotglob
for dir in */; do
    for file in *; do
        [[ -f $file ]] && cp "$file" "$dir"
    done
done
1

You're almost there. You need to run a separate cp command for each destination directory, because cp only makes one copy of every source file. The primary purpose of xargs is to run one command over multiple arguments, but you can tell it to pass a single item at a time with the -n option. Also {} ; is the syntax of find … -exec; you don't need (and can't use) it with xargs.

echo dir{1..3} | xargs -n 1 cp file{1..5}

Note that this only works if your directory names do not contain any whitespace or any of the characters \'", because xargs has no way to tell that those characters are part of file names and not quoting and separating input files. An alternative method that doesn't have any such problem is to use a shell loop:

for d in dirs{1..3}; do
  cp file{1..5} "$d"
done

All this assumes that you can easily distinguish between files and directories by their names. If you can't, you can match directories by adding a / at the end; for example */ matches all directories in the current directories, foo[0-9]*/ matches all directories whose name begins with foo and a digit, etc. There is no similar way to match only non-directories in most shells, but if you pass directories to cp with no argument to tell it to make a recursive copy, the directories are ignored with an error message.

for d in */; do
  cp * "$d"
done

In zsh, you can use glob qualifiers to match only certain file types.

for d in *(/); do
  cp *(.) $d
done
2
  • Excuse me but when I run the first command you wrote I got this error: cp: cannot stat {}': No such file or directory cp: cannot stat ;': No such file or directory cp: cannot stat {}': No such file or directory cp: cannot stat ;': No such file or directory cp: cannot stat {}': No such file or directory cp: cannot stat ;': No such file or directory as if it's not able to understand {} Jun 8, 2012 at 7:47
  • 1
    @LuigiTiburzi Oops, I forgot to remove that. Edited. Jun 8, 2012 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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