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.
replace
. See this answer for a complete solution (including generating a temporary symlink pathname). –
Ybarra replace
raises IsADirectoryError
if linkName
is a directory and tmpLink
will then need to be deleted. 2) replace
may fail if the two files are not on the same filesystem. I deal with both cases in this answer. –
Ybarra A little function for Python3 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 as e:
if e.errno == errno.EEXIST:
os.remove(link_name)
os.symlink(target, link_name)
else:
raise e
link_name
could be created again between removal and symlink creation. Try atomically overwriting the existing symlink. –
Ybarra except FileExistsError:
? I got a syntax error as written. –
Ybarra 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.
symlink()
. –
Ybarra tempfile.mktemp()
which has been deprecated since Python 2.3 and should be no longer used. –
Cursor mktemp
: any other process creating the file between mktemp
and symlink
is handled by FileExistsError
. The link you post includes the word 'may'. I avoid mkstemp
as it both creates and opens the file, requiring both closing then deleting the file, which must not exist (prerequisite for a successful symlink
.) –
Ybarra You could os.unlink()
it first, and then re-create using os.symlink()
to point to the new target.
link_name
could be created again between removal and symlink creation. Try atomically overwriting the existing symlink. –
Ybarra 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)
if os.path.islink(dst) or os.path.exists(dst):
–
Insincere © 2022 - 2024 — McMap. All rights reserved.
os.replace
which can be used instead ofos.rename
to provide the same behaviour on Posix & Windows systems. – Bleier