3

In Powershell, I would like to run the following command:

bash -c "echo 'hello world!'"

(I know I could use echo or Write-Host directly, but I need to use bash -c) and I would like hello to be surrounded by single quotes and world by double quotes. In other words, I'd like the following output:

'hello' "world"!

I should I escape those quotation marks?

user3534974
  • 173
  • 2
  • 9
  • But you are not running PowerShell. You are running bash.exe in a PowerShell session. That is not the same thing. To run .exe in PowerShell you must properly config the line. See this: --- https://social.technet.microsoft.com/wiki/contents/articles/7703.powershell-running-executables.aspx and this --- https://unix.stackexchange.com/questions/187651/how-to-echo-single-quote-when-using-single-quote-to-wrap-special-characters-in – postanote Mar 27 '20 at 21:14

2 Answers2

3

Since you are calling a program (e.g. bash) "normal" rules to escape quotation characters don't necessarily work the "same" way. Instead of PowerShell interpreting the string, (which would normally follow "normal" quotation rules), you are passing arguments to the program. This is interpreted in a completely different manner.

TLDR: To escape quotation characters requires the following command:

bash -c "echo \'Hello\' \\\""World\\\""!"

Outputs:

'Hello' "World"!

Now that looks awfully complicated. So, let's break it down.

First, what is the correct command in bash to output the desired quotation? Let's try regular quotes:

HAL9256@HAL9000:~$ echo 'Hello' "World"!
Hello World!

No quotes. Oh yeah! I have to escape them. In bash I have to escape them with backslashes (\):

HAL9256@HAL9000:~$ echo \'Hello\' \"World\"!
'Hello' "World"!

There we have it. We have to use a single backslash to properly escape our quotes in bash. So let's plug that into PowerShell:

PS C:\> bash -c "echo \'Hello\' \"World\"!"
/bin/bash: -c: line 0: unexpected EOF while looking for matching `"'
/bin/bash: -c: line 1: syntax error: unexpected end of file

Well that didn't work. Oh right, in PowerShell we have to escape the double quotes with backticks (`) because they are inside a set of double quotes:

PS C:\> bash -c "echo \'Hello\' \`"World\`"!"
'Hello' World!

Well, that didn't error out, but still not what we want. The quotes are still not escaped properly. Grrr. This is where you start typing in 100 different combinations of characters to find out what the right combination is ;-).

Or, let's go back to bash and figure out what may be going on. First, let's remember that the string that is being echoed is being interpreted like a string. So let's put double quotes around our echo statement so that it is being treated like a string, and see what it does. Remember, what we want out of this is the output to be the same bash command earlier, with the backslashes:

HAL9256@HAL9000:~$ echo "\'Hello\' \"World\"!"
\'Hello\' "World"!

Well, ultimately what we want to send to bash is that original string with the backslashes. Here we can see that the backslashes are escaping the double quotes and disappearing. So, adding more slashes:

HAL9256@HAL9000:~$ echo "\'Hello\' \\"World\\"!"
\'Hello\' \World\!

Oh, great. We escaped the backslashes. Like Inception, keep adding escape characters till it works....

HAL9256@HAL9000:~$ echo "\'Hello\' \\\"World\\\"!"
\'Hello\' \"World\"!

There! we have our output. Now we have to enter it into PowerShell:

PS C:\> bash -c "echo \'Hello\' \\\"World\\\"!"
/bin/bash: -c: line 0: unexpected EOF while looking for matching `"'
/bin/bash: -c: line 1: syntax error: unexpected end of file

Oh! yes the same error as before. We have to remember to escape the double quotes in PowerShell with a backtick:

