72

What is the most direct way to convert a symlink into a regular file (i.e. a copy of the symlink target)?

Suppose filename is a symlink to target. The obvious procedure to turn it into a copy is:

cp filename filename-backup
rm filename
mv filename-backup filename

Is there a more direct way (i.e. a single command)?

nibot
  • 14,428
  • 8
  • 54
  • 58
  • The other way around: http://stackoverflow.com/questions/2330543/how-to-turn-a-regular-file-into-a-symlink-on-linux. Both are probably not possible with low level calls because the symlink path is stored in the inode itself for some filesystems, including ext3. – Ciro Santilli OurBigBook.com Sep 09 '14 at 12:38
  • Note, simple change needed if the symlink is possibly an entire folder structure (your question was the almost-answer I needed): `cp -Lr filename filename-backup; rm filename; mv filename-backup filename` – Ryan Franz Jul 09 '21 at 18:35

16 Answers16

64

There is no single command to convert a symlink to a regular file. The most direct way is to use readlink to find the file a symlink points to, and then copy that file over the symlink:

cp --remove-destination `readlink bar.pdf` bar.pdf

Of course, if bar.pdf is, in fact, a regular file to begin with, then this will clobber the file. Some sanity checking would therefore be advisable.

nibot
  • 14,428
  • 8
  • 54
  • 58
  • 12
    Note: The `--remove-destination` option will not work on macOS. The answer `mv $(readlink link) link`works for that case. – mattmc3 Feb 20 '17 at 03:08
  • Note that this wouldn't work with special files like stdout, stderr, etc. E.g., with `access.log -> /dev/stdout` the `cp` would hang (probably forever).. so if anyone would think of using this an automated script, think twice ;-) – jso Sep 10 '19 at 10:45
  • 2
    I would say that `realpath` is more preferrable than `readlink` because it gives you absolute path (while readlink gives path relative to a link not to a working directory). – myaut Nov 11 '20 at 17:28
35
for f in $(find -type l);do cp --remove-destination $(readlink $f) $f;done;
  • Check symlinks in the current directory and subdirectories find -type l
  • Get the linked file path readlink $f
  • Remove symlink and copy the file cp --remove-destination $(readlink $f) $f
user1767754
  • 23,311
  • 18
  • 141
  • 164
Yadhu
  • 351
  • 3
  • 2
  • 2
    I got the error cp: missing destination file operand after `./_home_xyz' – Matthew Lock May 26 '16 at 08:17
  • This is good, but it doesn't copy symlink folders and their contents. – Jonno_FTW Apr 09 '18 at 03:39
  • This is great for doing file symlinks in sub-folders! The only thing I had to do was run 'unalias cp' beforehand as this answer prompts on every file on Red Hat/CentOS based distro's otherwise. – Ralph Oct 26 '18 at 10:58
  • 2
    This didn't work for me for some reason on Ubuntu 20.04, got a lot of errors about things not being directories, but this did: `for f in $(find -type l);do a=$(readlink $f) rm "$f"; cp "$a" "$f"; done;` – Konstantin Pereiaslov Jan 16 '21 at 04:12
21

Just a rehash of other's answers, but adding the "sanity check" to ensure the link passed in is actually a symbolic link:

removelink() {
  [ -L "$1" ] && cp --remove-destination "$(readlink "$1")" "$1"
}

This is saying that if the file is a symbolic link, then run the copy command.

kbrock
  • 958
  • 12
  • 15
  • 1
    You should replace `\`readlink "$1"\`` with `"$(readlink "$1")"` to be sure everything works correctly with filenames with spaces. – Frank Kusters Mar 12 '15 at 13:16
18

For text files the sed command can do this in one line if you pass it the in-place switch (-i). This is because sed does a single pass over a file, cats the output into a temporary file which it subsequently renames to match the original.

Just do an inline sed with no transforms:

sed -i ';' /path/to/symbolic/link
dsclose
  • 556
  • 5
  • 15
  • 1
    This seems like the simplest solution to me. Note that on macOS you will need to use `-i ''` instead of just `-i`: `sed -i '' ';' /path/to/link`. – J.D. Nov 14 '16 at 15:49
  • 1
    I like my answer but I still wouldn't use it. It's a bit abusive – dsclose Nov 26 '16 at 09:20
  • 3
    `sed` will in fact create new file with same content but it will not copy any attributes (such as ownership and permissions). Whereas using `cp` it's possible to control which attributes to copy. – reddot Jun 27 '18 at 11:35
  • `sed -i '' ';' /path/to/link` doesn't work on macOS: `sed: /path/to/link: in-place editing only works for regular files` – Tobias Bergkvist Mar 15 '23 at 18:09
7

On OSX

rsync `readlink bar.pdf` bar.pdf

Paul Beusterien
  • 27,542
  • 6
  • 83
  • 139
  • How would you make that apply to all symlinks in a directory? I tried variations of `find . -type l -exec rsync $(readlink {}) {} \;`, but it either just lists all the symlinks in the directory (similar to the output of `ls`) or it complains `skipping directory .` – cjm Jan 16 '17 at 19:11
3

This solution works with symlink files and symlink folders/subfolders.

One line, no script needed. This would be ideal to export or transfer symlinks elsewhere.

Put everything inside a folder.

