44

I need to pass /DEF:c:\filepath\myLib.def" command line option from a bash script to MS compiler/linker. The path is generated as part of build process by a bash script. Basically, the argument that my script passes is:

-DEF:/c/filepath/myLib.def

MSYS path conversion can't handle it properly because it doesn't understand /DEF: part. It works if I do

-DEF=/c/filepath/myLib.def

but then ms tools don't understand this parameter. In short, what's the proper way to write that parameter in MSYS bash so that it converts it to proper argument?

On cygwin I could use cygpath, but there is no equivalent, because somebody from msys thinks that it's not needed (even if there are scripts for cygwin that uses cygpath).

cxw
  • 16,685
  • 2
  • 45
  • 81
Pavel P
  • 15,789
  • 11
  • 79
  • 128
  • The "not needed" argument can be stretched a long way. But sometimes it's useful, for example with Jython's shell script allowing for Cygwin but not MSYS. Jython's shell script calls `cygpath`. If we add `cygpath.exe` from @user1307996's answer into MSYS bin, it works. – Evgeni Sergeev Dec 25 '13 at 04:02
  • Both directions handled here: http://stackoverflow.com/q/13701218/321973 – Tobias Kienzler Jan 07 '15 at 14:03
  • @TobiasKienzler Try to convert `\Windows` to posix using these hairy scripts. It makes no sense not to use cygpath on cygwin – Pavel P Jan 07 '15 at 18:51
  • of course not, unless you don't _have_ `cygpath` available... – Tobias Kienzler Jan 07 '15 at 18:55
  • I'm voting to close this question as off-topic because the problem described has been solved in a later version of the software - msys2 includes cygpath. – Richard Neish Feb 21 '19 at 08:23

9 Answers9

59

Update (Aug-2016):

This question is no longer relevant, as msys2 now comes with cygpath in its installation.

...

I'll summarize my research here.

The cygpath equivalent in MSYS is to use this command:

{ cd /c/some/path && pwd -W; } | sed 's|/|\\|g'

The problem with this approach is that it requires existing path, e.g. the c:\some\path has to be an existing directory; however, real cygpath supports paths that do not exist.

So, if you need to get path to a directory that doesn't exist, then you can fallback to sed conversion of the path:

{ cd 2>/dev/null /c/some/path && pwd -W ||
  echo /c/some/path | sed 's|^/\([a-z,A-Z]\)/|\1:/|'; } | sed 's|/|\\|g'

The mouthful of slashes is there to satisfy quoting rules of sed. So, if c:\some\path doesn't exist on your PC, it will try to convert forward to back slashes and replace /c/ with c:\ (or any other drive letter). The only drawback for this is that it won't work correctly non-existing paths that contain a mounted component, such as /bin/does-not-exist or /usr/bin/does-not-exist.

One more approach is to use cygpath from cygwin in MSYS. It seems that cygwin sets global environment variable CYGPATH, that is, you can use it from regular cmd.exe:

%CYGPATH% -w /c/some/path
C:\some\path

or from MSYS:

$CYGPATH -w /c/some/path
C:\some\path

as long as you set to point /c to /cygdrive/c in cygwin. But this approach will print you /usr located in cygwin installation, not in MSYS.

In short, I think msys should really include real cygpath in the default set of tools just for some cases that aren't handled automatically by msys command line argument conversion logic

Pavel P
  • 15,789
  • 11
  • 79
  • 128
  • 1
    There's no need to escape backslashes if you are already using single-quotes. – Rufflewind Mar 01 '15 at 11:39
  • 1
    Excellent answer, however if your sed script is modified to change \([a-z]\) to \([a-z,A-Z]\) , then capital drive letters may be used too. – stellarpower Sep 25 '15 at 19:34
  • 1
    Also, by removing the forwars slashes at the end of the first sed script it allows something like /c to be converted to C: instead of \c. – stellarpower Sep 25 '15 at 22:29
  • @stellarpower But then `/abc` will also get converted to `a:bc` or `a:\bc` depending on what `/a` gets converted to. – Pavel P Apr 14 '17 at 15:28
  • 1
    The sed part is imprecise - `pwd -W` generates uppercase drive letter whereas sed keeps the lowercase one (yes, this does matter sometimes). – yugr Jan 12 '20 at 13:26
  • The issue with any `sed` approach is that it depends on "only get paths that have the "/x/" form. But there can be different mount points that don't match this, most important ones are "/mingw" and "/home/". The best option is to always use `pwd -W` for the first part of the entry, then convert /->\ and uppercase the first character (`pwd -W` converts according to the mounts and may also use lowercase letters). – Simon Sobisch Apr 05 '22 at 14:21
