22

I want to put a command into a shell script which will create a symlink to directory, but this script could be run over and over, so on subsequent invocations, the command should not change anything.

Here is the directory structure:

% tree /tmp/test_symlink 
/tmp/test_symlink
├── foo
└── repo
    └── resources
        └── snippets
            ├── php.snippets
            ├── sh.snippets
            ├── snippets.snippets
            ├── sql.snippets
            └── vim.snippets

I want to create a symlink in foo/ called snippets which points to the directory /tmp/test_symlink/repo/resources/snippets.
So I run:

% ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/snippets
'/tmp/test_symlink/foo/snippets' -> '/tmp/test_symlink/repo/resources/snippets'

which gives the desired result.

% tree /tmp/test_symlink                                                          
/tmp/test_symlink
├── foo
│   └── snippets -> /tmp/test_symlink/repo/resources/snippets
└── repo
    └── resources
        └── snippets
            ├── php.snippets
            ├── sh.snippets
            ├── snippets.snippets
            ├── sql.snippets
            └── vim.snippets

5 directories, 5 files

However, when the command is run again,

% ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/snippets
'/tmp/test_symlink/foo/snippets/snippets' -> '/tmp/test_symlink/repo/resources/snippets'

it creates a symlink to a directory, where the symlink aleady exists puts symlink inside the real directory

% tree /tmp/test_symlink                                                          
/tmp/test_symlink
├── foo
│   └── snippets -> /tmp/test_symlink/repo/resources/snippets
└── repo
    └── resources
        └── snippets
            ├── php.snippets
            ├── sh.snippets
            ├── snippets -> /tmp/test_symlink/repo/resources/snippets
            ├── snippets.snippets
            ├── sql.snippets
            └── vim.snippets

why is this happening and how can I modify the command so that subsequent invocations won't create this weird effect?

2 Answers 2

21

You should use the -T option for this, it tells ln to always treat the link name as the desired link name, never as a directory.

Without this option, if you give ln a link name which exists as a directory, it creates a new link to the target inside that directory.

Note that -T is a GNU-ism (at least, it’s not in POSIX), but you’re already using -v which is a GNU-ism too so I imagine that’s not an issue.

Alternatively, you can just specify the parent directory as the link name, and the link will always be (re-)created there:

ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/

This works because your symlink has the same name as the target.

6
  • thanks, yes the gnu options are not necessarily portable, but they can be really useful - when portability is not a requirement. your answer works for me. From the man page -n, --no-dereference treat LINK_NAME as a normal file if it is a symbolic link to a directory. -T, --no-target-directory treat LINK_NAME as a normal file always do you think it is better to treat a symlink as a file always? I would have thought it would be better to limit the use of these "special" options? Apr 1, 2017 at 7:31
  • If you know they’re available, there’s no reason to avoid GNU-specific options (IMO). You asked for an idempotent command, -T is how you make ln idempotent. There are other ways of getting the same result if you prefer, such as deleting the link and re-creating it, or if you know foo exists already, ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/ (actually that might be a better answer altogether, I’ll update). Apr 1, 2017 at 7:46
  • 1
    cool, yes I had previously been using if statements to detect the presence of the symlink and skip the command if the symlink existed, but the scripts end up getting cluttered and hard to read, I was going for something idempotent but simple, like using mkdir -p no tests, no logic, just good ol one liners :) Apr 1, 2017 at 7:52
  • @Stephen Kitt: you mentioned the use of -T, but I couldn't find it in the code snippet of your answer. Am I misunderstanding or missing something?
    – Guizmoo03
    Apr 24, 2019 at 13:26
  • @Guizmoo03 perhaps you missed the “Alternatively” which introduces the snippet. The first three paragraphs explain -T, but the end of my answer presents another solution which doesn’t use -T. Apr 24, 2019 at 13:28
-1

Best I can tell what is happening here is that on the second pass the ln command behaves like cp or mv, which is that if it sees a "directory" at <destination> it will put the file inside it, else if <destination> doesn't exist, it will make <destination>. (which is what the "slashdot" trick avoids - i.e. it will make sure the <destination> exists and is a directory).
But I could be wrong.

In either case this seems to work

ln -sfv --no-dereference /tmp/test_symlink/repo/resources/snippets/ foo/snippets

You must log in to answer this question.

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