itchio / butler

🎩 Command-line itch.io helper
MIT License
745 stars 52 forks source link

Windows-style path separators (backslashes) give error #188

Closed Deozaan closed 5 years ago

Deozaan commented 5 years ago

When using the default Windows path separators (backslash) to push with butler, I get an error saying:

butler: error: required argument 'target' not provided

Changing the backslashes to forward slashes in the path allows things to push properly.

It seems that butler doesn't understand that strings surrounded in single-quotes should be interpreted literally and not be escaped.

Example:

> butler push '.\Game Dir\' username/game:win32
butler: error: required argument 'target' not provided

> butler push './Game Dir/' username/game:win32
∙ For channel `win32`: pushing first build
∙ Pushing 64.05 MiB (117 files, 33 dirs, 0 symlinks)
√ Added 64.05 MiB fresh data
√ 25.20 MiB patch (60.65% savings)
∙ Build is now processing, should be up in a bit.

Use the `butler status username/game:win32` for more information.
fasterthanlime commented 5 years ago

So, first of all: the shell is responsible for splitting arguments and passing them to applications, butler just uses that.

Second, I'm confused which shell you're using currently, because neither bash nor cmd.exe behave like that.

bash

In bash, single quotes are valid, if we take this test script:

#!/bin/bash
echo "Argument 1 = ($1)"
echo "Argument 2 = ($2)"
echo "Argument 3 = ($3)"

We get:

$ ./test.sh '.\Game Dir\' username/game:win32
Argument 1 = (.\Game Dir\)
Argument 2 = (username/game:win32)
Argument 3 = ()

The only way to reproduce the output you're seeing would be to move the backslash after the single quote, in which case it would act as an escape for the space, and we'd see:

$ ./test.sh '.\Game Dir'\ username/game:win32
Argument 1 = (.\Game Dir username/game:win32)
Argument 2 = ()
Argument 3 = ()

... which is consistent with the error message you're posting.

cmd.exe

In cmd.exe, single quotes don't have any special meaning, so your whole command is just space-separated.

If we take test.bat:

@echo Argument 1 = (%1)
@echo Argument 2 = (%2)
@echo Argument 3 = (%3)

We get:

> test.bat '.\Game Dir\' username/game:win32
Argument 1 = ('.\Game)
Argument 2 = (Dir\')
Argument 3 = (username/game:win32)

Which would give a different butler error message:

butler: error: unexpected username/game:win32

..which is also correct, because it expects two arguments, not three.

PowerShell

Since I don't believe you're making up bug reports, I tried PowerShell, and it does behave as you've posted.

The test.bat gives this:

> .\test.bat '.\Game Dir\' username/game:win32
Argument 1 = (".\Game Dir\")
Argument 2 = (username/game:win32)
Argument 3 = ()

And a sample test.c, like so:

#include <stdio.h>

int main(int argc, char **argv) {
    for (int i = 0; i < argc; i++) {
        printf("Argument %d = (%s)\n", i, argv[i]);
    }
    return 0;
}

Gives us this:

> .\test '.\Game Dir\' username/game:win32
Argument 0 = (C:\msys64\home\amos\Dev\butler\test.exe)
Argument 1 = (.\Game Dir" username/game:win32)

Which is interesting. Additionally, if I change butler's main function to print its arguments, like this:

func main() {
    for i, arg := range os.Args {
        log.Printf("Arg %d: %q", i, arg)
    }
    doMain(os.Args[1:])
}

Then we see:

> butler push '.\Game Dir\' username/game:win32
2019/05/02 13:10:14 Arg 0: "C:\\Users\\amos\\go\\bin\\butler.exe"
2019/05/02 13:10:14 Arg 1: "push"
2019/05/02 13:10:14 Arg 2: ".\\Game Dir\" username/game:win32"
butler: error: required argument 'target' not provided

Which also matches my expectations.

The cause

It has been mentioned before - cmd.exe has different rules for "tokenizing" arguments than CommandLineToArgvW, which is what pretty much all native programs use.

If you pass 'a\' b to a native program, it'll receive "a\" b as a single argument.

The fix

This isn't a bug in butler, or Go, or windows. It's just cmd.exe/PowerShell having silly rules. The fix is to simply not have a trailing backslash at the end of your arguments. I wish it was my fault so that I could fix it, but it's not, and adding workarounds toe butler's codebase would make the situation even more confusing! (as it would behave differently from every other native program out there).

Deozaan commented 5 years ago

Yes, I was using PowerShell. Thanks for the investigative work, and the explanation.

I'll keep in mind to avoid the trailing backslash at the end of my path.