97

When I try to create a symbolic link from the Git Bash shell, it fails every time all the time:

ln -s /c/Users/bzisad0/Work testlink

Output:

ln: creating symbolic link `testlink' to `/c/Users/bzisad0/Work': Permission denied

The only thing it does, besides giving the error message, is create an empty directory named (in this case) testlink.

I don't see any problem with the ln executable. For instance, it is owned by me and marked as executable:

which ln
ls -hal /bin/ln

Output:

/bin/ln

-rwxr-xr-x    1 BZISAD0  Administ      71k Sep  5 11:55 /bin/ln

I also own the current directory (~, which is /c/Users/bzisad0):

ls -dhal .

Output:

drwxr-xr-x  115 BZISAD0  Administ      40k Sep  5 12:23 .

I have administrative rights, and I've tried opening the Git Bash shell with "Run as Administrator", but that makes no difference.

I've tried opening the Windows properties for ln.exe and setting the Privilege Level to "Run this program as an administrator" but that doesn't help.

I've gone into the SecurityAdvanced properties in Windows and made myself (rather than the Administrators group) the owner, but that doesn't fix anything either.

I'm at a loss. I don't know whether this error message is ultimately coming from ln, from Bash, or from Windows, or how I could possibly lack the permission. How can I get to the bottom of this?

iconoclast
  • 19,365
  • 11
  • 98
  • 131

10 Answers10

78

It is possible, albeit extremely awkward, to create a symlink in MSYSGIT.

First, we need to make sure we are on Windows. Here's an example function to check that:

windows() { [[ -n "$WINDIR" ]]; }

Now, we can't do cmd /C, because MSYSGIT will fornicate with this argument and turn it into C:. Also, don't be tempted to use /K, it only works if you don't have a K: drive.

So while it will replace this value on program arguments, it won't on heredocs. We can use this to our advantage:

if windows; then
    cmd <<< "mklink /D \"${link%/}\" \"${target%/}\"" > /dev/null
else
    ln -s "$target" "$link"
fi

Also: note that I included /D because I'm interested in directory symlinks only; Windows has that distinction. With plenty of effort, you could write a ln() { ... } function that wraps the Windows API and serves as a complete drop-in solution, but that's... left as an exercise for the reader.


Edit: As a thank-you for the accepted answer, here's a more comprehensive function.

# We still need this.
windows() { [[ -n "$WINDIR" ]]; }

# Cross-platform symlink function. With one parameter, it will check
# whether the parameter is a symlink. With two parameters, it will create
# a symlink to a file or directory, with syntax: link $linkname $target
link() {
    if [[ -z "$2" ]]; then
        # Link-checking mode.
        if windows; then
            fsutil reparsepoint query "$1" > /dev/null
        else
            [[ -h "$1" ]]
        fi
    else
        # Link-creation mode.
        if windows; then
            # Windows needs to be told if it's a directory or not. Infer that.
            # Also: note that we convert `/` to `\`. In this case it's necessary.
            if [[ -d "$2" ]]; then
                cmd <<< "mklink /D \"$1\" \"${2//\//\\}\"" > /dev/null
            else
                cmd <<< "mklink \"$1\" \"${2//\//\\}\"" > /dev/null
            fi
        else
            # You know what? I think ln's parameters are backwards.
            ln -s "$2" "$1"
        fi
    fi
}

Also note a few things:

  1. I just wrote this and briefly tested it on Win7 and Ubuntu, give it a try first if you're from 2015 and using Windows 9.
  2. NTFS has reparse points and junction points. I chose reparse points because it's more of an actual symlink and works for files or directories, but junction points would have the benefit of being an usable solution in XP, except it's just for directories.
  3. Some filesystems, the FAT ones in particular, do not support symlinks. Modern Windows versions do not support booting from them anymore, but Windows and Linux can mount them.

Bonus function: remove a link.

