Search code examples
powershellcygwin

Powershell 5 and 7 processes double quotes in different way when running Cygwin


I need to run bash commands under Powershell using Cygwin's bash.

I faced the issue that Powershell 5 and 7 processes double quotes in different way.

I'd like to have bash script working in both. Can I set up both Powershell to make them processing quotes in the same way? Or another way to have export MYKEY="MYVALUE" with double quotes in Cygwin?

Powershell 5:

PS D:\work> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      22621  3958

PS D:\work> C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key="$value";'
export MYKEY=MYVALUE

PS D:\work> C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\"$value\";'
export MYKEY=MYVALUE

PS D:\work> C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\\\"$value\\\";'
export MYKEY="MYVALUE"

Powershell 7:

PS D:\work> $PSVersionTable.PSVersion

Major  Minor  Patch  PreReleaseLabel BuildLabel
-----  -----  -----  --------------- ----------
7      4      4

PS D:\work> C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key="$value";'
export MYKEY=MYVALUE

PS D:\work> C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\"$value\";'
export MYKEY="MYVALUE"

PS D:\work> C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\\\"$value\\\";'
export MYKEY=\"MYVALUE\"

Solution

  • It is PowerShell (Core) 7's behavior that is correct, specifically in v7.3+, which corrected a long-standing bug that still affects Windows PowerShell - see this answer for details.

    You can instruct PowerShell 7.3+ to exhibit the old, broken behavior via the $PSNativeCommandArgumentPassing preference variable, by setting it to 'Legacy':

    Therefore, the following snippet makes the bash.exe call exhibit the same behavior in both PowerShell editions:

    if ($IsCoreCLR) { $PSNativeCommandArgumentPassing = 'Legacy' }
    C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\\\"$value\\\";'
    
    # Alternatively, with a specific version check, which also avoids
    # strict-mode problems, given that the $IsCoreCLR variable isn't defined
    # in Windows PowerShell:
    if ($PSVersionTable.PSVersion -ge '7.3') { $PSNativeCommandArgumentPassing = 'Legacy' }
    C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\\\"$value\\\";'
    

    Note:

    • You may choose to simply unconditionally set $PSNativeCommandArgumentPassing = 'Legacy', given that Windows PowerShell will just ignore it.

    • If you want to scope the effect of setting $PSNativeCommandArgumentPassing, so as to limit it to specific commands, you can enclose the latter in a script block ({ ... }) and invoke that with &, the call operator, which runs the enclosed code in a child scope, so that a scope-local copy of $PSNativeCommandArgumentPassing is created, without affecting the caller's copy.

      # Enclosure in & { ... } scopes the effect of setting 
      # $PSNativeCommandArgumentPassing
      # The caller's original value remains in effect after execution
      # of the script block.
      & {
        $PSNativeCommandArgumentPassing = 'Legacy'
        C:\cygwin64\bin\bash.exe -l -c 'key=MYKEY; value=MYVALUE; echo export $key=\\\"$value\\\";'
      }