Does `ln -sf` overwrite existing files which are only symbolic links

lnsymlink

From the coreutils ln manual:

Normally ln does not remove existing files. Use the –force (-f) option to remove them
unconditionally, the –interactive (-i) option to remove them conditionally,
and the –backup (-b) option to rename them.

$ mkdir output

I can understand this failing:

$ ln -sT /etc/passwd output
ln: failed to create symbolic link ‘output’: File exists

But why does adding -f also fail:

$ ln -sfT /etc/passwd output
ln: ‘output’: cannot overwrite directory

Does -f overwrite existing files which are only symbolic links, but not files of other types (directories, regular files, …)?

Can -T be used when the last argument, (i.e. the target file argument), is an existing directory, with the intention to overwrite the directory into a link?

Best Answer

It can remove files, but directories are not "files".

➜  lab touch file        
➜  lab mkdir dir
➜  lab ln -sfT /home file
➜  lab ln -sfT /home dir 
ln: dir: cannot overwrite directory

This is seen in the source:

  if (remove_existing_files || interactive || backup_type != no_backups)
    {
      dest_lstat_ok = (lstat (dest, &dest_stats) == 0);
      if (!dest_lstat_ok && errno != ENOENT)
        {
          error (0, errno, _("failed to access %s"), quoteaf (dest));
          return false;
        }
    }
[...]
  if (dest_lstat_ok)
    {
      if (S_ISDIR (dest_stats.st_mode))
        {
          error (0, 0, _("%s: cannot overwrite directory"), quotef (dest));
          return false;
        }
      if (interactive)
        {
          fprintf (stderr, _("%s: replace %s? "), program_name, quoteaf (dest));
          if (!yesno ())
            return true;
          remove_existing_files = true;
        }

dest_lstat_ok boolean which starts as false becomes true, the first if statement is called since remove_existing_files is true due the --force flag, which in turn allows the second if statement to be checked. It refuses to remove directories because it's expecting a file.

If you don't set the -T which makes ln to not treat the directory as not a directory, ln would just created a symlink under the directory with the basename of the source.

Related Question