11

So I have a symlink sd/common.py -> actual_file

I would like to replace the symlink with a generated file, but when I do:

cp /tmp/Star_Wrangler/common.py sd/common.py

It copies from common.py and obliterates the actual_file instead of just copying over the symlink like I want it to. This keeps happening everytime I forget to delete the symlink before copying.

Is there a flag to get the correct behavior? I look at the man pages but they all talk about symlinks at the source not at the target.

2
  • 1
    This, coincidentally, is also the reason why build scripts test exactly how the locally installed version of some executable behaves in specific circumstances.
    – MechMK1
    Sep 2 at 14:38
  • @MechMK1 Another reason why everytime I've started a script in bash, I end up regretting it and switching to Python. It just works and it works across platforms and any utility I need, I can call with subprocess Sep 2 at 17:07
17

This depends on what Unix you are using.

On some BSD systems (OpenBSD, FreeBSD), you will find that cp -f will unlink (remove) the symbolic link and replace it with the source.

Using GNU cp, this would not have the same effect, and you would need to use the long option --remove-destination instead.

On macOS, use cp -c to, as the manual says, "copy files using clonefile(2)".

On NetBSD, use cp -a ("archive mode", the same as cp -RpP on that system). This doesn't work on GNU, macOS, OpenBSD, or FreeBSD, even though all of these systems have the same or similar -a option for cp (on GNU systems, it's the same as -dR --preserve).

You already mention this yourself: Removing the link before copying the file will solve the issue. The rm utility removes the link rather than the file referenced by the link. This is also the most portable way to replace a symbolic link with a regular file.

If you are writing a script, then I suggest that you use rm followed by cp.

If you are working interactively and keep forgetting to do this, then it's also likely that you forget to use a specific option with cp for these situations.

1

Symlinks have very confusing behavior, not just with cp, but also chmod, chown and the builtin test (or its [ .. ] alias) for example.

Remove the symlink first (rm -f sd/common.py), then cp.

Sure, the various cp's on various systems have various options to do this, but it's very easy to get wrong-footed.

Another thing you can do is avoid cp and aim for a symlink result. ln -sf SOURCE DESTINATION (with both args being absolute paths) works on all POSIX systems and does not create any further confusions. -f removes any existing DESTINATION file / symlink (but not dir, alas). You can combine this with realpath to avoid the relative / absolute path problem:

ln -sf "$(realpath "$SOURCE")" "$(realpath "$DESTINATION")"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.