How to preserve symbolic link while copying a file in Linux

Below set of commands are executed on SuSE Linux Enterprise Server 11 SP 3 operating system,
There can be many scenarios of copying symbolic links between different directories, let me try to explain some of the cases with examples to help you understand
I have created few sample files and symbolic links to demonstrate this example

# mkdir /tmp/src_file
# cd /tmp/src_files
# ls -l
total 0
-rw-r--r-- 1 root root  0 Sep 11 17:53 a
-rw-r--r-- 1 root root  0 Sep 11 17:53 b
-rw-r--r-- 1 root root  0 Sep 11 17:53 c
lrwxrwxrwx 1 root root 13 Sep 11 17:53 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root root 12 Sep 11 17:53 cron_deny -> ../cron.deny

 

Copy symbolic links as files

Above I have two smylinks cron_allow and cron_deny, Let us copy them to /tmp/dst_files as normal files instead of symlinks

# cp -LRv /tmp/src_files/* /tmp/dst_files/
`/tmp/src_files/a' -> `/tmp/dst_files/a'
`/tmp/src_files/b' -> `/tmp/dst_files/b'
`/tmp/src_files/c' -> `/tmp/dst_files/c'
`/tmp/src_files/cron_allow' -> `/tmp/dst_files/cron_allow'
`/tmp/src_files/cron_deny' -> `/tmp/dst_files/cron_deny'

Verify the symlinks

# ll /tmp/dst_files/
total 8
-rw-r--r-- 1 root root  0 Sep 11 18:03 a
-rw-r--r-- 1 root root  0 Sep 11 18:03 b
-rw-r--r-- 1 root root  0 Sep 11 18:03 c
-rw-r--r-- 1 root root 28 Sep 11 18:03 cron_allow
-rw-r--r-- 1 root root 15 Sep 11 18:03 cron_deny

As you see both symlinks are converted into normal file. Let us match the content of these with the original file

# sdiff cron_allow /tmp/cron.allow
root                                                         root
deepak                                                       deepak
amit                                                         amit
rahul                                                        rahul

From the 'man' page

‘-L’ ,‘--dereference’ - Follow symbolic links when copying from them. With this option, cp cannot create a symbolic link. For example, a symlink (to regular file) in the source tree will be copied to a regular file in the destination tree.

 

Copy symlinks as symlinks (not a file)

# cp -Prv /tmp/src_files/* /tmp/dst_files/
`/tmp/src_files/a' -> `/tmp/dst_files/a'
`/tmp/src_files/b' -> `/tmp/dst_files/b'
`/tmp/src_files/c' -> `/tmp/dst_files/c'
`/tmp/src_files/cron_allow' -> `/tmp/dst_files/cron_allow'
`/tmp/src_files/cron_deny' -> `/tmp/dst_files/cron_deny'

Verify the symlinks

# ll /tmp/dst_files/
total 0
-rw-r--r-- 1 root root  0 Sep 11 18:06 a
-rw-r--r-- 1 root root  0 Sep 11 18:06 b
-rw-r--r-- 1 root root  0 Sep 11 18:06 c
lrwxrwxrwx 1 root root 13 Sep 11 18:06 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root root 12 Sep 11 18:06 cron_deny -> ../cron.deny

This can also be achieved with some more arguments like below

# cp -dv /tmp/src_files/* /tmp/dst_files/
`/tmp/src_files/a' -> `/tmp/dst_files/a'
`/tmp/src_files/b' -> `/tmp/dst_files/b'
`/tmp/src_files/c' -> `/tmp/dst_files/c'
`/tmp/src_files/cron_allow' -> `/tmp/dst_files/cron_allow'
`/tmp/src_files/cron_deny' -> `/tmp/dst_files/cron_deny'

 

Copy symlinks with same permissions and ownership as from source directory

Normally if you copy a file the permission of the target location is changed to the user which was used to copy the file (considering that user as the new owner of the target location)
Let us change the permission and owner of one of the file from our source directory

# ls -l
total 0
-rwxrwxrwx 1 oamsys root  0 Sep 11 17:53 a
-rwxrwxrwx 1 oamsys root  0 Sep 11 17:53 b
-rw-r--r-- 1 root   root  0 Sep 11 17:53 c
lrwxrwxrwx 1 root   root 13 Sep 11 17:53 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root   root 12 Sep 11 17:53 cron_deny -> ../cron.deny

After copying

