Search code examples
bashdockerpathwhitespacewrapper

In this simple Docker wrapper script example, how may one correctly pass a current working directory path which contains spaces?


My Docker wrapper script works as intended when the current working directory does not contain spaces, however there is a bug when it does.

I have simplified an example to make use of the smallest official Docker image I could find and a well known GNU core utility. Of course this example is not very useful. In my real world use case, a much more complicated environment is packaged.

Docker Wrapper Script:

#!/usr/bin/env bash
##
## Dockerized ls
##

set -eux

# Only allocate tty if one is detected
# See https://stackoverflow.com/questions/911168/how-to-detect-if-my-shell-script-is-running-through-a-pipe
if [[ -t 0 ]]; then
    DOCKER_RUN_OPTIONS+="-i "
fi
if [[ -t 1 ]]; then
    DOCKER_RUN_OPTIONS+="-t "
fi

WORK_DIR="$(realpath .)"

DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source=${WORK_DIR},target=${WORK_DIR}"

exec docker run ${DOCKER_RUN_OPTIONS} busybox:latest ls "$@"

You can save this somewhere as /tmp/docker_ls for example. Remember to chmod +x /tmp/docker_ls

Now you are able to use this Dockerized ls in any path which contains no spaces as follows:

/tmp/docker_ls -lah
/tmp/docker_ls -lah | grep 'r'

Note that /tmp/docker_ls -lah /path/to/something is not implemented. The wrapper script would have to be adapted to parse parameters and mount the path argument into the container.

Can you see why this would not work when current working directory path contains spaces? What can be done to rectify it?

Solution:

@david-maze's answer solved the problem. Please see: https://stackoverflow.com/a/55763212/1782641

Using his advice I refactored my script as follows:

#!/usr/bin/env bash
##
## Dockerized ls
##

set -eux

# Only allocate tty if one is detected. See - https://stackoverflow.com/questions/911168
if [[ -t 0 ]]; then IT+=(-i); fi
if [[ -t 1 ]]; then IT+=(-t); fi

USER="$(id -u $(logname)):$(id -g $(logname))"
WORKDIR="$(realpath .)"
MOUNT="type=bind,source=${WORKDIR},target=${WORKDIR}"

exec docker run --rm "${IT[@]}" --user "${USER}" --workdir "${WORKDIR}" --mount "${MOUNT}" busybox:latest ls "$@"

Solution

  • If your goal is to run a process on the current host directory as the current host user, you will find it vastly easier and safer to use a host process, and not an isolation layer like Docker that intentionally tries to hide these things from you. For what you’re showing I would just skip Docker and run

    #!/bin/sh
    ls "$@"
    

    Most software is fairly straightforward to install without Docker, either using a package manager like APT or filesystem-level isolation like Python’s virtual environments and Node’s node_modules directory. If you’re writing this script then Docker is just getting in your way.


    In a portable shell script there’s no way to make “a list of words” in a way that keeps their individual wordiness. If you know you’ll always want to pass some troublesome options then this is still fairly straightforward: include them directly in the docker run command and don’t try to create a variable of options.

    #!/bin/sh
    RM_IT="--rm"
    if [[ -t 0 ]]; then RM_IT="$RM_IT -i"; fi
    if [[ -t 1 ]]; then RM_IT="$RM_IT -t"; fi
    UID=$(id -u $(logname))
    GID=$(id -g $(logname))
    
    # We want the --rm -it options to be expanded into separate
    # words; we want the volume options to stay as a single word
    docker run $RM_IT "-u$UID:$GID" "-w$PWD" "-v$PWD:$PWD" \
      busybox \
      ls "$@"
    

    Some shells like ksh, bash, and zsh have array types, but these shells may not be present on every system or environment (your busybox image doesn’t have any of these for example). You also might consider picking a higher-level scripting language that can more explicitly pass words into an exec type call.