I’m a self-confessed fan of Visual Studio Code’s dev container experience and have a number of posts about them including a list of some of my favourite things with dev containers. I find it productive to be able to capture the pre-requisites for working with a project programmatically, and share it with others working on the project. However, there’s a feature of dev containers that I use heavily which has the potential to break this model: dotfiles.
The dotfiles support in dev containers allows you to specify a dotfiles repo that VS Code will clone and install as part of the dev container build & startup. This allows you to do this such as configure your shell prompt, configure aliases, install extra tools, or whatever else your imagination conjures up!
I find that this feature allows me to make the experience in the dev container feel comfortable to me.
For example, I started out on a BBC micro model B (and then DOS) where
cls was the command to clear the screen and that muscle memory means that typing
clear still requires concentration, so I have an alias for
Along with my aliases, I also install a few tools that I generally like to have around, such as
Installing these tools is useful for times when I’m poking around on something trying to debug it, but it’s also the source of the potential problem: if these tools aren’t specified in the dev container then only I will have them available to me.
So if I create a script for use in the project and make use of one of these tools then it will work for me but no-one else!
As I commented in another post:
“This is an area to exercise caution. It is important to be mindful of what customisations you apply with this to ensure that they are not things that should be part of the core dev container experience”
Since writing that comment, I’ve done a pretty good job of being careful, but it remained an aspect of dotfiles that was niggling at me. I eventually got some time to play around with addressing that niggle, and in this post, I will walk through the approach I’ve taken with my dotfiles to try to reduce the chances of accidentally using a dotfiles-installed tool in a script.
My first thought was to create an alias for each tool installed and have the alias point to a
wrapper.sh script took the tool/command as the first argument and then remaining arguments that should be passed to the command.
When run, the script outputs a warning message and then calls on to the real tool.
# wrapper.sh tool_command=$1 shift echo "**** Using $tool_command ****" $tool_command $@
With the script in place, I created an alias. For example, to wrap the
jq command, I used the following:
alias jq=~/dotfiles/wrapper.sh jq. Any additional arguments passed to the
jq alias are passed to the wrapper script which passes them on to the real
Having hacked this together, I gave it a test from my bash prompt and it worked - the script output a warning and then called
jq passing the extra arguments along.
With hindsight, the fact that the script worked at this point might have started alarm bells ringing, but we’ll come back to that shortly.
My next test was to create a bash script that called
jq and test that the wrapper approach still worked there.
Narrator: it didn’t.
When running bash scripts, you can have them load aliases by adding
shopt -s expand_aliases, but if I have to add that to my scripts to get a warning when I use a tool that’s not installed by the dev container then… well, it’s not really a solution!
Side note… when the interactive test for this ran successfully earlier, if the
wrapper.shscript had been respecting the aliases it would have created an infinite loop as the script calls the command that was being aliased
Undeterred, I came up with a Plan B. Instead of aliasing the commands I want to wrap, I opted to create a script file for each command that is wrapped giving it the same name as the wrapped command. This script file outputs the warning and then invokes the original command.
The full script can be found here, but the key aspects are shown below:
# install-wrapper.sh # tool_command is passed to this script and is the command to wrap # get the actual command that should be run original_command=$(which $tool_command) # create the wrapper script with the same name as tool_command cat <<EOF > $script_dir/wrappers/$tool_command #!/bin/bash echo -e "\033[0;30;103m** using $TOOL from dotfiles **\033[0m" >&2 $original_command \$@ EOF chmod +x $script_dir/wrappers/$tool_command
When run for
install-wrapper.sh generates a script file called
jq in the
wrappers directory with the following content:
#!/bin/bash echo -e "\033[0;30;103m** using jq from dotfiles **\033[0m" >&2 /usr/bin/jq $@
With this in place, the dotfiles
load.sh updates the
PATH environment variable so that the
wrappers directory is searched ahead of the real installation directories to ensure that the wrapper is used.
The solution above works well for helping to prevent me using a tool that is installed by my dotfiles. A warning message is output whenever a wrapped command is executed, prompting me to add that tool to the dev container definition before committing a script that makes use of it.
However, there is one final tweak to make: since I only care about preventing the use of dotfiles-installed tools when they are use in scripts, I would happily not get a warning when I use such a tool interactively.
Fortunately, there is a way to achieve that… and it makes use of the mistake I made in my original attempt: aliases!
In my final
install-wrapper.sh, I include the following extra step:
echo "alias $tool_command='$original_command'" >> $script_dir/wrappers/.aliases
This adds an alias for the wrapped command that directly calls the installed tool using the qualified path.
The dotfiles load script then loads these aliases from the generated
So, using a wrapped command interactively resolves to the alias, which in turn calls the installed tool directly.
When using a wrapped command in a script, the aliases aren’t loaded (as we discovered in the first attempt) and the command resolution finds the wrapper script that was added to the
PATH which outputs the warning as desired.
Mission accomplished… my interactive use of tools is unaffected, and I get a handy nudge if I’m using a tool that isn’t installed by the dev container.