ln — Create a hard link or symbolic link to a file or directory across all 5 shells
Equivalents in every shell
ln -s target linkname`ln source target` creates a HARD LINK (same inode, file content shared, both pointers must be on the same filesystem). `ln -s source target` creates a SYMBOLIC link (a pointer file containing the source path, can cross filesystems, can point to dirs). `-f` overwrites an existing target; `-r` (`--relative`, GNU only) makes a relative symlink from absolute paths.
ln -s target linknameSame external `/bin/ln`. macOS BSD `ln` lacks GNU's `-r` flag — for relative symlinks compute the path manually or `brew install coreutils` and use `gln -sr`. macOS extended attribute `com.apple.quarantine` is INHERITED through symlinks but NOT through hardlinks.
ln -s target linknameSame external. Fish has no built-in linker. The argument order is a frequent confusion: `ln -s SOURCE LINK_NAME` — i.e., the link gets created with the second name, pointing at the first. Mnemonic: same order as `cp source destination`.
New-Item -ItemType SymbolicLink -Path linkname -Target targetPowerShell-native (PS 5.0+). `-ItemType SymbolicLink` creates a symlink; `-ItemType HardLink` creates a hard link (FILES only — Windows hard links cannot point to directories). On Windows < 10 build 14972, creating symlinks requires ADMIN privileges; from 14972+ enable Developer Mode for non-admin symlink creation.
mklink linkname targetBuilt-in (Vista+). `mklink linkname target` creates a SYMBOLIC link to a file; `mklink /D linkname target` for a directory symlink; `mklink /H linkname target` for a hard link (files only); `mklink /J linkname target` for a JUNCTION (similar to dir-symlink but locally only, since NTFS Vista). Note argument order is REVERSED from Unix `ln`: `mklink LINK TARGET` (Windows) vs `ln -s TARGET LINK` (Unix).
Worked examples
Create a symbolic link to a file
ln -s /opt/app/bin/myapp /usr/local/bin/myappNew-Item -ItemType SymbolicLink -Path C:\bin\myapp.exe -Target C:\opt\app\bin\myapp.exemklink C:\bin\myapp.exe C:\opt\app\bin\myapp.exeCreate a hard link to a file
ln original.txt copy.txtNew-Item -ItemType HardLink -Path copy.txt -Target original.txtmklink /H copy.txt original.txtCreate a symbolic link to a directory
ln -s /var/log logsNew-Item -ItemType SymbolicLink -Path .\logs -Target C:\Users\me\AppData\Local\App\Logsmklink /D logs C:\Users\me\AppData\Local\App\LogsGotchas
- cmd `mklink` argument order is REVERSED from Unix `ln`. `mklink LINK TARGET` makes the link with name LINK pointing at TARGET; Unix `ln -s TARGET LINK` makes the link with name LINK pointing at TARGET. So the names go in opposite positions. This is the single most common Windows-Unix porting mistake on links.
- Creating SYMBOLIC links on Windows historically required SeCreateSymbolicLink privilege (admin). Starting Windows 10 build 14972 (Creators Update), enabling DEVELOPER MODE in Settings unlocks non-admin symlink creation. For CI / CD this matters: GitHub Actions Windows runners run as admin and can always make symlinks; local non-admin dev boxes may need Developer Mode enabled.
- NTFS Junctions (`mklink /J`) and Symlinks (`mklink /D`) BOTH point at directories but are NOT the same. Junctions are an older mechanism, local-only (cannot point to UNC paths), and have different security semantics — non-admins can create junctions on Vista+ without Developer Mode. Symlinks are POSIX-compatible. Prefer `/D` symlinks for new code.
- Hard links on Windows can ONLY point at files, not directories — same as Linux. Hard links also cannot CROSS volumes (drive letters) — `mklink /H D:\copy.txt C:\orig.txt` ERRORS. Symlinks can cross volumes. On Linux, hardlinks also cannot cross filesystems (mountpoints).
- A "broken symlink" (target deleted or never existed) is silently valid — `ls -l` shows it red, but `ls -l` alone might miss it. Use `test -e linkname` (true if target exists) or `test -L linkname` (true if it's a symlink, target or not). PowerShell `(Get-Item linkname).Target` returns the link target string; `Test-Path -PathType Leaf linkname` returns true only if the target exists.