Powershell Docker Command Doesn't Work Like Bat File
Asked Answered
I

1

1

I'm trying to create a script that starts a docker container and mounts a local folder that contains spaces in the name. I can get it to work fine when I run a *.bat file with the docker run command:

docker run -p 8081:8081 -v "C:\Test Folder With Blanks":/zzz myimage jupyter lab --notebook-dir=/zzz--ip=0.0.0.0 --port=8081 --allow-root

But when I try to do the same in a Powershell script file, I get an error:

$CMD = 'docker run -p 8081:8081 -v "C:\Test Folder With Blanks":/zzz myimage jupyter lab --notebook-dir=/zzz--ip=0.0.0.0 --port=8081 --allow-root'
    
Invoke-Expression $CMD
docker: invalid reference format.
See 'docker run --help'.

I'm on Win10 and running Powershell in Visual Studio Code IDE.

Thanks for ideas.

Izawa answered 20/12, 2020 at 1:32 Comment(0)
M
1

First, the obligatory warning: Unless you have a command line stored as a single string somewhere and you either fully control or trust the content of the string, Invoke-Expression should generally be avoided.


You're seeing an inconsistency in how PowerShell treats compound tokens composed of directly concatenated quoted and unquoted parts.

Specifically, argument "C:\Test Folder With Blanks":/zzz is unexpectedly broken in two, i.e passed as two arguments.

The workaround is to quote the entire argument, i.e.
"C:\Test Folder With Blanks:/zzz"

Note: I'm assuming that docker doesn't actually require partial quoting in its arguments, which it shouldn't; however, there are high-profile CLIs on Windows that do, notably msiexec.

Alternatively, use an expression enclosed in (...) to compose your string; e.g.
("C:\Test Folder With Blanks" + ':/zzz')

There's no good reason to do so in this case, but it could be helpful if you need string interpolation in one part of your string ("..."), but not in another ('...').

General caveats:

  • Compared to cmd.exe and also POSIX-compatible shells such as bash, PowerShell has several additional metacharacters, notably @ (at the start of a token), { / }, and ;. Therefore, you cannot always expect command lines written for these shells to work as-is in PowerShell.

  • As of PowerShell 7.2.2, passing arguments to external programs (such as docker) is fundamentally broken with respect to arguments that have embedded " characters and empty-string arguments - see this answer.


The general pattern of the inconsistency, as of PowerShell 7.2.2, is as follows:

If an argument:

  • starts with a quoted token - whether single- ('...') or double-quoted ("...") -
  • and has additional characters directly following the closing quote,

the first such character starts a separate argument.

E.g. "foo":bar / "foo"=bar / "foo"'bar' are passed as separate arguments foo and :bar / foo and =bar / foo and bar, respectively.

In other words:

  • You cannot compose a single string argument from a mix of quoted and unquoted / differently quoted tokens if the first token is quoted.

  • Conversely, it does work if the first token is unquoted, including an unquoted simple variable reference such as $HOME.

# OK: First token is unquoted.
PS> cmd /c echo foo"bar"'baz'last
foobarbazlast

# !! BROKEN: First token is quoted.
# !!         Because each successive token is quoted too, 
# !!         each becomes its own argument.
PS> cmd /c echo 'foo'"bar"'baz'last
foo bar baz last

GitHub issue #6467 discusses this inconsistency; however, it has been closed, because the behavior is - surprisingly - considered by design.


This does not happen if the first token is unquoted; however, there are related bugs that start with unquoted tokens that similarly break arguments in two, related to their looking like named arguments to PowerShell (which is a PowerShell concept that doesn't apply when calling external programs):

Margarite answered 20/12, 2020 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.