14

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?

15

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.

  • 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? – the_velour_fog Apr 1 '17 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). – Stephen Kitt Apr 1 '17 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 :) – the_velour_fog Apr 1 '17 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 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. – Stephen Kitt Apr 24 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.