Terminal Primer – Part 6 – Symbolic Links

I will be posting drafts of parts of my next book on macOS Terminal and bash. Let me know how you like them or if you think something important is wrong or missing. You can give feedback in the comments, over Twitter or in the MacAdmins forum (also scriptingosx). Thank you for your interest and feedback!

Symbolic Links

When you get a detailed list of the file system root / you will see a few interesting entries: (output abbreviated)

$ ls -l /
lrwxr-xr-x@    1 root  wheel    etc -> private/etc
lrwxr-xr-x@    1 root  wheel    tmp -> private/tmp
lrwxr-xr-x@    1 root  wheel    var -> private/var

These are symbolic link files. Symbolic links are identified by the first character l on a long ls. Symbolic links are redirections to other files or directories. /etc, /tmp and /var are standard directories for certain kinds of files in Unix systems, but in this case these directories are actually located in /private.

Note: The reason for this is historical and goes back to NeXTStep. This setup could allow the /private folder to be mounted from another disk or file share separate from the rest of the system. This is not used in macOS anymore, but the directory structure remains.

Aside from the long ls you can use the readlink command to determine where a symbolic links points to:

$ readlink /etc
private/etc

or the stat -l command:

$ stat -l /etc
lrwxr-xr-x 1 root wheel 11 Nov 17 07:50:53 2016 /etc -> private/etc

A symbolic link contains a path pointing to the original file or directory. Most operations such as reading or changing are directed to the original, rather than the symbolic link. ls /tmp and ls /private/tmp will show you both the contents of the original /private/tmp.

An example for a symbolic link to a file is the file /usr/share/dict/words (a list of english words, which can be surprisingly useful to have around) which points to a file web2 in the same directory. Symbolic links can be used as a means to ‘switch’ between files without having to change around filenames and configuration files.

When you read the file /usr/share/dict/words the read command will be redirected to /usr/share/dict/web2:

$ cat /usr/share/dict/words | wc 
  235886  235886 2493109
$ cat /usr/share/dict/web2 | wc 
  235886  235886 2493109

Note: the wc command counts words, lines and bytes in a file.

Symbolic links can be relative or absolute. However, most of the time they are relative, since you do not want them pointing to different files, depending on which volumes the system is booted from. Relative paths are resolved relative to the link itself, not the current working directory. The above link for /etc points to the relative path private/etc so to the sub-directory etc in the directory private in the same location as the symbolic link itself.

To create a symbolic link use the ln -s command.

$ mkdir directory
$ touch directory/file
$ ln -s directory/file
$ readlink file
directory/file

This series of commands will create a directory, and en empty file in directory and then a link to that file in the current working directory. When you want the symbolic link to have a different name, you can give that as a second argument:

$ ln -s directory/file second_link
$ readlink second_link 
directory/file

The first argument is not really the path to a file or directory, but the path the symbolic link points to. When this path is relative it will be resolved relative to the location of the symbolic link. So if you wrote:

$ ln -s directory/file directory/link_to_file

A symbolic link named link_to_file will be created in directory but it will point to directory/directory/file instead of the file next to it. When you try to read from the symbolic link pointing to nowhere, you will get an error:

$ cat directory/link_to_file 
cat: directory/link_to_file: No such file or directory

The reference you pass when creating the symbolic link has to be relative to the where the symbolic link is created. The correct command would have been:

$ ln -s file directory/link_to_file

When the second argument is a directory itself, a link named the same as the file it refers to will be created:

$ mkdir another_dir
$ ln -s ../directory/file another_dir

will create a link named file pointing to ../directory/file. Since you have to give the path to the target relative to the where the link is created you have to add the ../ to ‘go up a level’ out of the another_dir directory and then back into directory.

Note: you can create a symbolic link that points to non-exiting path. Also when the original file or directory gets deleted, a ‘dangling’ symbolic link will remain.

When you use rm or mv on a symbolic link, only the link will be affected, not the original item.

When you run cp on a symbolic link to a file, the contents of the original will be copied:

$ cp another_dir/file filecopy
 $ stat -l filecopy
-rw-r--r-- 1 armin staff 0 Sep  4 14:51:44 2017 filecopy

However, when you recursively copy a directory tree containing symbolic links, they will be copied as symbolic links:

$ cp -R another_dir/ copy_dir
$ ls -l copy_dir/
total 8
lrw-r--r--  1 armin  staff  17 Sep  4 14:52 file -> ../directory/file

Usually the destination of a copy will be in a vastly different location and this will break the links:

$ cp -R another_dir/ /Users/Shared/copy_dir
$ ls -l /Users/Shared/copy_dir
total 8
lrwxr-xr-x  1 armin  wheel  17 Sep  4 15:19 file -> ../directory/file
$ cat /Users/Shared/copy_dir/file 
cat: /Users/Shared/copy_dir/file: No such file or directory

You can use cp‘s -L option to make the copy process resolve symbolic links and copy their contents instead:

$ cp -RL another_dir/ /Users/Shared/another_dir
$ ls -l /Users/Shared/another_dir
total 8
-rwxr-xr-x  1 armin  wheel  17 Sep  4 15:19 file

Other commands will have similar options to control the behavior when encountering symbolic links. However, their names are not standardized in anyway and you will have to consult the man page to find out the details.

Symbolic Links vs Finder Aliases

In macOS Finder, you can create aliases with the menu item ‘Make Alias’ from the ‘File’ or context menu. Finder Aliases have much the same role as symbolic links, but a few significant differences in behavior:

  • the shell and most command line tools cannot resolve Finder Aliases and treat them as files
  • Finder Aliases will ‘follow’ the original when it is moved or renamed.
  • However, when the original is deleted and replaced by an item of the same name, a Finder Alias will resolve to the new item.
  • When a Finder alias points to an item on a file share, double clicking the alias in Finder will attempt to connect to the file share, if it not already connected.

Finder will display Aliases and symbolic links with a small arrow in the corner of the icon. Both symbolic links and Finder Aliases have a ‘Show Original’ menu item in the ‘File’ menu or context menu.

There are no commands to create or resolve Finder aliases in Terminal, but you can use AppleScript with the osascript command:

$ osascript -e 'tell app "Finder" to make new alias to posix file "/Users/armin/Documents" at posix file "/Users/armin/Desktop"'

This will create a new Finder alias to ‘Documents’ in the ‘Desktop’ folder. The user who is running this command has to be logged in to the Mac, so that osascript can connect to the Finder to run this command.

To find out the original of a Finder alias, you can use:

$ osascript -e 'tell application "Finder" to get POSIX path of ( (original item of (POSIX file "/Users/armin/Desktop/Documents" as alias) ) as alias)'

Note: there is a lot of type casting (as alias) in this command. To further confuse matters the data type alias in AppleScript is not the same as a Finder alias. It is a special data type in AppleScript that references a file or folder.

One thought on “Terminal Primer – Part 6 – Symbolic Links”

  1. Good article! I assume the target of the article is folks that are not familiar with the CLI environment. Perhaps it would be useful for some to know what symlinks might be used for. –Craig

Leave a Reply

Your email address will not be published. Required fields are marked *