-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document the need for additional \-escaping of double quotes (") when calling external programs #2361
Comments
@mklement0 If you invoke
(though I'm willing to bet that is not documented) |
That is also true, but - on Windows - not only. Yes, you first need to make the calling language happy so as not to cause a syntax error there. But, due to the anarchy that is Windows argument passing, it is ultimately up to the called program to parse the command line from a single string. As I've stated, that problem generally doesn't arise on Unix-like platforms, where programs are invoked with an array of literals - no command-line parsing needed (but see below). PowerShell is actively involved in transforming the command line, which is a conceptual necessity on Windows, and an unfortunate technical one on Unix-like platforms, due to current limitations in CoreFX that will be addressed in 2.1 (see below). The way the transformation is implemented is unfortunate: For instance: echoArgs.exe 'Nat "King" Cole' results in the following command line: "C:\path\to\EchoArgs.exe" "Nat "King" Cole" While the To put it differently: PowerShell should never have put the burden of While - as stated - it is ultimately up to the target program to parse the command line, enclosing arguments in Even PowerShell itself honors this convention when it is called from the outside: On Unix-like platforms, PowerShell should only ever have passed the arguments resulting after its parsing and expansion as an array of literals to the target program - the problem of escaping just for the command line would never even arise. Unfortunately, prior to the addition of Yes, the behavior is consistent, but it is now broken on all supported platforms. The pain of that will be felt more strongly on Unix-like platforms, where calling external utilities is much more common than on Windows. It is one of the real pain points that motivated me to start the conversation about "PowerShell vZeroTechnicalDebt" In short: The need for the user to Indeed, use of a script block - available from within PowerShell only and only when calling PowerShell - is the best way to avoid quoting headaches in that scenario. Its use is documented in |
I should add that there are also scenarios where it is better to replace a double quote with 3 double quotes in the case of the ProcessStartInfo.Arguments property in .Net, which boils down to be caused by the Windows API that is called at the end (therefore not really PowerShell's fault). The documentation on it says:
|
Thanks, @bergmeister, good to know, but that's a slightly different scenario in that the intent there is to pass embedded, literal double quotes - in addition to satisfying syntactic quoting requirements.
The triple- Unfortunately, however, the triple- What works on both Windows and Unix is to use $psi = [System.Diagnostics.ProcessStartInfo]::new()
$psi.Arguments = 'one \""two in literal quotes"\"' |
@SteveL-MSFT is this worth trying to document? |
@sdwheeler good question. I think we are ok with just adding an example where we execute a native command with one that requires literal quotes. |
Given how much of a pain point quoting for external programs is (including calling another instance of PowerShell), please consider a dedicated As stated, with CLI-savvy Unix users starting to use PowerShell who expect friction-less calls to external utilities, the pain will increase - see PowerShell/PowerShell#6935 for a recent example. What's needed is a systematic explanation of how quoting does not work as it should with external programs, and how to work around that - not just an example or two. In addition to what I've discussed above, such a topic could cover additional pitfalls:
|
@mklement0 Would you be willing to submit a draft of this about topic? |
Thanks, @SteveL-MSFT, but I prefer that someone else take this on. |
I would also like to see an example of passing in JSON (from a literal string or from a file) to an external program. It's a common enough pattern that it would be good to document for those looking for answers to why what they do in, e.g., bash, doesn't work in PowerShell. |
Good suggestion, @2BitSalute; in fact it just came up on SO (which may be how you found this post). In short, you currently must manually # Sample JSON
PS> $json = '{ "hello": "world" }'
# Pass to Bash for echoing the argument.
PS> bash -c 'echo $1' - $json
{ hello: world } # !! Double quotes were stripped.
# Cumbersome workaround: \-escape embedded " chars.
PS> bash -c 'echo $1' - ($json -replace '"', '\"')
{ "hello": "world" } # OK On a side note, the Bash command should really be So, in summary, what should work as follows: PS> bash -c 'echo "$1"' - $json currently has to be: PS> bash -c 'echo \"$1\"' - ($json -replace '"', '\"') Backward compatibility is the only reason to hang on to these obscure requirements. |
How can I write a command that works both on Windows and Linux? This is crucial for Github Actions' workflow files. Edit: julia -e 'using Pkg; Pkg.add(PackageSpec(url = \"https://github.com/aminya/SnoopCompile.jl\", rev = \"multios\"));' So the only difference with |
An update for those not following the discussion in PowerShell/PowerShell#1995: PowerShell/PowerShell#1995 (comment) states that no action will be taken to fix the underlying problem in v7.1 In the meantime, as an alternative to the currently required workarounds for passing arguments properly to external programs from PowerShell - which would break if and when the underlying issues get fixed - you can use the PSv3+ |
This commit updates the PuppetBolt powershell module to correctly escape hash parameters for linux platforms. Conversation in this thread: MicrosoftDocs/PowerShell-Docs#2361 describe the circumstances of why interpolation breaks. The fix is to include backslash escape chars for all quotes in hash parameters if the powershell function is running on linux
This commit updates the PuppetBolt powershell module to correctly escape hash parameters for linux platforms. Conversation in this thread: MicrosoftDocs/PowerShell-Docs#2361 describe the circumstances of why interpolation breaks. The fix is to include backslash escape chars for all quotes in hash parameters if the powershell function is running on linux !no-release-note
This commit updates the PuppetBolt powershell module to correctly escape hash parameters for linux platforms. Conversation in this thread: MicrosoftDocs/PowerShell-Docs#2361 describe the circumstances of why interpolation breaks. The fix is to include backslash escape chars for all quotes in hash parameters if the powershell function is running on linux !no-release-note
Related: #5152; once PowerShell/PowerShell#1995 gets fixed, this topic will become obsolete.
An additional layer of escaping of
"
chars. is unexpectedly needed when calling external programs from PowerShell to make sure that the external program sees"
embedded in"..."
as\"
, which is what PowerShell's own CLI expects too.This is currently not documented, as far I can tell, neither in about_Parsing nor in about_Quoting_Rules nor in about_Special_Characters
Update: As an alternative to the manual
\
-escaping detailed below, you can use the PSv3+ie
helper function from theNative
module (in PSv5+, install withInstall-Module Native
from the PowerShell Gallery), which internally compensates for all broken behavior and allows passing arguments as expected; to use it, simply prependie
to your invocations; e.g.:ie pwsh -noprofile -command ' "hi" '
works as expected.Calling an external program from PowerShell (the external program just happens to be another instance of PowerShell (
pwsh
) in the following examples):In order to pass string literal
"hi"
as a command to an external program from PowerShell, PowerShell's own escaping of embedded"
chars. must unexpectedly be supplemented with\
-escaping (the following commands are equivalent):As an aside: If you're really calling another PowerShell instance from inside PowerShell, using a script block (
{ ... }
) to pass the command avoids all quoting headaches and additionally enables support for typed results (not just strings), albeit with the same limitations on type fidelity as with background jobs / remoting.Note: There are also a number of bugs:
Empty-string arguments are quietly removed from the invocation.
Values with embedded
"
are passed incorrectly: in addition to not automatically escaping them (the problem shown above), the situationally necessary enclosure in"..."
behind the scenes is not reliably triggered, such as with3" of snow
.In Windows PowerShell only, values with spaces that end in a
\
char. are passed incorrectly.For details and workarounds, see PowerShell/PowerShell#1995 (comment)
Calling the PowerShell CLI from another shell -
cmd.exe
orbash
:Note: The difficulties discussed next stem primarily from
cmd.exe
limitations and how commands are invoked by a single command-line string on Window.Calling from Bash / POSIX-like shells works robustly and as expected.
cmd.exe
:On Windows, PowerShell Core now properly recognizes as
""
as escaped"
, which enables robust escaping, given that""
is also recognized bycmd.exe
itself:Sadly,
""
doesn't work in Windows PowerShell, where\
-escaping for PowerShell's sake is also required, which causes problems:Using
\""
(sic) doesn't require escaping of cmd.exe metachars., but doesn't preserve whitespace as-is:Only using
\"
preserves whitespace as-is, but it additionally requires individual^
-escaping of the followingcmd.exe
metacharacters inside\"...\"
runs:& | < > ^
bash
:Use
\"
inside"...."
strings, and no escaping at all inside'...'
strings:Note that on Unix-like platforms PowerShell's own command-line parsing does not come into play (arguments are invariably passed as an array of literal tokens), and the above commands solely use Bash [non]-escaping to pass an argument with literal contents
"hi"
.Thus:
\"
is Bash's native way to escape"
inside a"..."
string.'...'
,"
needs no escaping\"
inside'...'
, the\
chars. would be passed as literals, and the PowerShell command would break.This asymmetry with how things work on Windows is unfortunate, but unavoidable.
Note that calling from PowerShell on Unix-like platforms still requires the extra
\
-escaping:While this requirement at least makes for consistent behavior across platforms from the PowerShell side, it is certainly unexpected - and an artificial requirement - for anyone familiar with Unix shell scripting.
The bottom line is:
In an ideal world, PowerShell would transparently handle any additional, platform-specific escaping needs for interfacing with external programs behind the scenes, so that all that users need to take care of - on any supported platform - is to follow PowerShell's escaping rules.
Sadly, this is not an option if backward compatibility must be preserved.
Version(s) of document impacted
The text was updated successfully, but these errors were encountered: