9

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
  • 500

3 Answers3

4

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
  • 7,544
  • 10
  • 54
  • 94
0

I just hard-coded the string. I know it's not the general case, but dropping this here with all the escaped characters for easy copy-paste:

/cygdrive/c/Program\ Files\ \(x86\)/
0

On a C-level, environment variable names can contain pretty any character from the posix portable character set, except NUL.

In bash, a variable can only contain certain characters (for instance, it must not contain a parenthese), and an environment variable in bash is simply a normal variable, which happens to be exported.

Since most shells are having such a restriction on the naming of an environment variable, you can't set a variable named PROGRAMFILES(X86), and if you write your own program in a language where you can do with the environment everything what is allowed by Posix (say, you write in C or Ruby), creating a variable with such a name would be not very wise.

If you are in the situation, that the malevolent parent process of your bash script indeed has created a variable with such a bizarre name, you can however retrieve the value of this variable in your shell by using printenv:

value=$(printenv 'PROGRAMFILES(X86)')