4
\$\begingroup\$

I have written a simple wrapper for docker build and docker run that allows me to build and run my image without having to use the docker command directly.

Here's the layout of my code:

esp8266
├── scripts
│ ├── config.sh <- configuration
│ └── util.sh <- utility functions
├── docker <- docker image root
│  └── Dockerfile
├── build <- script to build image
└── make <- script to run image

And the files themselves:

scripts/config.sh

readonly DOCKER_IMAGE_NAME="jackwilsdon/esp8266"
readonly DOCKER_IMAGE_TAG="0.1.0"
readonly DOCKER_IMAGE_MOUNT="/mnt/data"
readonly DOCKER_LOCAL_SOURCE_DIRECTORY="docker"
readonly DOCKER_LOCAL_MOUNT_DIRECTORY="data"

scripts/util.sh

#!/usr/bin/env bash
#
# Utilities for esp8266 development container scripts
# usage: print [args...]
print() {
 echo "${CURRENT_SCRIPT}: $*"
}
# usage: print_raw [args...]
print_raw() {
 echo "$*"
}
# usage: error [args...]
error() {
 print "error: $*" >&2
}
# usage: error_raw [args...]
error_raw() {
 print_raw "$*" >&2
}
# usage: check_requirements
check_requirements() {
 if [[ -z "${CURRENT_SCRIPT}" ]]; then
 error "CURRENT_SCRIPT not set"
 exit 1
 fi
 if [[ -z "${CURRENT_DIRECTORY}" ]]; then
 error "CURRENT_DIRECTORY not set"
 exit 1
 fi
 if [[ -z "${DOCKER_BINARY}" ]]; then
 error "unable to find docker (DOCKER_BINARY not set)"
 return 1
 fi
}
# usage: docker [args...]
docker() {
 "${DOCKER_BINARY}" "$@"
 local exit_code="$?"
 if [[ "${exit_code}" -ne 0 ]]; then
 error "${DOCKER_BINARY} exited with code ${exit_code}"
 return 1
 fi
}
# usage: arg_or_default arg default [new arg value]
arg_or_default() {
 if [[ -n "1ドル" ]]; then
 if [[ "$#" -eq 3 ]]; then
 echo "3ドル"
 else
 echo "1ドル"
 fi
 else
 echo "2ドル"
 fi
}

build

#!/usr/bin/env bash
#
# Build esp8266 development container
readonly CURRENT_SCRIPT="$(basename -- ${BASH_SOURCE[0]})"
readonly CURRENT_DIRECTORY="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly DOCKER_BINARY="$(command -v docker)"
source "${CURRENT_DIRECTORY}/scripts/config.sh"
source "${CURRENT_DIRECTORY}/scripts/util.sh"
# usage: usage [printer]
usage() {
 local printer="$(arg_or_default "1ドル" 'print_raw')"
 "${printer}" "usage: ${CURRENT_SCRIPT} [-h] [TAG]"
}
# usage: full_usage [printer]
full_usage() {
 local printer="$(arg_or_default "1ドル" 'print_raw')"
 usage "${printer}"
 "${printer}"
 "${printer}" 'Build tool for esp8266 development container'
 "${printer}"
 "${printer}" 'arguments:'
 "${printer}" ' -h show this help message and exit'
 "${printer}" ' TAG the tag of the image to build'
}
# usage: build_image [tag]
build_image() {
 # Generate image name
 local name="${DOCKER_IMAGE_NAME}:$(arg_or_default "1ドル" \
 "${DOCKER_IMAGE_TAG}")"
 print "building image ${name}"
 # Run docker with the provided arguments
 docker build -t "${name}" \
 "${CURRENT_DIRECTORY}/${DOCKER_LOCAL_SOURCE_DIRECTORY}"
}
# usage: main [-h] [-d DATA_DIRECTORY] [-t TAG] [ARGS...]
main() {
 check_requirements "$@" || exit 1
 while getopts ':h' OPT; do
 case "${OPT}" in
 h)
 full_usage
 exit 0
 ;;
 ?)
 full_usage
 print
 error "invalid argument: ${OPTARG}"
 exit 1
 ;;
 esac
 done
 shift $((OPTIND - 1))
 build_image "$@"
}
if [[ "0ドル" == "${BASH_SOURCE[0]}" ]]; then
 main "$@"
fi

make

