Published at
Updated at
Reading time

I went into the rabbit hole of writing shell scripting today. ๐Ÿ™ˆ The goal: saving keystrokes and manually ran commands when starting to work on a project.

Here's the result. ๐Ÿ‘‡

Terminal session showing the `dev` command with auto-completion.

The video above shows a custom dev command that:

  • navigates into a project
  • runs npm run dev
  • opens VS Code

The exciting part is that it supports auto-completion to pass paths to projects with a working npm run dev command quickly.

A shell function to run multiple commands

To define a custom shell function that runs various commands add the following lines to your .zshrc or .bashrc.

function dev() {
  echo "Running 'npm run dev' in $1 ..."
  cd ~/Projects/"$1" || return
  code .
  npm run dev

Run source ~/.zshrc/source ~/.bashrc or open a new terminal session to load the new configuration. Tada!!! You now have a new dev command available.

First step; done! Let's come to the tricky part โ€“ the auto-completion...

Defining custom auto-completion for your function

Disclaimer: if you follow the instructions in this article, keep in mind that my setup heavily relies on ohmyzsh. There is a chance that the described steps don't work in your environment.

If you start reading about auto-completion functionality, you'll learn that the recommended way is to add "completion files" to your system. These files should follow the convention of starting with an underscore. As an example, the file _dev acts as the auto-completion for the dev command. Additionally, the files have to start with the line #compdef.

Alternatively, you can also use the compdev command, but I didn't explore it and I actually like to have a separate file for every completion.

Let's have a look at my _dev completion file.

#compdef dev

# Cache the parsing of the projects directory for repeated use
# The first auto-completion for `dev` will be slow
#   because all projects have to be scanned.
# After the first run it's speedy

function _dev() {
  # only iterate over projects if cached results are empty
  if [ ${#__DIRS_WITH_DEV_COMMAND[@]} -eq 0 ]; then
    # iterate over directory including all coding projects
    for i in ~/Projects/*; do
      if [ -f "$i/package.json" ]; then
        # parse the package.json and see if `npm run dev` is available
        DEV_COMMAND=$(jq "" < "$i/package.json")

        if [ "$DEV_COMMAND" != "null" ]; then
          __DIRS_WITH_DEV_COMMAND+=("$(basename "$i")")

  # set auto completion result
  _describe 'command' __DIRS_WITH_DEV_COMMAND

The script iterates over my ~/Projects directory and checks if the included sub-directories include a package.json file that then defines a dev command. To define the completion result _describe 'command' is ran with an array of completion options.

This basic functionality worked great but was fairly slow because every completion attempt read and parsed all project directories. To speed things up, I defined "a cache variable" outside of the dev function __DIRS_WITH_DEV_COMMAND. I'm sure there's a better way to cache completion results, but it does the trick for me.

But where should you place the _dev file then? After reading the following guide, I learned that the $fpath variable defines where completion files are located.

echo $fpath with highlighted path /Users/stefanjudis/.oh-my-zsh/completions

I went with adding my completion files to ~/.oh-my-zsh/completions. And voilร  โ€“ I could now "tab around" to quickly start working on a project on my machine! ๐ŸŽ‰


Let's sum up what it took me to add a custom dev function with auto-completion.

  • define a function in your .zshrc/.bashrc
  • create a completion file in a directory that's included in $fpath
  • wrestle with the art of shell scripting and define your command completion results

And that's pretty much it (it still took me ages to figure out, though ๐Ÿ™ˆ). If you have comments on improving these scripts, I'd love to hear them.

And lastly, if you like these quick tricks, I send a weekly newsletter all around web development and love to have you read along!

Was this snippet helpful?
Yes? Cool! You might want to check out Web Weekly for more snippets. The last edition went out 4 days ago.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles