5

For application specific reasons, I need to copy an entire parent directory into a subdirectory. E.g.,

cp -r ../ tmp/

The problem with this is that it gets stuck in an infinite loop, recursively copying the contents of tmp/ over and over again.

There are a number of ways around this, like tarring the directory and unpacking it in tmp, but I'm wondering if there are any particularly elegant/unixy solutions.

(note: I'm using Apple OS/X).

3 Answers3

3

If you start from the parent directory, you can do this with find and GNU cp. Assuming the directory you're in currently (the one containing tmp) is called folder, and that tmp is empty, you'd run

cd ..
find . -path ./folder/tmp -prune -o -type f -exec cp --parents -t folder/tmp {} +

This asks find to list everything under . (which is your old ..), exclude it if it matches ./folder/tmp, and otherwise if it's a file pass it to cp with the --parents option (which tells cp to reconstruct the source hierarchy).

If tmp already has files which also need to be copied, the following variant is slightly less Unix-y (since it uses moreutils' sponge) but avoids skipping the contents of tmp:

cd ..
find . -type f -print0 | sponge | xargs -0 cp --parents -t folder/tmp

You could avoid the use of sponge by saving the list of files to copy in a file somewhere else (but then things get rather less elegant):

cd ..
find . -type f -print0 > /tmp/filelist
xargs -0 cp --parents -t folder/tmp < /tmp/filelist
rm /tmp/filelist

You can avoid the requirement on GNU cp by using cpio instead:

cd ..
find . -type f -print0 > /tmp/filelist
cpio -pmd0 folder/tmp < /tmp/filelist
rm /tmp/filelist

or

cd ..
find . -type f -print0 | sponge | cpio -pmd0 folder/tmp

If tmp is empty you can also avoid the requirement on moreutils:

cd ..
find . -path ./folder/tmp -prune -o -type f -print0 | cpio -pmd0 folder/tmp
Stephen Kitt
  • 434,908
  • Oh yes, I assumed tmp was empty but that may well be wrong! – Stephen Kitt May 27 '15 at 20:14
  • When I try this I get cp: illegal option -- -. I'm actually 3 levels up in the hierarchy. The command looks like this: find . -path ./docker/app/tmp/ -prune -o -type f -exec cp --parents -t docker/app/tmp/ {} + – user1427661 May 27 '15 at 22:15
  • Does it work if you add -- before {}? As in cp --parents -t docker/app/tmp -- {} +... – Stephen Kitt May 27 '15 at 22:37
  • No -- I get the same error. – user1427661 May 28 '15 at 15:18
  • Hmm... What does cp --version say? Does the last cpio variant work? – Stephen Kitt May 28 '15 at 18:03
  • cp --version gives me the usage screen for cp. I should have mentioned I'm actually doing this on OSX, so it's the BSD cp that ships with the OS. I guess this is one of the cases where the mac utils are annoyingly different than their unix/linux counterparts in subtle ways. The problem is I'm writing this as a util for the team, and most of them develop on macs. It's a tool for building containers, so once I get them in the containers I have control, but before that I can't enforce a specific version of cp or get sponge installed on the system. – user1427661 May 28 '15 at 23:12
  • That's why I mentioned the requirement on GNU cp. I've added a variant without GNU cp and moreutils, it works on Mac OS X (but it doesn't copy tmp so it's appropriate only if tmp is empty). – Stephen Kitt May 29 '15 at 05:03
0

For interactive use, I have a hack for the analogous case with mv:

cd ..
mv * tmp

mv: cannot move ‘tmp’ to a subdirectory of itself, ‘tmp/tmp’

Ignore the error, everything else got moved :-P. Doesn't work quite the same with cp, it creates an empty tmp/tmp, which is probably not what you want.

sourcejedi
  • 50,249
0

With pax, to copy . into subdir/tmp, skipping subdir/tmp:

mkdir -p subdir/tmp &&
  pax -rws'|^\./subdir/tmp/.*||' . subdir/tmp

Alternatively, you could do something like:

mkdir -p subdir/tmp/subdir &&
  ln -s tmp subdir/tmp/subdir/tmp &&
  cp -a . subdir/tmp

subdir/tmp/subdir/tmp is then a symlink to itself, to cp won't be able to create anything under it. You can replace it with an empty directory afterwards if you want.