#!/usr/bin/env bash
#
# Run esp8266 development container
readonly CURRENT_SCRIPT="$(basename -- ${BASH_SOURCE[0]})"
readonly CURRENT_DIRECTORY="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly DOCKER_BINARY="$(command -v docker)"
source "${CURRENT_DIRECTORY}/scripts/config.sh"
source "${CURRENT_DIRECTORY}/scripts/util.sh"
# usage: usage [printer]
usage() {
 local printer="$(arg_or_default "1ドル" 'print_raw')"
 "${printer}" "usage: ${CURRENT_SCRIPT} [-h] [-d DATA_DIRECTORY] [-t TAG]" \
 "[ARGS...]"
}
# usage: full_usage [printer]
full_usage() {
 local printer="$(arg_or_default "1ドル" 'print_raw')"
 usage "${printer}"
 "${printer}"
 "${printer}" 'Make tool for esp8266 development container'
 "${printer}"
 "${printer}" 'arguments:'
 "${printer}" ' -h show this help message and exit'
 "${printer}" ' -d DATA_DIRECTORY mount point for /mnt/data inside the'
 "${printer}" ' container'
 "${printer}" ' -t TAG the tag of the image to run'
 "${printer}" ' ARGS... the command to run in the container'
}
# usage: run_image [data_directory] [tag] [args...]
run_image() {
 local arguments=(run --rm --interactive --tty --entrypoint /sbin/my_init)
 # Generate volume and image names
 local volume="$(arg_or_default "1ドル" \
 "${CURRENT_DIRECTORY}/${DOCKER_LOCAL_MOUNT_DIRECTORY}")"
 local name="${DOCKER_IMAGE_NAME}:$(arg_or_default "2ドル" \
 "${DOCKER_IMAGE_TAG}")"
 # Add volume and name to arguments
 arguments+=(-v "${volume}:/mnt/data" ${name})
 # Remove the first two arguments from the argument list
 shift 2
 # Run docker with the provided arguments
 docker "${arguments[@]}" -- "$@"
}
# usage: main [-h] [-d DATA_DIRECTORY] [-t TAG] [ARGS...]
main() {
 check_requirements "$@" || exit 1
 local docker_volume=''
 local docker_image_tag=''
 while getopts ':hd:t:' OPT; do
 case "${OPT}" in
 h)
 full_usage
 exit 0
 ;;
 d)
 docker_volume="${OPTARG}"
 ;;
 t)
 docker_image_tag="${OPTARG}"
 ;;
 ?)
 full_usage error_raw
 error_raw
 error "invalid argument: ${OPTARG}"
 exit 1
 ;;
 esac
 done
 shift $((OPTIND - 1))
 run_image "${docker_volume}" "${docker_image_tag}" "$@"
}
if [[ "0ドル" == "${BASH_SOURCE[0]}" ]]; then
 main "$@"
fi

This image is primarily designed for development and ease of use, which is why I have created these shell scripts.

Here's some examples of how to use the scripts:

$ ./build -h
usage: build [-h] [TAG]
Build tool for esp8266 development container
arguments:
 -h show this help message and exit
 TAG the tag of the image to build
$ ./build example_tag
build: building image jackwilsdon/esp8266:example_tag
Sending build context to Docker daemon 3.584 kB
Step 1 : FROM phusion/baseimage:latest
 ---> c39664f3d4e5
Step 2 : MAINTAINER Jack Wilsdon <[email protected]>
 ---> Using cache
 ---> 4ec300220c6c
...
Step 7 : VOLUME /mnt/data
 ---> Using cache
 ---> f9f8a21a0d16
Successfully built f9f8a21a0d16
$ ./make -h
usage: make [-h] [-d DATA_DIRECTORY] [-t TAG] [ARGS...]
Make tool for esp8266 development container
arguments:
 -h show this help message and exit
 -d DATA_DIRECTORY mount point for /mnt/data inside the
 container
 -t TAG the tag of the image to run
 ARGS... the command to run in the container
$ ./make whoami
*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh...
*** Running /etc/rc.local...
*** Booting runit daemon...
*** Runit started as PID 9
*** Running whoami...
root
*** whoami exited with status 0.
*** Shutting down runit daemon (PID 9)...
*** Killing all processes...

I'm rather new to shell scripting, however I'd like to ensure my scripts are as robust as possible, is there anything I can do to improve their robustness?

asked Aug 5, 2016 at 1:54
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Overloading the meaning of make might come back and bite you one day. \$\endgroup\$ Commented Jan 11, 2017 at 11:51

2 Answers 2

2
\$\begingroup\$

Looking very good. Honestly I only have nitpicks.


The purpose of the 3rd argument of arg_or_default is unclear. From the comment in the code I don't understand it, and it's not used by any of the scripts. Either explain it better in the comment, or drop it, since you're not using it anyway.


The == operator in [[ is for pattern matching. When you don't need to match a pattern but a literal string, as in [[ "0ドル" == "${BASH_SOURCE[0]}" ]], then you can replace it with a single =.


You don't need to double-quote the $? variable, it's always safe.

You don't need to quote string literals like print_raw.

You don't need the single-quote pairs when setting a variable to empty as in docker_volume='', you can write simply docker_volume=

answered Aug 5, 2016 at 6:57
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the feedback! I didn't know you could omit the quotes for empty variables, cool! \$\endgroup\$ Commented Sep 27, 2016 at 23:10
2
\$\begingroup\$

This is excellent. My only suggestion would be to replace:

readonly CURRENT_SCRIPT="$(basename -- ${BASH_SOURCE[0]})"

with:

readonly CURRENT_SCRIPT="$(basename -- "${BASH_SOURCE[0]}")"

to avoid basename misbehaving if the script name contains a whitespace.

answered Jan 12, 2022 at 17:51
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.