views:

567

answers:

2

I would like to change the target of symbolic link from within a bash script. The problem is that the symlink is quite important (it's /bin/sh, namely) and I would to do it in fashion that:

  1. New target will be available immediately after removing old, i.e. there will be no possibility that something will notice disappearing of it,
  2. There will be no possibility that the change will fail in the middle, i.e. leaving user with symlink removed and no new one.

I thought about two methods. Either using plain ln:

ln -fs /bin/bash /bin/sh

or using mv:

ln -s /bin/bash /bin/sh.new
mv /bin/sh.new /bin/sh

Which one will suit my needs better? Is there any possibility that one of them would try to replace the symlink target instead of symlink itself?

+1  A: 

Renaming (mv) is an atomic operation; creating a new symlink is not (delete old symlink; create new one). So you should use mv:

$ ln -s new current_tmp && mv -Tf current_tmp current

Here's a blog post discussing this. Also, if you're worried about what will happen, why not try it on a non-critical symlink first?

ire_and_curses
You can't be sure there will be no potential problems if you just try it :). It is very hard to make a test for "something <will not> notice disappearing <ever>".
Eugene
About the blog post — `-T` doesn't seem to be a portable option.
Michał Górny
A: 

It looks like (from the man page) ln -f unlinks the symlink before making the new one, which means mv is the better option for this.

I would, however, strongly recommend against linking /bin/sh to bash. Many scripts use:

#!/bin/sh

and are written assuming that the shell is the classic Bourne shell. If this were to run bash instead, you could easily get obscure incompatibilities between what the script assumes sh does and what bash actually does. These will be nearly impossible to track down.

Greg Hewgill
Except of course for the fact that on many systems, /bin/sh *is* a symlink to /bin/bash; and when invoked through it, bash will enter sh compatibility mode.
Williham Totland
bash cannot be sh compatible. Try this in a real sh and in bash: i=0;ls|while read a ; do i=expr $i + 1 ; done ; echo $i If you do not have a real sh, try with ksh Note that the above result even differs among sh versions. On solaris the extremely old sh prints 0, whereas the xpg4 version gives non zero (like ksh)
Gunstick