How do I change a symlink to point from one file to another in Python?
The os.symlink function only seems to work to create new symlinks.
How do I change a symlink to point from one file to another in Python?
The os.symlink function only seems to work to create new symlinks.
If you need an atomic modification, unlinking won't work.
A better solution would be to create a new temporary symlink, and then rename it over the existing one:
os.symlink(target, tmpLink)
os.rename(tmpLink, linkName)
You can check to make sure it was updated correctly too:
if os.path.realpath(linkName) == target:
# Symlink was updated
According to the documentation for os.rename though, there may be no way to atomically change a symlink in Windows. In that case, you would just delete and re-create.
A little function for Python2 which tries to symlink and if it fails because of an existing file, it removes it and links again.
import os, errno
def symlink_force(target, link_name):
try:
os.symlink(target, link_name)
except OSError, e:
if e.errno == errno.EEXIST:
os.remove(link_name)
os.symlink(target, link_name)
else:
raise e
For python3 except condition should be except OSError as e:
Given overwrite=True, this function will safely overwrite an existing file with a symlink.
It is cognisant of race conditions, which is why it is not short, but it is safe.
import os, tempfile
def symlink(target, link_name, overwrite=False):
'''
Create a symbolic link named link_name pointing to target.
If link_name exists then FileExistsError is raised, unless overwrite=True.
When trying to overwrite a directory, IsADirectoryError is raised.
'''
if not overwrite:
os.symlink(target, link_name)
return
# os.replace() may fail if files are on different filesystems
link_dir = os.path.dirname(link_name)
# Create link to target with temporary filename
while True:
temp_link_name = tempfile.mktemp(dir=link_dir)
# os.* functions mimic as closely as possible system functions
# The POSIX symlink() returns EEXIST if link_name already exists
# https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html
try:
os.symlink(target, temp_link_name)
break
except FileExistsError:
pass
# Replace link_name with temp_link_name
try:
# Pre-empt os.replace on a directory with a nicer message
if not os.path.islink(link_name) and os.path.isdir(link_name):
raise IsADirectoryError(f"Cannot symlink over existing directory: '{link_name}'")
os.replace(temp_link_name, link_name)
except:
if os.path.islink(temp_link_name):
os.remove(temp_link_name)
raise
Notes for pedants:
If the function fails (e.g. computer crashes), an additional random link to the target might exist.
An unlikely race condition still remains: the symlink created at the randomly-named temp_link_name could be modified by another process before replacing link_name.
I raised a python issue to highlight the issues of os.symlink() requiring the target to not exist, where I was advised to raise my suggestion on the python-ideas mailing list
Credit to Robert Siemer’s input.
You could os.unlink() it first, and then re-create using os.symlink() to point to the new target.
I researched this question recently, and found out that the best way is indeed to unlink and then symlink. But if you need just to fix broken links, for example with auto-replace, then you can do os.readlink:
for f in os.listdir(dir):
path = os.path.join(dir, f)
old_link = os.readlink(path)
new_link = old_link.replace(before, after)
os.unlink(path)
os.symlink(new_link, path)
Don't forget to add a raise command in the case when e.errno != errno.EEXIST You don't want to hide an error then:
if e.errno == errno.EEXIST:
os.remove(link_name)
os.symlink(target, link_name)
else:
raise
A quick and easy solution:
while True:
try:
os.symlink(target, link_name)
break
except FileExistsError:
os.remove(link_name)
However this has a race condition when replacing a symlink which should always exist, eg:
/lib/critical.so -> /lib/critical.so.1.2
When upgrading by:
my_symlink('/lib/critical.so.2.0', '/lib/critical.so')
There is a point in time when /lib/critical.so doesn't exist.
This answer avoids the race condition.
I like this version more
import os
def force_symlink(src, dst):
if os.path.exists(dst):
if os.path.realpath(src) == dst:
return
os.unlink(dst)
os.symlink(src, dst)