meowsqueak meowsqueak - 2 months ago 37
Bash Question

Passing a command with arguments as a string to docker run

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?

Answer

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).