# cp -dv /tmp/src_files/* /tmp/dst_files/
`/tmp/src_files/a' -> `/tmp/dst_files/a'
`/tmp/src_files/b' -> `/tmp/dst_files/b'
`/tmp/src_files/c' -> `/tmp/dst_files/c'
`/tmp/src_files/cron_allow' -> `/tmp/dst_files/cron_allow'
`/tmp/src_files/cron_deny' -> `/tmp/dst_files/cron_deny'

Verify

# ll /tmp/dst_files/
total 0
-rwxr-xr-x 1 root root  0 Sep 11 18:12 a
-rwxr-xr-x 1 root root  0 Sep 11 18:12 b
-rw-r--r-- 1 root root  0 Sep 11 18:12 c
lrwxrwxrwx 1 root root 13 Sep 11 18:12 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root root 12 Sep 11 18:12 cron_deny -> ../cron.deny

So considering my umask value i.e. 0022 the file permission were updated to 755 and ownership has been changed to 'root'' as the files were copied using 'root' user.
So how do I preserve these permissions along with symlinks

# cp -dav /tmp/src_files/* /tmp/dst_files/
`/tmp/src_files/a' -> `/tmp/dst_files/a'
`/tmp/src_files/b' -> `/tmp/dst_files/b'
`/tmp/src_files/c' -> `/tmp/dst_files/c'
`/tmp/src_files/cron_allow' -> `/tmp/dst_files/cron_allow'
`/tmp/src_files/cron_deny' -> `/tmp/dst_files/cron_deny'

Verify

# ll /tmp/dst_files/
total 0
-rwxrwxrwx 1 oamsys root  0 Sep 11 17:53 a
-rwxrwxrwx 1 oamsys root  0 Sep 11 17:53 b
-rw-r--r-- 1 root   root  0 Sep 11 17:53 c
lrwxrwxrwx 1 root   root 13 Sep 11 17:53 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root   root 12 Sep 11 17:53 cron_deny -> ../cron.deny

So everything looks on track now.
From 'man' page

       -a, --archive
              same as -dR --preserve=all

 

Copy symlinks with relative path

One big question here, for me the copying of symbolic link is working fine because we are copying the symlink in the same parent directory and the target file is always accessible by the symlink.
Let me show you some more examples

# ll
total 0
-rwxrwxrwx 1 oamsys root  0 Sep 11 17:53 a
-rwxrwxrwx 1 oamsys root  0 Sep 11 17:53 b
-rw-r--r-- 1 root   root  0 Sep 11 17:53 c
lrwxrwxrwx 1 root   root 13 Sep 11 17:53 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root   root 12 Sep 11 17:53 cron_deny -> ../cron.deny

Here above the symlink cron_allow is pointing to /tmp/cron.allow, but what I copy these files to a completely different location where relative path ../cron.allow is inaccessible

# cp -Prv /tmp/src_files/* /tmp/test/new_dst/
`/tmp/src_files/a' -> `/tmp/test/new_dst/a'
`/tmp/src_files/b' -> `/tmp/test/new_dst/b'
`/tmp/src_files/c' -> `/tmp/test/new_dst/c'
`/tmp/src_files/cron_allow' -> `/tmp/test/new_dst/cron_allow'
`/tmp/src_files/cron_deny' -> `/tmp/test/new_dst/cron_deny'

Verify

# ll /tmp/test/new_dst/
total 0
-rwxr-xr-x 1 root root  0 Sep 11 18:19 a
-rwxr-xr-x 1 root root  0 Sep 11 18:19 b
-rw-r--r-- 1 root root  0 Sep 11 18:19 c
lrwxrwxrwx 1 root root 13 Sep 11 18:19 cron_allow -> ../cron.allow
lrwxrwxrwx 1 root root 12 Sep 11 18:19 cron_deny -> ../cron.deny
# cd /tmp/test/new_dst/
# less cron_allow
cron_allow: No such file or directory

So ../cron.allow is not accessible. To avoid such scenarios it is always  recommended to use full path of the target file for a symlink.
Currently I am now aware of a single command which can fix this, if someone knows than please put in your comment which can be used to overcome such scenarios
As of now I use scripting to fix these issues (a small sample script)

#!/bin/sh
src="/tmp/src_files"
dst="/tmp/test/new_dst"
#clean destination
if [ -d $dst ]; then
   echo "Cleaning target directory $dst"
   rm -f $dst/*
fi
#copy files
for i in `ls $src`; do
   path=""
   if [  -L $i ]; then
      path=`readlink -f $i`
      ln -s $path $dst/$i
   else
      cp -vP $i $dst
   fi
done
# Listing target directory files
ls -al $dst

I hope the article was useful.