# Remove a link, cross-platform.
rmlink() {
    if windows; then
        # Again, Windows needs to be told if it's a file or directory.
        if [[ -d "$1" ]]; then
            rmdir "$1";
        else
            rm "$1"
        fi
    else
        rm "$1"
    fi
}
Camilo Martin
  • 35,841
  • 20
  • 107
  • 152
  • 6
    The parameters for `ln` aren't really backwards since it supports multiple parameters, you can just list a bunch of files and a directory as a destination and it will work :) Also, with the normal order it is similar to `cp` and `mv` which is less confusing. – Wolph Jan 03 '15 at 16:36
  • 3
    @Wolph See, it's great that I didn't try to create a "drop-in" solution, lol. I had no idea you could pass more than two paths to `ln`. – Camilo Martin Jan 04 '15 at 17:29
  • 1
    Unfortunately, this gives me `You do not have sufficient privilege to perform this operation.` – Jacob Jun 19 '15 at 17:11
  • @Jacob Are you trying that on a regular folder on your profile? Or some system directory? Also, which version of Windows? – Camilo Martin Jun 24 '15 at 16:27
  • @Jacob That's wierd because that's what I'm using. Do you use MSYSGIT or some other Bash port? – Camilo Martin Jun 27 '15 at 04:56
  • Yes, it's the msysgit port. Maybe should add that I don't disable UAC. – Jacob Jul 04 '15 at 18:25
  • @Jacob That could be it! I disable UAC and run under administrator. – Camilo Martin Jul 05 '15 at 19:49
  • I used to think ln's parameters were backwards too, until I realized they were in the same order as cp's. – gknauth Nov 05 '15 at 18:33
  • I tried your more comprehensive function, and it worked like a charm. THANK YOU so much! – gknauth Nov 05 '15 at 19:35
  • 2
    Hey man, this is a *really* sweet answer, and this feels like nitpicking, but... WHY did you have to go and reverse the parameter order? it's "link target linkname" not the other way around. Pls. (Edit: OK I realize it's because you want the one-parameter overload to have it check the linkname if it is a link or not whereas unix `ln` does not do this. Point still stands though.) – Steven Lu Jul 14 '16 at 02:23
  • @StevenLu In retrospect, maybe I should have made it consistent with `ln`'s parameter order, and used `shift` for the overload. Thanks for the compliment :) – Camilo Martin Jul 15 '16 at 07:50
  • `ln`'s parameters seem intuitively "backwards" to me as well, though I can't think of any concrete benefit of having them the other way around. But, jeeze, this is an SO answer, not a library. If people want to switch the parameter order, they can switch the parameter order; no one is forcing them to copy&paste without modification! – Kyle Strand Mar 24 '17 at 18:51
  • Also, it appears that this doesn't really handle absolute paths correctly. Now that `cygpath` is bundled with `git-bash`, I'd suggest using it to correct this issue by creating a variable `target`: `target="$(cygpath -d ${1})"` – Kyle Strand Mar 24 '17 at 20:22
  • @KyleStrand Could you tell me what sort of absolute path was giving you troubles? Was it in the shape `/foo/bar` or `/c/Users/foobar` or `C:\Users\foobar`? – Camilo Martin Mar 26 '17 at 07:59
  • The first one. I didn't thoroughly check that this happened for all such paths, but it appeared that `/c/Users/foobar` somehow became `/c/c/Users/foobar`. – Kyle Strand Mar 26 '17 at 17:41
  • 3
    I'm from the future but I forgot to buy Windows 9 on the way. – Greg Oct 31 '17 at 13:06
  • yes, ln's parameters are backward, even though it mimics cp's parameter order. – Joseph Cheek Jan 03 '18 at 15:12
  • I don't think this has been mentioned, but it isn't best to use `rm` on a symlink. Instead `unlink` should be used. https://stackoverflow.com/questions/210120/remove-a-symlink-to-a-directory – geedew Mar 14 '18 at 11:52
  • The provided link function failed when the windows path has spaces. But It taught me how to use mklink native windows command, so did the trick. Thanks – Rafareino Apr 01 '18 at 10:45
  • 8
    I'm in 2019, still no Windows 9 :) – Maxim Mazurok Jan 07 '19 at 10:28
