bashdockerash

docker sh vs bash variable ${@} expansion - ${@} doesn't work in sh


It's really straightforward: I am trying to build a docker image where the entrypoint receives the args passed. When I use a bash shell, it works. When I use sh which comes with alpine it doesn't expand; and I think it's because sh takes no args at all

Is this possible? And, how?

This doesn't work; meaning docker run -it myimage arg1 arg2 doesn't receive arg1 arg2 as the ${@} requires. I would expect that gradlew would be invoked as ./gradlew arg1 arg2

FROM openjdk:8u212-jre-alpine
RUN apk add --no-cache jq openjdk8 python3 \
    && pip3 install --upgrade pip && pip3 install yq
COPY . /root
WORKDIR /root
RUN ./gradlew dependencies &> /dev/null
ENTRYPOINT [ "sh", "-c", "./gradlew ${@}" ]

and this does:

FROM gradle
COPY . /home/gradle
RUN ./gradlew -v && ./gradlew dependencies &> /dev/null
ENTRYPOINT [ "bash", "-c", "./gradlew ${@}" ]

When I dig into what sh really is:

/bin # which sh
/bin/sh
/bin # ls -al /bin/sh
lrwxrwxrwx    1 root     root            12 May  9  2019 /bin/sh -> /bin/busybox

Solution

  • I don't know anything about "gradle", so let's replace your call to gradlew with an echo statement in order to diagnose things:

    FROM openjdk:8u212-jre-alpine
    ENTRYPOINT [ "sh", "-c", "echo ${@}" ]
    

    If I build an image named cbtest and run it like this:

    docker run --rm cbtest arg1 arg2
    

    I get as output:

    arg2
    

    Which means it's mostly working, except as I noted in the comments you're losing your first argument. If we take a look at the bash(1) man page, we find:

    -c  If  the  -c option is present, then commands are read from the
        first non-option argument  command_string.   If  there  are
        arguments  after  the  command_string,  the  first  argument is
        assigned to $0 and any remaining argu‐ ments are assigned to the
        positional parameters.  The assignment to $0  sets the name of the
        shell, which is used in warning and error messages.
    

    The key phrase is the first argument is assigned to $0. The argument $0 is the name of the currently running program, rather than an argument to the program. You need to add a -- to your sh command line to indicate that it should stop trying to parse arguments and just pass everything as parameters to the called script. Again from the man page:

    --  A -- signals the end of options and disables further option
        processing.  Any arguments  after the -- are treated as filenames
        and arguments.  An argument of - is equivalent to --.
    

    This gives us:

    FROM openjdk:8u212-jre-alpine
    ENTRYPOINT [ "sh", "-c", "echo ${@}", "--" ]
    

    If I run the same command as above, I now get:

    arg1 arg2
    

    So that's great.

    I mentioned in the comments that you're misusing the ${@} variable because you're not quoting it. You might think you are, but the /bin/sh you're calling doesn't know anything about those quotes; the command passed to the shell is simply:

    ./gradlew ${@}
    

    ...with no quotes around ${@}. Let's modify the script slightly so that we can see the difference; consider this script:

    FROM openjdk:8u212-jre-alpine
    ENTRYPOINT [ "sh", "-c", "for arg in ${@}; do echo $arg; done", "--" ]
    

    If I run the image like this:

    docker run --rm cbtest "arg with spaces" "second arg"
    

    I get as output:

    arg
    with
    spaces
    second
    arg
    

    As you can see, the script is seeing five arguments rather than two. We can fix that with proper quoting:

    FROM openjdk:8u212-jre-alpine
    ENTRYPOINT [ "sh", "-c", "for arg in \"${@}\"; do echo $arg; done", "--" ]
    

    Which given the above input produces:

    arg with spaces
    second arg
    

    So your ENTRYPOINT should probably look like this:

    ENTRYPOINT [ "sh", "-c", "./gradlew \"${@}\"", "--" ]