Copying a symbolic link in Python
Question:
I want to copy a file src
to the destination dst
, but if src
happens to be a symbolic link, preserve the link instead of copying the contents of the file. After the copy is performed, os.readlink
should return the same for both src
and dst
.
The module shutil
has several functions, such as copyfile
, copy
and copy2
, but all of these will copy the contents of the file, and will not preserve the link. shutil.move
has the correct behavior, other than the fact it removes the original file.
Is there a built-in way in Python to perform a file copy while preserving symlinks?
Answers:
Just do
def copy(src, dst):
if os.path.islink(src):
linkto = os.readlink(src)
os.symlink(linkto, dst)
else:
shutil.copy(src,dst)
shutil.copytree does something similar, but as senderle noted, it’s picky about copying only directories, not single files.
Python 3 follow_symlinks
In Python 3, most copy methods of shutil
have learned the follow_symlinks
argument, which preserves symlinks if selected.
E.g. for shutil.copy
:
shutil.copy(src, dest, follow_symlinks=False)
and the docs say:
shutil.copy(src, dst, *, follow_symlinks=True)
Copies the file src to the file or directory dst. src and dst should be strings. If dst specifies a directory, the file will be copied into dst using the base filename from src. Returns the path to the newly created file.
If follow_symlinks
is false, and src is a symbolic link, dst will be created as a symbolic link. If follow_symlinks` is true and src is a symbolic link, dst will be a copy of the file src refers to.
This has one problem however: if you try to overwrite an existing file or symlink, it fails with:
FileExistsError: [Errno 17] File exists: 'b' -> 'c'
unlike the follow_symlinks=True
which successfully overwrites.
The same also happens for os.symlink
, so I ended up using instead:
#!/usr/bin/env python3
import shutil
import os
def copy(src, dst):
if os.path.islink(src):
if os.path.lexists(dst):
os.unlink(dst)
linkto = os.readlink(src)
os.symlink(linkto, dst)
else:
shutil.copy(src, dst)
if __name__ == '__main__':
os.symlink('c', 'b')
os.symlink('b', 'a')
copy('a', 'b')
with open('c', 'w') as f:
f.write('a')
with open('d', 'w'):
pass
copy('c', 'd')
copy('a', 'c')
Tested in Ubuntu 18.10, Python 3.6.7.
I want to copy a file src
to the destination dst
, but if src
happens to be a symbolic link, preserve the link instead of copying the contents of the file. After the copy is performed, os.readlink
should return the same for both src
and dst
.
The module shutil
has several functions, such as copyfile
, copy
and copy2
, but all of these will copy the contents of the file, and will not preserve the link. shutil.move
has the correct behavior, other than the fact it removes the original file.
Is there a built-in way in Python to perform a file copy while preserving symlinks?
Just do
def copy(src, dst):
if os.path.islink(src):
linkto = os.readlink(src)
os.symlink(linkto, dst)
else:
shutil.copy(src,dst)
shutil.copytree does something similar, but as senderle noted, it’s picky about copying only directories, not single files.
Python 3 follow_symlinks
In Python 3, most copy methods of shutil
have learned the follow_symlinks
argument, which preserves symlinks if selected.
E.g. for shutil.copy
:
shutil.copy(src, dest, follow_symlinks=False)
and the docs say:
shutil.copy(src, dst, *, follow_symlinks=True)
Copies the file src to the file or directory dst. src and dst should be strings. If dst specifies a directory, the file will be copied into dst using the base filename from src. Returns the path to the newly created file.
If
follow_symlinks
is false, and src is a symbolic link, dst will be created as a symbolic link. If follow_symlinks` is true and src is a symbolic link, dst will be a copy of the file src refers to.
This has one problem however: if you try to overwrite an existing file or symlink, it fails with:
FileExistsError: [Errno 17] File exists: 'b' -> 'c'
unlike the follow_symlinks=True
which successfully overwrites.
The same also happens for os.symlink
, so I ended up using instead:
#!/usr/bin/env python3
import shutil
import os
def copy(src, dst):
if os.path.islink(src):
if os.path.lexists(dst):
os.unlink(dst)
linkto = os.readlink(src)
os.symlink(linkto, dst)
else:
shutil.copy(src, dst)
if __name__ == '__main__':
os.symlink('c', 'b')
os.symlink('b', 'a')
copy('a', 'b')
with open('c', 'w') as f:
f.write('a')
with open('d', 'w'):
pass
copy('c', 'd')
copy('a', 'c')
Tested in Ubuntu 18.10, Python 3.6.7.