70

For my setup, that is Git for Windows 2.11.0 installed on Windows 8.1 export MSYS=winsymlinks:nativestrict does the trick as explained here: https://github.com/git-for-windows/git/pull/156 It's important to launch the Git Bash shell as administrator as on Windows only administrators could create the symbolic links. So, in order to make tar -xf work and create the required symlinks:

  1. Run Git Bash shell as an administrator
  2. Run export MSYS=winsymlinks:nativestrict
  3. Run tar
  • 2
    it's the real answer~ – fatfatson Mar 26 '19 at 10:21
  • 8
    you don't need to run as administrator if you turn on windows developer mode (requires Windows 10 version 1703 or later) https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development – Sebastian Jan 14 '20 at 06:57
  • `ln -s currentFile newLink` also works with this approach (not just tar). The link created appears as a shortcut in windows explorer, but following the shortcut does not jump to the other folder's path, instead you see a path like `C:\currentFolder\newLink`. I have not seen a Windows link like this before! – Josiah Yoder Jun 16 '20 at 16:23
  • I've developed an example with samples and images here: https://stackoverflow.com/a/63325536/1315009 - Also back-linked from there to here. – Xavi Montero Aug 09 '20 at 11:15
  • real deal answer. – Amjo May 11 '21 at 20:07
29

A workaround is to run mklink from Bash. This also allows you to create either a symbolic link or a junction point.

Take care to send the mklink command as a single argument to cmd...

cmd /c "mklink link target"

Here are the options for mklink...

cmd /c mklink

Output:

   Creates a symbolic link.

MKLINK [[/D] | [/H] | [/J]] Link Target

    /D      Creates a directory symbolic link.  Default is a file
            symbolic link.
    /H      Creates a hard link instead of a symbolic link.
    /J      Creates a Directory Junction.
    Link    specifies the new symbolic link name.
    Target  specifies the path (relative or absolute) that the new link
            refers to.

If you want to create links via a GUI instead ... I recommend Link Shell Extension that is a Windows Explorer plugin for creating symbolic links, hard links, junction points, and volume mount points. I've been using it for years!

Link Shell Extension

