Filehandles, files, directories
symlink#
Create a symbolic link at NEWFILE that points at the string OLDFILE.
symlink is the thin wrapper around the POSIX symlink(2) system
call. It does not copy anything, does not read OLDFILE, and does
not require OLDFILE to exist. It writes a new directory entry at
NEWFILE whose payload is the literal bytes of OLDFILE, and the
kernel later resolves that payload whenever something opens, stats,
or traverses the link.
Synopsis#
symlink OLDFILE, NEWFILE
What you get back#
1 on success, 0 on failure (with $! set). On systems
that do not support symbolic links at all, symlink raises an
exception rather than returning 0. To probe for support at runtime
without dying, wrap a harmless call in eval:
my $symlink_exists = eval { symlink("", ""); 1 };
The eval pattern is the idiomatic capability check — it runs a
no-op symlink whose only job is to fail cheaply on platforms where
the call exists, and to throw on platforms where it does not.
How symlinks differ from hard links#
A symlink and a hard link (link) look similar from the shell
but behave very differently:
linkcreates a second directory entry pointing at the same inode. Both names are equal; deleting either one leaves the file alive under the other.symlinkcreates a tiny file of typelnkwhose contents are a pathname. The target is resolved by name at every lookup. The two names are not equal — the symlink has its own inode, its own permissions bits (mostly ignored), and its own timestamps.
Practical consequences:
OLDFILEis stored verbatim. It is not canonicalised, not resolved against the current working directory, and not checked for existence. A symlink can be created pointing at a path that does not exist yet (a dangling symlink) and will start working the moment the target appears.Because
OLDFILEis just a string, it can be relative. Relative symlink targets are resolved at lookup time relative to the directory containingNEWFILE, not the process’s current working directory. This is the single biggest source of surprise.Symlinks can cross filesystem boundaries. Hard links cannot.
Symlinks can point at directories. Hard links to directories are forbidden on every mainstream Unix.
unlinkon a symlink removes the link, not the target. To remove the target, unlink the target’s path directly.
Examples#
Create a symlink to an existing file:
symlink "/etc/hostname", "hostname.lnk"
or die "symlink failed: $!";
Dangling symlink — the target does not exist yet, and that is fine:
symlink "/will/appear/later", "pending.lnk"
or die "symlink failed: $!";
# opening pending.lnk now fails with ENOENT;
# it will start working once /will/appear/later exists.
Relative target resolved against the link’s directory, not the cwd:
chdir "/var/log" or die $!;
symlink "syslog", "/tmp/current.lnk"
or die "symlink failed: $!";
# readlink("/tmp/current.lnk") eq "syslog"
# opening /tmp/current.lnk resolves syslog in /tmp, not /var/log.
Symlink to a directory — this works, unlike link:
symlink "/var/log", "logs"
or die "symlink failed: $!";
opendir my $dh, "logs" or die $!; # traverses the symlink
Inspect and remove a symlink without touching its target:
my $target = readlink "hostname.lnk"; # the stored OLDFILE string
unlink "hostname.lnk" or die $!; # removes the link only
# /etc/hostname is untouched.
Atomic-swap pattern — replace a symlink pointing at the “current” release directory by writing a new symlink and renaming over it:
symlink "release-2", "current.new" or die $!;
rename "current.new", "current" or die $!;
# rename over an existing symlink is atomic on POSIX filesystems.
Edge cases#
OLDFILEis stored verbatim. No normalisation, no resolution, no existence check.symlink "", "foo"succeeds on most systems and creates an empty-target symlink that every lookup rejects withENOENT. This is why the capability-probe idiom uses("", "")— a cheap call that always fails on supporting platforms.Relative
OLDFILEis resolved relative to the directory ofNEWFILE, not the current working directory. If you buildNEWFILEfrom a relative path, think carefully about what directory the kernel will see it in.NEWFILEalready exists → failure with$!set toEEXIST. There is no “replace” flag; to overwrite, write to a fresh name andrenameover the old one (atomic).Permissions on the symlink itself are almost always ignored. The kernel enforces the permissions of the target. On most filesystems the symlink is reported as
lrwxrwxrwxregardless of whatchmodis called on it.statfollows symlinks,lstatdoes not. To examine the link itself — its type, its link count, the length of its target string — uselstat. To examine what the link points at, usestat.unlinkremoves the link, not the target. There is no way to delete the target through the symlink except by resolving it first.Platforms without symlink support raise an exception on any call, even one that would otherwise fail. Use the
evalprobe shown above rather than assuming a return value.Filesystems without symlink support (some FAT variants, certain network mounts) fail with
EPERMorEOPNOTSUPPeven on a kernel that implementssymlink(2). Check$!on failure rather than assuming the call was rejected for the obvious reason.Symlink loops are not detected at creation time.
symlink "a", "b"followed bysymlink "b", "a"succeeds; the loop only surfaces when something tries to resolve either name, which fails withELOOPafter the kernel hits its chase limit (typically 40).
Differences from upstream#
Fully compatible with upstream Perl 5.42.
See also#
link— create a hard link (same inode, same filesystem, file only); the strict counterpart tosymlinkreadlink— read theOLDFILEstring stored in a symlink without resolving itlstat— stat the symlink itself rather than its target; the only way to see that a path is a symlink from Perlstat— stat what the symlink points at, following the chain of linksunlink— remove a symlink (the link, not the target); also the way to remove a dangling symlink$!—errnoset on failure; inspect it to distinguishEEXIST,EPERM,EOPNOTSUPP,ENOENTon the parent directory