29

What's the simplest way on Linux to "copy" a directory hierarchy so that a new hierarchy of directories are created while all "files" are just symlinks pointing back to the actual files on the source hierarchy?

cp -s does not work recursively.

Simon East
  • 55,742
  • 17
  • 139
  • 133
Max Spring
  • 1,111
  • 2
  • 11
  • 18

7 Answers7

39

I just did a quick test on a linux box and cp -sR /orig /dest does exactly what you described: creates a directory hierarchy with symlinks for non-directories back to the original.

freiheit
  • 4,976
  • 37
  • 35
  • 4
    It only works on the first directory level. For every file in subdirectories I get "xyz-file: can make relative symbolic links only in current directory". (On Ubuntu 8.10, cp version 6.10) – Max Spring Aug 10 '09 at 03:48
  • 36
    The reason you get "xyz-file: can make relative symbolic links only in current directory" is because for the source directory, you specified a relative path. It'll work as you want it if you specify an absolute path for the source, like so: "cp -sR /root/absolute/path/name dest". – PonyEars Sep 03 '11 at 00:34
  • 6
    In case you need/want to make all the symlinks relative you can use [symlinks](http://www.linuxcommand.org/man_pages/symlinks8.html) like this: `cp -sR /orig /dest && symlinks -rc /dest` – Nathan S. Watson-Haigh Feb 04 '14 at 02:38
  • 4
    Does not work on all systems. On Mac/PowerPC, I get: `cp: illegal option -- s`, so if you need to make a portable script, you'll need a different approach. –  Oct 09 '14 at 06:39
  • Just to confirm that this does symlink dot files, as expected. – fooquency Jul 19 '16 at 17:14
  • Is there a way to do the same thing but without `orig` ending up as the first directory in `/dest`? I.e. presence of `/orig/foo/bar` would result in a symlink `/dest/foo/bar -> /orig/foo/bar` , whereas the above results in `/dest/orig/foo/bar -> /orig/foo/bar` . – fooquency Jul 19 '16 at 17:24
  • @fooquency Pay careful attention to the slashes. `/dest/` and `/dest` will give you different results. – freiheit Jul 19 '16 at 17:28
  • @freiheit That seems to make no difference to whether `orig` ends up in the path. – fooquency Jul 19 '16 at 20:59
  • On QNX the `-s` also has other meaning causing this solution not to work. – avp Sep 07 '16 at 13:41
  • @avp `-s` is not in the POSIX standard, that I can see, so there's no guarantee that this works on a system that doesn't use the GNU utils for cp, etc. I don't believe this will work on OSX, either. But most Linux distributions *do* use GNU for cp, etc. – freiheit Sep 09 '16 at 00:45
  • @freiheit, that's right. I just find wrong the fact that for the question which is marked with [unix] tag -- it is exactly my case therefore I care -- the community ignores the best answer from Drew Stephens and votes for this one. It is very good answer, but it is clearly not applicable for unix as you have already mentioned it, that's it. – avp Sep 09 '16 at 14:48
  • @avp The question clearly states *on Linux* and asks for "simplest". Drew Stephens' answer works in more other unix environments, but doesn't seem to me to be the simplest solution on a typical Linux system. – freiheit Sep 09 '16 at 21:48
18
cp -as /root/absolute/path/name dest_dir

will do what you want. Note that the source name must be an absolute path, it cannot be relative. Else, you'll get this error: "xyz-file: can make relative symbolic links only in current directory."

Also, be careful as to what you're copying: if dest_dir already exists, you'll have to do something like:

cp -as /root/absolute/path/name/* dest_dir/
cp -as /root/absolute/path/name/.* dest_dir/
PonyEars
  • 2,144
  • 4
  • 25
  • 30
13

Starting from above the original & new directories, I think this pair of find(1) commands will do what you need:

find original -type d -exec mkdir new/{} \;
find original -type f -exec ln -s {} new/{} \;

The first instance sets up the directory structure by finding only directories in the original tree and recreating them in the new tree. The second creates the symlinks to the original files in the new tree.

Drew Stephens
  • 17,207
  • 15
  • 66
  • 82
  • 1
    This works very nicely for me (as written above, cp -as or -Rs won't work on Mac OS X). It might be a good idea to `(cd origParentDir; find origDir .. ; find origDir ..)` in order to get the relative path as parameter for both mkdir and ln -s. Use parantheses to start a `(`subshell`)` because running cd in a subshell will only temporary set CWD; CWD is restored when the subshell exits. Eg. if you're 'cloning' `/src/binutils-2.24` to `${HOME}/source` then you do not want the '`src`' folder to be created. –  Oct 10 '14 at 01:46
  • Odd that `ln` doesn't have a recursive option... like `ln -s -R orig/dir dest/dir`. – jiggunjer Oct 07 '16 at 03:35
7

There's also the "lndir" utility (from X) which does such a thing; I found it mentioned here: Debian Bug report #301030: can we move lndir to coreutils or debianutils? , and I'm now happily using it.

imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
  • 2
    I had the exact problem as the original questioner, and after starting to code a shell script, found this answer and discovered lndir already installed. Works exactly as I need it. – shmuelp Oct 06 '09 at 19:02
1

I googled around a little bit and found a command called lns, available from here.

JesperE
  • 63,317
  • 21
  • 138
  • 197
  • Why do you recommend a 3rd party tool if cp with some (2) options does the trick. Seems like a security risk to me. – Nikodemus RIP Aug 05 '17 at 14:22
  • Because I did not know about the -s option eight years ago when I wrote the answer. Feel free to upvote/downvote if you think the answer is good/bad. – JesperE Aug 14 '17 at 16:55
1

If you feel like getting your hands dirty Here is a trick that will automatically create the destination folder, subfolders and symlink all files recursively.

In the folder where the files you want to symlink and sub folders are:

  1. create a file shell.sh:

    nano shell.sh

  2. copy and paste this charmer:

#!/bin/bash

export DESTINATION=/your/destination/folder/
export TARGET=/your/target/folder/

find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; 
do
  echo "${DESTINATION}${DIR}"
  mkdir -p "${DESTINATION}${DIR}"        
  done' -


find . -type f -print0 |  xargs -0 bash -c 'for file in "$@"; 
do
  ln -s  "${TARGET}${file}"  "${DESTINATION}${file}"
   done' -
  1. save the file ctrl+O
  2. close the file ctrl+X
  3. Make your script executable chmod 777 shell.sh

  4. Run your script ./shell.sh

Happy hacking!

0

I know the question was regarding shell, but since you can call perl from shell, I wrote a tool to do something very similar to this, and posted it on perlmonks a few years ago. In my case, I generally wanted directories to remain links until I decide otherwise. It'd be a fairly trivial change to do this automatically and recursively.

Tanktalus
  • 21,664
  • 5
  • 41
  • 68