17

It seems like it should be simple to symlink one file to a new file in a subdirectory.... ....without moving subdirectories. But something about the syntax is perplexing and counter to what I would expect. Here's a test case:

mkdir temp
cd temp
mkdir deploy
echo "Contents of the build file!" > deploy/resources.build.php
ln -s deploy/resources.build.php deploy/resources.php
cat deploy/resources.php #bad symlink

This just creates a broken symlink! I am running this in a build environment setup script, so I want to avoid changing the current working directory if at all possible.

ln -s deploy/resources.build.php resources.php
cat deploy/resources.php

Also doesn't work because it creates the symlink in the temp directory instead of the deploy subdirectory.

cd deploy
ln -s resources.build.php resources.php
cd ..

This works, but I'd prefer to know how to do it without changing directories.

Using a full path like:

/home/whatever/src/project/temp/stuff/temp/deploy/resources.build.php

Works, but is unweildy and somewhat impractical, especially in a build environment where all the project stuff might be different between builds, and the like.

How can I create a symlink between two files in a subdirectory, without moving into that subdirectory and out of it, and while giving the new file "alias" a new name?

4 Answers 4

14

But something about the syntax is perplexing and counter to what I would expect.

The arguments for ln, in the form that you're using it, are:

ln [OPTION]... [-T] TARGET LINK_NAME (1st form)

The perplexing, unintuitive thing is that when you're creating a symlink, the target argument for ln isn't expected to be a path to a file, but rather the contents of the symlink to be created. If you think about it for a moment, it's obvious that it has to be that way. Consider:

$ echo foo >foo
$ ln -s foo bar1
$ ln -s $PWD/foo bar2
$ cat bar1
foo
$ cat bar2
foo
$ ls -l bar1 bar2
lrwxrwxrwx 1 matt matt  3 Dec 29 16:29 bar1 -> foo
lrwxrwxrwx 1 matt matt 29 Dec 29 16:29 bar2 -> /home/matt/testdir/foo

In that example I create 2 symlinks, named "bar1" and "bar2", that point to the same file. ls shows that the symlinks themselves have different contents, though - one contains an absolute path, and one contains a relative path. Because of this, one would continue working even if it were moved to another directory, and the other wouldn't:

$ mv bar2 /tmp
$ cat /tmp/bar2
foo
$ mv bar1 /tmp
$ cat /tmp/bar1
cat: /tmp/bar1: No such file or directory

So, considering that we must be able to make both relative and absolute symlinks, and even to create broken symlinks that will become un-broken if the target file is later created, the target argument has to be interpreted as freeform text, rather than the path to an already-existing file.

If you want to create a file named deploy/resources.php that links to deploy/resources.build.php, you need to decide if you want to create an absolute symlink (which is resilient against the symlink being moved, but breaks if the target is moved), or a relative symlink (which will keep working as long as both the symlink and the target are moved together and maintain the same relative paths).

To create an absolute symlink, you could do:

$ ln -s $PWD/deploy/resources.build.php deploy/resources.php

To create a relative one, you would first figure out the relative path from the source to the target. In this case, since the source and target are in the same directory relative to one another, you can just do:

$ ln -s resources.build.php deploy/resources.php

If they weren't in the same directory, you would need to instead do something like:

$ ln -s ../foo/f bar/b

In that case, even though foo and bar are both in your current directory, you need to include a ../ into the ln target because it describes how to find f from the directory containing b.

That's an extremely long explanation, but hopefully it helps you to understand the ln syntax a little better.

0
4

You can create the link in a subshell, as follows:

  (cd deploy && ln -s resources.build.php resources.php && cat resources.php)

When the subshell ends execution, you will find yourself still in the correct directory.

Alternatively, you may try

 ln -s resources.build.php deploy/resources.php

which also works, neglecting to include in the CL the fact that the file resources.build.php is not in the directory where you are issuing the command, but is in fact inside ./deploy.

1
  • This is black magic, sir!
    – Kzqai
    Dec 30, 2014 at 1:18
2

This confused me till I realized a symlink is basically a config file. i.e. how would I write that path data into a simple text file:

ln -s [target] [link name]

becomes:

echo [target] > [link name]

The error that I (and probably OP) was making is in thinking ln needs to know about the file it is targeting. ln doesn't care. Its just writing some path information into a file. This is a perfectly reasonable ln command:

ln -s /path/doesnt/exist/file.err
ll
file.err -> /path/doesnt/exist/file.err

Hence:

ln -s deploy/resources.build.php deploy/resources.php

produces a simlink file called resources.php in the ./deploy folder that is referencing the file resources.build.php in the folder ./deploy/deploy/.

That is unlikely what you want and gives a bad (broken) link. There's nothing wrong with the link, if you put that file there then the link works. However, (as pointed out by others) what I and the OP wanted was:

ln -s resources.build.php deploy/resources.php
0

I came across this issue recently. Keep in mind that source is a kind of descriptor path for the target.

❯ ln -s source target

That’s why you might need to use an absolute path in source or change current directory to the final target directory to give the source (AKA descriptor) a sense as it appears.

Example: Linking the source a to target b.

root
  ├── foo
  │    └── a
  └── bar
❯ pwd
root
❯ ln -s ../foo/a ./bar/b

In this example, the description for the file b is ../foo/a. So, as I'm the file b, I should:

  1. Go one directory up from bar.
  2. Change to foo.
  3. Find a.
root
  ├── foo
  │    └── a
  └── bar
       └── b -> ../foo/a

You must log in to answer this question.

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