14

use pwd -W

or download cygpath for msys from here http://mingw.5.n7.nabble.com/enhanced-version-of-cygpath-td28556.html

and use cygpath -wa

rtxndr
  • 872
  • 1
  • 9
  • 20
8

Similar to dmitri-rubinstein@ above, I've cleaned up the code a bit and added the reverse conversion as well.

winpath() {
    if [ ${#} -eq 0 ]; then
        : skip
    elif [ -f "$1" ]; then
        local dirname=$(dirname "$1")
        local basename=$(basename "$1")
        echo "$(cd "$dirname" && pwd -W)/$basename" \
        | sed \
          -e 's|/|\\|g';
    elif [ -d "$1" ]; then
        echo "$(cd "$1" && pwd -W)" \
        | sed \
          -e 's|/|\\|g';
    else
        echo "$1" \
        | sed \
          -e 's|^/\(.\)/|\1:\\|g' \
          -e 's|/|\\|g'
    fi
}

unixpath() {
    echo "$1" \
    | sed -r \
      -e 's/\\/\//g' \
      -e 's/^([^:]+):/\/\1/'
}
TJR
  • 3,617
  • 8
  • 38
  • 41
6

I am using this with msysgit:

winpath() {
    if [ -z "$1" ]; then
        echo "$@"
    else
        if [ -f "$1" ]; then
            local dir=$(dirname "$1")
            local fn=$(basename "$1")
            echo "$(cd "$dir"; echo "$(pwd -W)/$fn")" | sed 's|/|\\|g';
        else
            if [ -d "$1" ]; then
                echo "$(cd "$1"; pwd -W)" | sed 's|/|\\|g';
            else
                echo "$1" | sed 's|^/\(.\)/|\1:\\|g; s|/|\\|g';
            fi
        fi
    fi
}
3

My bash foo is weak and I couldn't get regexes working in bash 3.1 so I hacked out a perl script for it:

#!/bin/env perl
use strict;

my @r;
foreach my $e (@ARGV) {
 $e=~s/\//\\/g;
 $e=~s/^\\([A-Za-z])\\/\1:\\/;
 push @r, $e;
}

print join(" ", @r);
  • 1
    I know how to do that using scripts; I think that there is some MSYS specific way to quote arguments so that it converts this path automatically. That's what I'm looking for. – Pavel P Aug 21 '12 at 21:59
3

MSYS cygpath

Program

This program convert a DOS path to a UNIX path and vice versa

#!/bin/env perl
# DOS to UNIX path conversion
# © John S. Peterson. License GNU GPL 3.
use strict;
use Getopt::Std;

# usage
if ($#ARGV == -1) {
    print 'Usage: cygpath (-w) NAME...

Convert Unix and Windows format paths

Output type options:

  -w, --windows         print Windows form of NAMEs (C:\WINNT)
';
    exit 0;
}

# option
my %opt;
getopts('w', \%opt);

# convert path
my @r;
foreach my $e (@ARGV) {
    if ($opt{w}) {
        # add drive letter suffix
        $e =~ s,^\/([A-Za-z])\/,\1:\/,;
        $e =~ s,\/,\\,g;

    } else {
        $e =~ s,\\,\/,g;
        # add leading slash
        $e = "/$e";
        # remove drive letter suffix
        $e =~ s,:,,;
    }

    push @r, $e;
}

print join("\n", @r);

Compared to Cygwin cygpath

The output from this program is better than the output from Cygwin cygpath in MSYS because

  • Cygwin cygpath remove the Cygwin home from a converted path, f.e.
cygpath "$CYGWIN/usr/local/bin"
/usr/local/bin

which is a problem because

  • it's sometimes useful to convert a DOS Cygwin path to a UNIX path for the purpose of copying files from Cygwin to MSYS

This program doesn't remove the Cygwin home

cygpath "$CYGWIN/usr/local/bin"
/c/file/program/cygwin/usr/local/bin

Compared to automatic MSYS path conversion

Manual path conversion has a use in MSYS because

  • the automatic path conversion is inadequate

for f.e.

John Peterson
  • 83
  • 1
  • 5
3

How about this one ? cmd //c echo <your path>

It may not work always but it is the shortest I found

  • Solution based on this how to pass it from a Makefile is provided in http://stackoverflow.com/questions/40257679/how-do-i-define-a-path-from-an-msys-makefile-for-a-c-program/40257680#40257680 – Klamer Schutte Oct 26 '16 at 08:47
  • Commented there that the syntax may actually be causing issues for me, and a slight change to `cmd /c echo ...` may work better. – Pysis Sep 14 '21 at 20:11
0

This works for me

df "$1" | tac >k
read b <k
rm k
set "$1" $b
echo ${1/$7/$2/}

ref

Zombo
  • 1
  • 62
  • 391
  • 407
  • Did you try my own solution: `sh -c 'cd /c/some/path; pwd -W'`? it's functionally equivalent to `cygpath -w '/c/some/path'` on cygwin. Your solution maybe has that functionality of cygpath, but it's not very clear how to translate it to cygpath-like command. – Pavel P Jan 30 '13 at 20:38
0

nearly pure GNU bash solution (which is what you commonly run in MSYS) (interestingly not working with MSYS2, leave a comment if you know why):

#!/bin/bash

# check if we have cygpath (cygwin, newer MSYS2), then just use that 
which cygpath 1>/dev/null 2>&1
[[ $? = 0 ]] && (cygpath -wa "$1"; exit $?)

# check if it looks like a Windows path, in which case we directly convert and exit
[[ ${1} =~ ^[a-zA-Z]: ]] && \
  echo "${1}" | sed -e 's|/|\\|g' -e 's/\(.\)/\u\1/' && exit 0

# split first path entry (if any) with trailing slash and filename
[[ ${1} =~ ^\([/a-zA-Z0-9_.-]\\w*/\)?\(.*\)$ ]]

chk_root="${BASH_REMATCH[1]}"
chk_rest="${BASH_REMATCH[2]}"

# check if the root path exists and more important: let pwd binary resolve the translation according to the mount
chk_winroot="$(cd "${chk_root}." 2>/dev/null && pwd -W)"
[[ "${chk_winroot}" == "" ]] && echo "${chk_root}: No such file or directory" && exit 1

# using substition to replace all / by \ and uppercasing the first character
# pure bash solution; sadly: the first part needs a newer bash than old MSYS have ...
# chk_drv="${chk_winroot:0:1}"
# chk_all="${chk_winroot:1}/${chk_rest}"
# echo "${chk_drv^^}${chk_all//\//\\}"

# ... so fallback to GNU sed
echo "${chk_winroot}/${chk_rest}" | sed -e 's|/|\\|g' -e 's/\(.\)/\u\1/'

There's still an issue with it: if MinGW's fstab contains an entry like /mnt/c the pwd -W of /mnt/. done in this script won't work.
To fix it: replace pwd -W by inspecting $ cat /etc/fstab | cut -d'#' -f1 | grep -v "^\s*$" entries manually and replace the first match - while this will never work for cygwin or msys2 which use a different format this is covered by using cygpath there.

Simon Sobisch
  • 6,263
  • 1
  • 18
  • 38
  • Is there any reason to use it now when MSYS2 ships `cygpath`? – HolyBlackCat Apr 05 '22 at 17:12
  • Yes, if you use the original MSYS (or a very old MSYS2). Because of completely different dependencies and missing support for "old" Windows versions one may still want to use MSYS/MinGW instead of MSYS2's environments. – Simon Sobisch Apr 05 '22 at 21:14