Passing a command with arguments as a string to docker run
Asked Answered
T

1

11

The issue I'm facing is how to pass a command with arguments to docker run. The problem is that docker run does not take command plus arguments as a single string. They need to be provided as individual first-class arguments to docker run, such as:

#!/bin/bash
docker run --rm -it myImage bash -c "(cd build && make)"

However consider the command and argument as the value of a variable:

#!/bin/bash -x
DOCKER_COMMAND='bash -c "(cd build && make)"'
docker run --rm -it myImage "$DOCKER_COMMAND"

Unfortunately this doesn't work because docker run doesn't understand the substitution:

+ docker run --rm -it myImage 'bash -c "(cd build && make)"'
docker: Error response from daemon: oci runtime error: exec: "bash -c \"(cd build && make)\"": stat bash -c "(cd build && make)": no such file or directory.

A slight change, removing the quotation of DOCKER_COMMAND:

#!/bin/bash -x
DOCKER_COMMAND='bash -c "(cd build && make)"'
docker run --rm -it myImage $DOCKER_COMMAND

Results in:

+ docker run --rm -it myImage 'bash -c "(cd build && make)"'
build: -c: line 0: unexpected EOF while looking for matching `"'
build: -c: line 1: syntax error: unexpected end of file

How can I expand a string from a variable so that it is passed as a distinct command and arguments to docker run inside a script?

Trichotomy answered 12/9, 2016 at 5:36 Comment(3)
I think you should strip your question down to the bare minimum. (e.g. why would it be important that docker is involved.) To me it sounds like: "How can I parse parameters to my script using environment variables."Gobi
@MichaWiedenmann thanks for your comment. It's actually more about how Docker accepts arguments, and how to extract these from an environment variable. This normally doesn't come up because most commands can be build from quoted strings, but docker requires separate arguments. I'll add some background.Trichotomy
@MichaWiedenmann ok, I've simplified it considerably.Trichotomy
M
12

Start with the syntax of the docker run command, which is:

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

This means if you run:

DOCKER_COMMAND='bash -c "(cd build && make)"'
docker run --rm -it myImage "$DOCKER_COMMAND"

You are passing the entirety of the $DOCKER_COMMAND variable as the COMMAND. You are asking Docker to find a file matching the name bash -c "(cd build && make)", so it should be no surprise that it fails. It doesn't have anything to do with "docker run doesn't understand the substitution". This is all related to the way your shell parses command lines before executing them.

When you remove the quotes around $DOCKER_COMMAND, you end up calling it like this (I'm putting each argument on a separate line to make it obvious):

docker
run
--rm
-it
myImage
bash
-c
"(cd
build
&&
make)"

And that's not going to work, because bash is going to try to run the script "(cd, which should make obvious the reason for the unexpected EOF while looking for matching"'error. Bash's-c` option only takes a single argument, but because of the way shell expansion works it's getting 4.

You could do it this way:

DOCKER_COMMAND='cd build && make'
docker run --rm -it myImage bash -c "$DOCKER_COMMAND"

(I've removed the parentheses around your command because they don't do anything the way you're using them.)

This way, you're calling docker run with a command of bash, and you're giving bash's -c option a single argument (the contents of the $DOCKER_COMMAND variable).

Matejka answered 13/9, 2016 at 1:25 Comment(1)
Thank you in particular for the explanation as it helps me understand the difference between these two forms. I have adopted your suggestion of passing the string to bash -c, which suits my application well.Trichotomy

© 2022 - 2024 — McMap. All rights reserved.