Skip to content
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

Strange quoting behavior when using cmd.exe #743

Closed
casey opened this issue Jan 7, 2021 · 9 comments
Closed

Strange quoting behavior when using cmd.exe #743

casey opened this issue Jan 7, 2021 · 9 comments
Labels

Comments

@casey
Copy link
Owner

casey commented Jan 7, 2021

This was noticed by @SuperCuber in #741.

Quoting commands, which works when using cmd.exe interactively, doesn't work in a Justfile.

This works:

set shell := ["cmd.exe", "/c"]

run:
    cmd.exe

But this doesn't:

set shell := ["cmd.exe", "/c"]

run:
    "cmd.exe"

The "s are passed to cmd.exe, and interpreted as part of the command name. Aside from just being weird, this makes it impossible to run commands with spaces.

@casey casey added the bug label Jan 7, 2021
@casey
Copy link
Owner Author

casey commented Jan 8, 2021

I have a feeling that this might be an inherent limitation of cmd.exe.

Let's use this Justfile as an example:

foo:
  echo "bar"

When just executes a recipe line, it passes the whole line as a single argument to the shell binary. When running the foo recipe with sh, the default shell, this is like running the following command:

sh -c 'echo "bar"'

sh receives two arguments -c and echo "bar". It interprets echo "bar" as a command, and performs all the usual variable interpolation and quote interpretation. This strips the double quotes around "bar", so the argument to echo is just bar.

However, when cmd.exe processes a command after /c, it looks like it doesn't process quotes at all. If you have this Justfile:

set shell := ["cmd.exe", "/c"]

foo:
    echo "bar"

Which translates into cmd.exe being passed the argument echo "bar", it prints out "bar", so it's not stripping the quotes at all.

So I'm not sure how to make this work. I tried different combinations of quotes, spaces, carets (which is an escape character in cmd.exe) but couldn't get it to work.

Is using PowerShell an option for you? I think it has much saner quoting behavior than cmd.exe.

Also, if someone knows how to get this to work, definitely chime in.

I'm thinking that I should perhaps put the powershell instructions first in the readme, so that people are more likely to try it than cmd.exe, and maybe add a warning next to the cmd.exe instructions that it's weird and probably not what you want if you can use powershell.

@SuperCuber
Copy link

from cmd /?:

If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:

    1.  If all of the following conditions are met, then quote characters
        on the command line are preserved:

        - no /S switch
        - exactly two quote characters
        - no special characters between the two quote characters,
          where special is one of: &<>()@^|
        - there are one or more whitespace characters between the
          two quote characters
        - the string between the two quote characters is the name
          of an executable file.

    2.  Otherwise, old behavior is to see if the first character is
        a quote character and if so, strip the leading character and
        remove the last quote character on the command line, preserving
        any text after the last quote character.

I couldn't get it working with cmd /s /c either though...

@casey
Copy link
Owner Author

casey commented Jan 8, 2021

Same, I tried with and without /s, but no dice.

@SuperCuber
Copy link

SuperCuber commented Jan 8, 2021

I ran some more research, and this is really strange.
With a main.rs that just prints std::env::args, these (pasted into a cmd window) invocations are equivalent:

target\debug\testing.exe test

and

target\debug\testing.exe "test"

and

"target\debug\testing.exe" "test"

So here the quotes behave like in sh.

But these two invocations are different:

cmd /C target\debug\testing.exe "test"

and

cmd /C "target\debug\testing.exe" "test"

This is really strange to me. Is it special-casing cmd /c? What's going on here...


Also, following the answer here, this works as unexpected:

cmd /C ""target\debug\chesster.exe" "test""

To figure out what's happening, I ran

target\debug\testing.exe ""hello" "world""

and got

[src\main.rs:9] std::env::args().collect::<Vec<_>>() = [
    "target\\debug\\testing.exe",
    "hello world",
]

I'm completely lost.
EDIT: This could be becase it's interpreting it as (empty string) + hello + (literal space) + world + (empty string)... In that case looks like that SO answer is wrong and quotes are not preserved.

@casey
Copy link
Owner Author

casey commented Jan 9, 2021

I have a suspicion that the behavior when you type a command into the cmd.exe terminal, the behavior is different than when you run that command in the terminal as cmd.exe /s SOME COMMAND.

So when you do, in the terminal:

$ foo bar

There is a different quoting behavior as if you do:

$ cmd.exe /c foo bar

This is the only thing I can think of that might explain the inconsistency.

@casey
Copy link
Owner Author

casey commented Mar 25, 2021

I think that this is an inherent limitation of windows, and can't be addressed by making a change to Just, so I'm going to close this. Please feel free to re-open or comment if that's not the case!

@runeimp
Copy link

runeimp commented Mar 30, 2021

@casey
Copy link
Owner Author

casey commented Mar 31, 2021

@casey Yeah, Windows command line (any invocation) is crazy. See http://www.windowsinspired.com/summary-of-important-concepts-for-quoting-and-escaping-command-line-arguments/

Wow, thanks for this. My mind is blown, it's even nuttier than I thought.

@SuperCuber
Copy link

Windows strikes again...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants