27

I understand that Windows later-than-or-equal-to Vista provides the mklink shell command. I'd like to make use of this from the Msys terminal. Any idea how?

When I enter mklink on the msys terminal, it outputs sh: mklink: command not found. Msys only provides a fake ln utility which appears to be effectively the same as cp.

I tried writing a shell script to open a Windows shell and run mklink within it, but when my shell script tries to execute cmd /C <instructions>, msys brings the Windows shell to the foreground of the current terminal and leaves it there, without running the instructions.

Jellicle
  • 2,356
  • 4
  • 28
  • 32

4 Answers4

22

Windows 10 now supports symbolic links without needing to run as administrator, so long as you turn on developer mode in the Windows settings.

Once that is done, you can get ln working correctly with a single line in a .bashrc file in your home directory!

export MSYS=winsymlinks:nativestrict

(I was not able to figure out where the equivalent .ini file is in my Git Bash installation. But setting the environment variable there would probably work, too.)

More details in this very helpful article by Josh Kelley: https://www.joshkel.com/2018/01/18/symlinks-in-windows/

AmeliaBR
  • 321
21

Using cmd //c mklink directly on the MSYS bash command line should work.

$ cmd //c mklink
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.

Note: The mklink command and arguments need to be provided as a single argument to cmd. Quote the entire command like so

cmd //c 'mklink link target'

Note that the command would normally be

cmd  /c 'mklink link target'

which would work in Cygwin and other shell environments, and even in an existing CMD session.  However, msys seems to mangle command-line arguments (to Windows commands), and it interprets /c as a pathname to the root of the C disk, and converts it to c:\.  Typing //c has been found to cause msys to pass the /c option to cmd.  See How to run internal cmd command from the msys shell?

ak2
  • 3,785
14

You can use Windows native symlinks. To enable it uncomment line with:

MSYS=winsymlinks:nativestrict

in MSYS2 start bat file. And run MSYS2 with admin privileges.

driftcrow
  • 241
8

MSYS=winsymlinks:nativestrict requires you to run MSYS2 in elevated mode, I'm really not comfortable with that.

This script only prompts for UAC elevation when invoked, of course it won't be useful for scripting then, but at least it suits my needs:

  • ~/scripts/sh/msys2-ln.sh:

    #!/bin/sh
    if [ "$#" -eq 2 -a "$1" == "-s" ]; then
        TARGET="$2"
        LINK=$(basename "$TARGET")
    elif [ "$#" -eq 3 -a "$1" == "-s" ]; then
        TARGET="$2"
        LINK="$3"
    else
        echo "this weak implementation only supports \`ln -s\`"
        exit
    fi
    
    if [ -d "$TARGET" ]; then
        MKLINK_OPTS="//d"
    fi
    
    TARGET=$(cygpath -w -a "$TARGET")
    LINK=$(cygpath -w -a "$LINK")
    
    echo "$TARGET"
    echo "$LINK"
    cscript //nologo ~/scripts/wsh/run-elevated.js \
        cmd //c mklink $MKLINK_OPTS "$LINK" "$TARGET"
    
  • ~/scripts/wsh/run-elevated.js

    var args = WScript.Arguments;
    if(args.length == 0){
        WScript.Echo("nothing to run");
        WScript.Quit(0);
    }
    
    var quoted_args = [];
    for(var i = 1; i < args.length; ++i){
        var arg = args(i); // it's a callable, not array like
        if(arg.indexOf(" ") != -1){
            arg = "\"" + arg + "\"";
        }
        quoted_args.push(arg);
    }
    
    var SHAPP = WScript.CreateObject("shell.application");
    SHAPP.ShellExecute(args(0), quoted_args.join(" "), "", "runas", 1);
    
  • ~/.bashrc

    # workaround for MSYS2's awkward ln
    if [ $- == *i* -a ! -z $MSYSTEM ]; then
        alias ln='~/scripts/sh/msys2-ln.sh'
        alias ecmd='powershell "start-process cmd.exe \"/k cd /d $(pwd -W)\" -verb runas"'
    fi
    

The additional ecmd alias launches an elevated cmd in current directory, might be useful sometimes, and it also serves as an example of acquiring UAC elevation through powershell, if somebody knows how to escape this thing properly, we can ditch that WSH helper.

JimmyZ
  • 89