PS C:\> bash -c "echo \'Hello\' \\\`"World\\\`"!"
'Hello' "World"!

Success! It only took a lot of escaping, both bash-wize, and PowerShell-wize. Another way, and in my opinion, easier to understand way to escape inside double quotes is to use two double quotes instead of the backtick like so:

PS C:\> bash -c "echo \'Hello\' \\\""World\\\""!"
'Hello' "World"!
HAL9256
  • 12,384
  • 1
  • 34
  • 46
  • On my computer, it doesn't work, I don't get any quotation marks. This is my $PSVersionTble: Name Value ---- ----- PSVersion 7.0.0 PSEdition Core GitCommitId 7.0.0 OS Darwin 18.7.0 Darwin Kernel Version 18.7.0: Mon Feb 10 21:08:45 PST 2020; root:xnu… Platform Unix PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1 – user3534974 Mar 27 '20 at 19:39
  • Nothing changes, unfortunately. – user3534974 Mar 27 '20 at 19:49
  • After downloading and installing WSL I realized that we aren't dealing with "typical" PowerShell quotation rules. We are dealing with the parsing and interpretation of arguments *as well*. This means you have to get a little crazy with escaping things. See edits above. – HAL9256 Mar 27 '20 at 21:12
  • Ok... I think I got it. There cannot be a unique way of escaping quotes: in fact, if we put the whole bash command between single quotes (instead of double), we can't use the exact same quoting. My problem is that I was trying to make a function to escape quotes automatically, but passing a string to it it's not enough... I also have to tell it if the escaped string will be enclosed in single or double quotes. It works, but it's not as straightforward as I thought :) – user3534974 Mar 27 '20 at 21:30
2

HAL9256's answer is helpful, but let me try to frame the issue differently:

To get the desired verbatim output, this is what your command would need to look like if you ran it from bash itself:

# From Bash itself
$ bash -c "echo \"'hello' \\\"world\\\"!\""
'hello' "world"!

Bash's initial parsing of the string literal above, inside of which \ functions as the escape character, means that the new bash instance sees the following verbatim text as its -c argument:
echo "'hello' \"world\"!"
Therefore, if you were to execute this as a command directly in Bash, it would yield the desired output too.

Note how the echo argument as a whole is enclosed in (initially\-escaped) "...", for robustness: without that, if world were world & space, for instance, the command would break; similarly, world class would turn into world class (normalization of whitespace due to shell expansions).

The many \ characters are needed due to 2 layers of escaping being involved:

  • Escaping for the calling shell (also bash in this example), to ensure that the input string is syntactically valid: \" embeds a single ", and \\ a single \.

  • Escaping for the target shell (bash), so that the resulting string is syntactically valid for it, given that the resulting string is parsed as a command in this case.

Translating the command so it can be called from PowerShell requires adapting the first layer above, which means using `, PowerShell's escape character, instead of \.

A direct translation is therefore:

# SHOULD work, but DOESN'T as of PowerShell 7.0
PS> bash -c "echo `"'hello' \`"world\`"!`""
hello          # !! WRONG output.

Yet, if you print string literal "echo `"'hello' \`"world\`"!`"" directly in PowerShell, you'll see that it properly yields the verbatim string that bash needs to see, as shown above
(echo "'hello' \"world\"!")

The reason it doesn't work is that PowerShell's passing of arguments to external programs has always been broken with respect to embedded double quotes:

This answer provides an overview, but the short of it is, as of PowerShell 7.0:

In addition to satisfying PowerShell's own syntax requirements - which is enough if you call PowerShell commands - you must additionally \-escape embedded " characters in arguments passed to external programs.

Therefore:

# OK, but the manual \-escaping shouldn't be necessary.
PS> bash -c "echo \`"'hello' \\\`"world\\\`"!\`""
'hello' "world"!

Why the current behavior is broken:

It is the job of a shell to pass arguments that result from the shell's own parsing verbatim to the target program, doing whatever is necessary behind the scenes to ensure that - you shouldn't have to worry about any escaping requirements other than PowerShell's own:

  • On Windows, out of unfortunate necessity, this means constructing a command line behind the scenes that applies double-quoting and escaping as needed; while double-quoting is applied by PowerShell on demand, it is the \-escaping of " chars. embedded in arguments that is missing.

  • On Unix-like platforms, no extra effort is required: there, programs receive arguments directly as an array of verbatim values.

While the particular command discussed in this question happens to make the translation to PowerShell fairly straightforward, there are much more insidious cases, where the additional need to \-escape - which is always cumbersome - is also unexpected, because these commands work as-is in bash itself:

PS> bash -c 'echo "hi, there"'
hi,   # !! " were stripped, so bash only saw `echo hi,` as the command string,
      # !! and `there` as a separate argument.
PS> /bin/echo '{ "foo": "bar" }' # Try to pass a JSON string
{ foo: bar } # !! BROKEN JSON
mklement0
  • 382,024
  • 64
  • 607
  • 775