Symbolic links can be a life saver if you have a smaller SSD drive on your system C: drive and need to symbolic link some bloated folders that don't need to be on SSD, but off onto other drives. I use the free WinDirStat to find the disk space hogs.

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Tony O'Hagan
  • 20,290
  • 3
  • 62
  • 69
  • 2
    Pervasively enough, MSYSGIT manages to further sodomize the user by making it impossible to run `cmd /c` because `/c` is always going to be replaced into `C:` (it's filesystem uses `/c/Windows/...` convention). Lucky thing you can do `echo -n | cmd /k ...` if you don't have a `K:` drive. Brilliant. – Camilo Martin Aug 19 '14 at 23:22
  • Oh and good luck with the `/d` argument. Buy a netbook with no disc drive and unplug external HDDs to run that :P – Camilo Martin Aug 19 '14 at 23:29
  • Yes I agree it sucks that both Windows and msysgit *still* has rather poor support for symlinks. I'm just documenting the options not praising them ... so really no justification for down voting this answer. – Tony O'Hagan Aug 19 '14 at 23:57
  • Downovte? I didn't downvote you nor imply you have anything to do with this issue, just ranting about my frustration. Actually, the voting breakdown for your answer shows no downvotes :) http://i.imgur.com/xVYFE5n.png Also: Windows supports symlinks just fine, it's just msysgit that should make its `ln` command wrap the WinAPI. I do propose a possible solution in my answer, though. – Camilo Martin Aug 20 '14 at 00:45
  • 2
    Is there any other reason why people say "Windows has poor support for symlinks", other than the fact that it requires admin for security reasons? You can grant a user the right to create symlinks unelevated using secpol.msc. – Justin Dunlap Aug 28 '14 at 19:48
  • 2
    @JustinDunlap Early implementation for linking in Windows NT/2000 was poor and just looked like a "hack" afterthought (Vista/Win7/8 implementations cleaned this up). Most Windows devs/sysadmins are not used to using them and MS documentation almost never refers to them. Consequently many Windows devs don't consider them in software solutions! I suspect many link related security scenarios have not been considered by product vendors hence MS made them "admin only". On Linux/OSX/Unix they are mainstream/encouraged so most apps are well tested with them and expected to work with them. – Tony O'Hagan Aug 29 '14 at 06:16
  • Even on *nix issues pop up regarding symlinks and security. Apache has (or had) a serious vuln related to symlinks and I've seen several cases where *nix apps were vulnerable to symlink race issues. I imagine that MS had visions of all the ways that malware could exploit symlinks when making the decision to require admin by default. At any rate, they did make it possible to easily change this should you want to create them unelevated. – Justin Dunlap Aug 29 '14 at 22:05
  • 7
    @CamiloMartin `/c` can be escaped like this `//c` to get the powershell command instead instead of the C: drive. I also wanted to add a couple of examples of exactly what the syntax needs to look like to use `mklink` in the git bash shell as it took me a while to figure it out. As Tony describes quoting the entire m`mklink` command will work, like this: `cmd //c "mklink .\b_dir\test.txt .\a_dir\test.txt"`, or you can omit the quotes and escape the backslahes like this: `cmd //c mklink .\\b_dir\\test.txt .\\a_dir\\test.txt` – Grant Humphries Sep 25 '15 at 19:03
  • 1
    @GrantHumphries Great, didn't know about `//c`. By the way, for cases like that single quotes are usually more convenient because there's no parsing done inside these strings. – Camilo Martin Oct 08 '15 at 02:08
16

I believe that the ln that shipped with MSysGit simply tries to copy its arguments, rather than fiddle with links. This is because links only work (sort of) on NTFS filesystems, and the MSYS team didn't want to reimplement ln.

See, for example, http://mingw.5.n7.nabble.com/symbolic-link-to-My-Documents-in-MSYS-td28492.html

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Austin Hastings
  • 587
  • 3
  • 13
  • If I use it to make *hard* links they seem to work, at least in my very brief testing, so I assume you mean just with regard to symbolic links? – iconoclast Oct 25 '13 at 18:11
  • 3
    You are correct. When I tried just doing `ln file1 file2; ln file1 file3; ln file1 file4` I got what appeared to be valid link counts in the output of `ls -l`. But be aware that it fails with `ln: hard link not allowed for directory.` if you try that. – Austin Hastings Nov 01 '13 at 21:44
  • 1
    Don't most modern Windows systems already have NTFS? Also, haven't they had them since Windows NT 3.1 in 1993 (according to trusty old Wikipedia)? – trysis Nov 19 '13 at 02:42
  • 2
    Yes, it seems like a glaring omission, and not one they can blame on Microsoft. – iconoclast Feb 14 '14 at 02:10
  • FAT is still supported, since it consumes less space on small systems. And I have the feeling that the mingw/msys team is not big on re-visiting decisions, since they are a little busy. :) – Austin Hastings Feb 14 '14 at 14:27
  • 1
    @AustinHastings, Linux doesn't allow hard links on directories either, so that part is still in line with `ln`. – trysis May 16 '14 at 23:09
  • 1
    @AustinHastings No, FAT is not *really* supported. You can't install currently-supported versions of Windows on FAT anymore. – Camilo Martin Aug 19 '14 at 23:10
  • 1
    FAT is still supported on Windows, you just can't install Windows to a FAT partition. You can have all your other partitions be FAT if you want, and it's very common for external devices like USB drives and SD cards to be FAT formatted. But the main reason for not supporting symlinks in MSYS is that only Vista or newer supports them. And as Austin Hastings suggests the developers apparently haven't seen a pressing need to add symbolic link support since then. – Ross Ridge Aug 21 '14 at 22:26
  • The link is broken: *"Hmm. We’re having trouble finding that site. We can’t connect to the server at mingw.5.n7.nabble.com."* – Peter Mortensen May 07 '22 at 22:04
