5

In Git Bash and Cygwin, I can easily access the Windows %PROGRAMFILES% environment variable:

$ echo $PROGRAMFILES
C:\Program Files

$ echo ${PROGRAMFILES}
C:\Program Files

However, due to the brackets/parentheses in the variable name, I cannot access %PROGRAMFILES(X86)% in the same way:

$ echo $PROGRAMFILES(X86)
bash: syntax error near unexpected token `('

$ echo ${PROGRAMFILES(X86)}
bash: ${PROGRAMFILES(X86)}: bad substitution

$ echo ${PROGRAMFILES\(X86\)}
bash: ${PROGRAMFILES\(X86\)}: bad substitution

Unless, of course, I use Command Prompt:

C:\Users\myname>echo %PROGRAMFILES(X86)%
C:\Program Files (x86)

(which I rarely do!)

Is there a way to escape parentheses in environment variable names, or are these completely invalid (and hence inaccessible) in Bash-like environments?

AJM
  • 222
  • 1
  • 11
  • https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_03.html – Lazy Badger Jan 13 '22 at 14:12
  • The problem is not so match the space and parentheses, which you could overcome easily by quoting your variable properly, but that you have, for instance, backslashes as path separator, which does not make sense in bash. In Cygwin, you could use `cygpath` to convert the path, but you also tagged this as git-bash (why?), and I don't know whether you have there something equivalent. – user1934428 Jan 17 '22 at 14:00
  • [This](https://github.com/13circle/windows-git-bash-path-converter) seems to be something like `cygpath`, but for Git-bash. – user1934428 Jan 25 '22 at 14:29

2 Answers2

3

Better answer

Turns out with cygpath -F we can query a number of special "folder IDs". The following outputs the IDs along with the values for your system.

for ((id=0;id<=64;id++))
do
  F=$(cygpath -amF $id 2> /dev/null)
  [[ -n "$F" ]] && echo "$id = $F"
done

As a one-liner:

for ((id=0;id<=64;id++)); do F=$(cygpath -amF $id 2> /dev/null); [[ -n "$F" ]] && echo "$id = $F"; done

Meanwhile I checked in the code. The function get_special_folder() in cygpath.cc uses SHGetSpecialFolderLocation() and subsequently SHGetPathFromIDListW, so the value 42 (hexadecimal 0x2a) corresponds to CSIDL_PROGRAM_FILESX86 and so we can rely on this as long as Windows upholds its API contracts.

IMPORTANT: The "mixed representation" (cygpath -m) -- essentially Windows paths with forward slashes instead of backslashes -- appears to be more robust and portable across Cygwin flavors, including MSYS2!


Original answer

Here's a solution that works with Bash from Git for Windows and from Cygwin alike (should work with MinGW, too):

PF86="$(cygpath -u "$(env|tr -d '\r'|gawk -F= '$1 == "ProgramFiles(x86)" {print $2}')")"

The rest is making use of the populated environment variable. It is advisable to check if the output comes back empty and act accordingly, though. Example:

PF86="$(cygpath -u "$(env|tr -d '\r'|gawk -F= '$1 == "ProgramFiles(x86)" {print $2}')")"
[[ -n "$PF86" ]] || { echo "\${ProgramFiles(x86)} not set"; exit 1; }

Note: gawk can usually be substituted by awk. Depends on the exact details of your AWK script, but this one is so trivial, it should work in most if not all AWK flavors.

What does it do?

  1. env lists environment variables as key=value pairs, one per line
  2. tr -d '\r' deletes any carriage return from the stdin and writes the result to stdout
  3. gawk -F= '$1 == "ProgramFiles(x86)" {print $2}')" uses = as field separator and matches for ProgramFiles(x86) in the first field. When matched, it prints field 2 (the value).
  4. the output from all the above steps is then used as input to cygpath -u, converting the path from Windows to Unix representation

Please be aware that there is a very very subtle difference which causes the \r (carriage return) to remain part of the value for output captured from Windows programs. For the above the tr -d '\r' may not be strictly necessary, but I'd rather be safe than sorry.

Disclaimer: while this may not qualify as "pretty" in the stricter sense, it has turned out to be a very robust solution to the problem at hand. I have used it in numerous scripts, also professionally. One application I had was to use vswhere.exe, whose well-known path is based in this subfolder. Also vswhere was one of the cases where the tr -d '\r' workaround originated.

NB: I did not change the original answer above, but would strongly recommend the "mixed representation" (cygpath -m) as opposed "universal Unix representation" (I made that up; cygpath -U)!

0xC0000022L
  • 6,819
  • 10
  • 50
  • 82
  • *`gawk` can usually be substituted by `awk`* - I tested, and in this case it certainly can. – AJM Apr 04 '23 at 16:35
  • I'm going to give this one a green tick. I'd actually been using a similar solution: `$ export PFTEST=$(env | grep -i "^PROGRAMFILES(X86)=" | cut -d= -f2-); echo $PFTEST` - which output `C:\Program Files (x86)` - after which `cygpath` or `wslpath` could be used. I don't know if it's as robust as your solution though. – AJM Apr 04 '23 at 16:41
  • I *did* try out the alternative answer. In both Git Bash and Cygwin: `42 = /proc/cygdrive/c/Program Files (x86)`. Not sure why there'd be a `/cygdrive/` in a non-Cygwin filepath, but `ls /proc/cygdrive/c` is giving me the same output as `ls /c`, so it seems to work OK. – AJM Apr 04 '23 at 16:47
  • 1
    Git Bash is based on MSYS which in turn relies on the Cygwin DLL to pretend a unixoid environment. It's a modified Cygwin DLL, but with this normalized path trick it allows interop between flavors of Cygwin. – 0xC0000022L Apr 11 '23 at 15:28
  • 1
    @AJM as a side note you can also say `cygpath -amF 42` to achieve the same in "mixed representation" (understood by both Cygwin and MSYS2 and less of an issue when processed in shell scripts). It uses Windows paths, but with forward slashes throughout. In general you can tell `cygpath` to convert the path into whichever representation you prefer. The most compatible by far is "mixed representation" (`-m`). I've tried in scripts that need to run across Cygwin and MSYS2 and this is my experience. – 0xC0000022L Jun 15 '23 at 12:50
  • 1
    Thanks - I didn't know about mixed representation, and trying to write scripts that run in all of Git Bash/Cygwin/the msys2 variants means that anything improving compatibility is helpful. I'll certainly try it out in a few more places in future. – AJM Jun 16 '23 at 18:27
  • 1
    @AJM essentially I originally thought `cygpath -U` (i.e. prefix `/proc/cygdrive/`) to be the silver bullet to bridge the gap between Cygwin flavors, but it turned out to be mixed representation all along. – 0xC0000022L Jun 19 '23 at 10:38
  • I don't know if you've noticed, but something seems to have significantly affected msys2/Git Bash compatibility recently. At the very least, I was not able to run msys2's `cdecl explain` from Git Bash without `"cygheap base mismatch detected"` - and now, this year, I can. Though in any situation where `cdecl` would output an error or warning message, it instead outputs `"cdecl: failed to determine number of columns or lines in terminal: No such file or directory"` so the improved compatibility is not 100% – AJM Jul 07 '23 at 14:27
  • I was only ever talking about compatibility *of the path representation*. Compatibility down to the binary level, i.e. being able to call an executable from one environment based on `cygwin1.dll` in another, based on the same DLL, but in an entirely different version? ... well that's a completely different topic. Based on your question this is also way beyond the scope of it, no? – 0xC0000022L Jul 07 '23 at 20:02
  • 1
    True, it is outside the scope of the question. But since you mentioned having to deal with compatibility issues like this, I thought it might be of interest to you. At the very least, there might be applications you worked with and had written off as incompatible, which would now be worth retesting. – AJM Jul 10 '23 at 09:27
  • Thanks for the info. It's indeed interesting and perhaps interesting to other internauts later finding this Q&A. – 0xC0000022L Jul 10 '23 at 14:10
-1

I found this solution at GitBash:

powershell  -c '${env:ProgramFiles(x86)}'
Toto
  • 17,001
  • 56
  • 30
  • 41
  • 1
    I'm trying to do this in something like Git Bash/Cygwin/msys, not Powershell. – AJM Jun 08 '22 at 13:35