rsync --archive --copy-links --recursive folderContainingSymlinkFilesAndSymlinkFoldersAndSubfolders myNewFolder

  • 1
    --archive implies --recursive, and also --links which may not be what you want, better to do rsync -rptgoDLk (see https://linux.die.net/man/1/rsync) – David Wu Apr 27 '22 at 08:34
3

cp can remove the destination file:

cp --remove-destination target filename
Andrii Radyk
  • 394
  • 1
  • 4
  • 2
    How does this accomplish what I want? `cp foo.pdf foo.pdf` complains that these "are the same file". `cp --remove-desintation foo.pdf foo.pdf` removes the file before it can be copied, giving "`No such file or directory`". – nibot Nov 15 '12 at 10:59
  • You should edit your answer to indicate that it's not what OP wants. – Uri Sep 28 '15 at 15:06
2

Many have already stated the following solution:

cp --remove-destination `readlink file` file

However, this will not work on symbolically linked directories.

Adding a recursive and force will not work either:

cp -rf --remove-destination `readlink file` file

cp: cannot copy a directory, ‘path/file, into itself, ‘file’

Therefore, it is probably safer to delete the symlink entirely first:

resolve-symbolic-link() {
  if [ -L $1 ]; then
      temp="$(readlink "$1")";
      rm -rf "$1";
      cp -rf "$temp" "$1";
  fi
}

Not exactly a one-liner like you had hoped, but put this it into your shell environment, and it can be.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
1

Another approach if you find yourself doing this often is adapting kbrock's answer into a shell script and putting it into /usr/bin/. Something along the lines of:

if [ $# -ne 1 ] 
then
  echo "Usage: $0 filename"
  exit 1
fi
[ -L "$1" ] && cp --remove-destination "$(readlink "$1")" "$1"

chmod +x it, and then just run it as a normal command.

sidgate
  • 14,650
  • 11
  • 68
  • 119
BANZ1111
  • 11
  • 2
1

Change all file.txt from link into file.

for f in *.txt; do echo $(readlink $f) $f | awk '{print "rsync -L "$1" "$2}'; done | bash
1

I tried to synthesize everything above into a total solution. I do a "deep de-link", so there's an extra call to find even when it's not needed. Someone may want to add a --deep option to prevent the double-find, which in the case of large dir trees, may be very expensive. (Sorry for all the 1-liners)

#!/bin/bash
[[ -z $1 ]] && target="." || target=$1

last_wc=0
links=$(/bin/find $target -type l)
rc=$? && [[ $rc -ne 0 ]] && exit $rc

while true; do
    wc=$(echo $links | /bin/wc -w)
    [[ $wc -eq $last_wc ]] && break
    for f in $links; do
        link_target=$(/bin/readlink --canonicalize-existing --no-newline --verbose "$f")
        [[ $? -ne 0 ]] && continue

        if [[ -d "$link_target" ]]; then
            cmd="/bin/rm -f '$f' && /bin/cp -r '$link_target' '$f'"
        else
            cmd="/bin/rm -f '$f' && /bin/cp '$link_target' '$f'"
        fi
        eval $cmd
        [[ $? -ne 0 ]] && echo "WARNING: Problem running '$cmd'" >&2
    done
    last_wc=$wc
    links=$(/bin/find $target -type l)
done
Myles Prather
  • 171
  • 1
  • 12
0

Rsync can nativly deference the symlink for you using -L flag.

[user@workstation ~]$ rsync -h | grep -- -L
 -L, --copy-links            transform symlink into referent file/dir

It would be as simple as: rsync -L <symlink> <destination>

0

This worked perfectly for me (edited to work with spaces):

find . -type l | while read f; do /bin/cp -rf --remove-destination -f $(find . -name $(readlink "${f}")) "${f}";done;

Allows you to recursively convert all symlinks under the current working folder to its regular file. Also doesn't ask you to overwrite. the "/bin/cp" exists so that you can bypass a possible cp -i alias on your OS which prevents the "-rf" from working.

Freddie
  • 908
  • 1
  • 12
  • 24
  • 2
    Using for f in $(find) will break if there are spaces in the filename, and is potentially dangerous: [http://mywiki.wooledge.org/BashPitfalls#pf1]. Instead you should us find with the exec command. $ find . -type l -exec bash -c '...' _ {} \; – SamGoody Jun 15 '16 at 10:22
  • `find . -type l -exec bash -c 'echo mv \"$(readlink {})\" \"{}\"' \;` and scrutinize the output. If ok, append `| bash`. – Hannes Dec 29 '20 at 19:16
0

in case of "relative" symlinks you could add a awk statement to @jared answer:

for f in $(find . -type l); do /bin/cp -rf --remove-destination -f $(find . \! -type l -name $(readlink $f | awk -F"../" '{print $NF}')) $f;done;

If you have symlinks pointing to directories you need to use a more radical approach because cp --remove-destination does not work together with symlinks directories correctly

for f in `find . -type l`; do (cd `dirname $f`; target=`basename $f`; source=`readlink $target` ; rm -rf $target && cp -r $source $target); done

For sure this could be written with less overhead. but it is quite good to read in my opinion.

ttwhy
  • 61
  • 4
0

The most direct way, if both the source and target of the link are on the same partition, is to convert soft-links to a hard-links (I assume the current links are soft). And it's done.

This effectively accomplishes the 'copying'. If you check the disk usage size (du) of a directory of all soft symlinks, then perform the hard-link conversion (below), and check again, the 'after' du size reflects the actual original file sizes. Then you can rm the source of the link at any time without effecting the target of the hardlink.

find . -type l -exec bash -c 'ln -f "$(readlink -m "$0")" "$0"' {} \;
Rondo
  • 3,458
  • 28
  • 26
0

There's no single command. But if you don't want a copy and all you want is another reference, you can replace the symlink with a link (aka "hard link"). This only works if they're on the same partition, BTW.

rm filename
ln target filename
Brian Cain
  • 14,403
  • 3
  • 50
  • 88