9

Do

Grant yourself privileges' to create symlinks.

  1. Search for local security policies
  2. Local Policies/User Rights Assignment/Create symbolic links
  3. Take a moment to scold Windows. "Bad OS! Bad!"
  4. Profit

This grants you the pricledge to create symlinks. Note, this takes effect on next login.

The next step is to figure out how ln is configured

env | grep MSYS

What we are looking for is MSYS=winsymlink: which controls how ln creates symlinks.

If the variable doesn't exist, create it. Note, this will overwrite the existing MSYS enviroment variable.

setx MSYS winsymlinks:nativestrict

Do not

Run your shell as an administrator just to create symlinks.

Explanation

The error is somewhat self-explanatory, yet elusive.

You lack the appropriate privileges' to run the command.

Why?

Be default, windows only grants symlink creation rights to Administrators?

CYGWIN has to do a song and dance to get around Windows subpar treatment of symlinks.

Why?

Something, something "secturity"

¯\_(ツ)_/¯

Edit:

I just realized OP had admin rights. I leave this answer up, hoping it's useful to others.

CervEd
  • 1,682
  • 18
  • 18
4

Extending Camilo Martin's anwser as you need to use the /j parameter switch for Windows 10; otherwise the call will just return "You do not have sufficient privilege to perform this operation."

This works for git bash 2.20.1.windows.1/MINGW64 (Windows 10) without Admin rights (if you can read/write both /old/path and /link/path:

original_folder=$(cygpath -w "/old/path")
create_link_new_folder=$(cygpath -w "/link/path")
cmd <<< "mklink /j \"${create_link_new_folder}\" \"${original_folder}\"" > /dev/null
Oliver Zendel
  • 2,378
  • 28
  • 28
3

for anyone who's interested in how to acomplish this in Windows 10 Git Bash 2.28.0.0.1, You have to prefix the ln -s command with the MSYS=.. instead of execute export MSYS=.. first, namely it's just one command:

 MSYS=winsymlinks:nativestrict ln -s <TARGET> <NEW_LINK_NAME>
2

Since this is one of the top links that come up when searching for creating symlinks in Msys or git bash, I found the answer was to add set MSYS=winsymlinks:native when calling git-cmd.exe (I run ConEmu) or uncomment the same line in the msys2_shell.bat

swee lim
  • 41
  • 3
  • 1
    I tried it and msys still copies and doesn't create actual link- I'm on Win10 – 0fnt Jun 13 '16 at 11:40
  • @sweelim Please clarify which `git-cmd.exe` you are using when this works for you. – clacke Aug 30 '16 at 09:28
  • I just tried setting `MSYS2=winsymlinks:native`, `MSYS=winsymlinks:native` and `CYGWIN=winsymlinks:native`, and used the official Git windows distribution version `2.8.3.windows.1`. `ln -s` doesn't symlink, it copies recursively. – clacke Aug 30 '16 at 10:47
1

I prefer Powershell to CMD, and thought i'd share the powershell version of this.

In my case it consists of making symlinks linking ~/.$file to ~/dotfiles/$file, for dotfile configurations. I put this inside a .sh script and ran it with git-bash:

powershell New-Item -ItemType SymbolicLink\
    -Path \$Home/.$file\
    -Target \$Home/dotfiles/$file
Christian Fosli
  • 1,248
  • 7
  • 13
0

Instead of symbolic links on windows, I found it easier to write small bash script that I place in my ~/bin catalog. To start notepad++ with the npp command I have this file:

$ cat ~/bin/npp

#!/usr/bin/bash

'/c/Program Files (x86)/Notepad++/notepad++.exe' $@

And I get the path syntax right by drag and drop the file from explorer into vim.

The windows command mklink /J Link Target doesn't seem to work anymore.

lgwest
  • 1,337
  • 4
  • 16
  • 25