12

I have a symbolic link, sd/common.py -> actual_file, and I want to replace the link with a generated file.

However, whenever I do

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

... it copies from /tmp/Star_Wrangler/common.py and overwrites the actual_file instead of just replacing the symbolic link as I intend. Every time I forget to delete the symbolic link before copying, this keeps happening.

Is there an option to get the behaviour I expect? I look at the manuals, but they all talk about symbolic links 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, 2021 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, 2021 at 17:07

2 Answers 2

19

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.

3

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")"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .