Generating templates with bash

by Camilo OrregoSep 11th, 20208 min read

Introduction

Usually, when projects reach a stage of maturity in architecture, each part or component shares a base structure. Sometimes, these components are composed by multiple files with different dependencies, and it can be cumbersome to create these new files each time you start working in something new.

In this post, we'll look at how to create a simple script that can be very helpful in any project to create boilerplate.

TL;DR

If you just want the script, click here. Adapt it to your project, create the templates you want!

Exercise

For this example, we'll use a frontend project. The requirements for the script are:

  • It must receive the name of the component as an argument. If the name is not provided, show a message.
  • It must create a folder with the name of the component, and inside it will create 3 files: js, html and css.
  • If the component already exists, the script ends.
  • It should apply the component name in specific parts of the generated files.

Important: Make sure your command line support the programs. They should be installed by default in any Unix based system.

Folder Structure

This is the folder structure that we're going to use in this example

├── components
└── scripts
    ├── new-component
    └── templates
        ├── component.css
        ├── component.html
        └── component.js
  • Inside components we'll have the generated components.
  • new-component is the script and we'll focus all our work there.
  • The templates folder contains the templates for each file. We'll use a special placeholder __COMPONENT_NAME__ to replace them with our new component name. You can set any name you want for the placeholder. I like to use verbose names, but you can use something simpler.

The templates will have the structure defined in your project. For this exercise they will be very simple.

component.js

import template from './__COMPONENT_NAME__.html'
import './__COMPONENT_NAME__.css'

class __COMPONENT_NAME__ {}

component.html

<div class="__COMPONENT_NAME__">
  __COMPONENT_NAME__ component
</div>

component.css

.__COMPONENT_NAME__ {
  padding: 0;
  margin: 0;
}

Running the script

From now, we'll focus on scripts/new-component. Let's add some code to the script and see what's happening.

#!/bin/bash
echo "hello world"

echo will print whatever is passed in the terminal. To run the script, put this command in your terminal:

./scripts/new-component

Probably you'll get an error about permission denied. You can fix this by changing the permissions of the script.

chmod +x ./scripts/new-component

Now we can run the script again and it should show hello world.

Tip: if you want to persist this permission change in git, you can run this command.

git update-index --chmod=+x scripts/new-component

Passing arguments

Now, let's add the component name to the script. Arguments in scripts are accessed by using $ followed by the number of the argument (starting in 1, because 0 is the script path called in the terminal).

Let's change our script to show the component name.

#!/bin/bash
echo "$1"

If we run ./scripts/new-component SidePanel, it will show SidePanel.

Validating that the argument exists

We can use the old if to achieve this. In bash, this is the syntax for a conditional.

if [ <condition> ]; then
  <commands>
fi

Inside the square brackets, you can use a condition based on the command test. If you want to know more about this command (or any command), you can use man (like manual) to access the documentation of the commands (example: man test).

To check if an argument is empty, we can use -z. Let's add some lines to check if it's empty and then, show a message to the user.

#!/bin/bash
echo "$1" # remove this line
if [ -z "$1" ]; then  echo "No component name supplied."  echo "Example: ./new-component MyComponent"  exit 1fi

exit 1 is the trick to stop the execution of the following code. Think about it as a return. exit 0 is success. 1 is generally used for general errors.

Creating variables

Using $1 everywhere is not very clear, so let's create a variable with a better name. Here's the syntax for variables in bash.

<VARIABLE_VALUE>=<VALUE>

As a tip, if your variables are private to your script, go for lowercase. In this way, you won't end up overwriting environment variables.

#!/bin/bash
name="$1"
if [ -z "$name" ]; then  echo "No component name supplied."
  echo "Example: ./new-component MyComponent"
  exit 1
fi

Checking if the folder already exists

Before that, we need to know what's the target directory. For that, let's create a variable that points out to the components folder and another one for the new component directory name.

#!/bin/bash
name="$1"

if [ -z "$name" ]; then
  echo "No component name supplied."
  echo "Example: ./new-component MyComponent"
  exit 1
fi

components_folder=./componentsnew_component_folder="$components_folder/$name"

Now we need to check if the target directory path exists. Checking the test documentation:

-d file       True if file exists and is a directory.

So let's add another conditional for new_component_folder:

#!/bin/bash
name="$1"

if [ -z "$name" ]; then
  echo "No component name supplied."
  echo "Example: ./new-component MyComponent"
  exit 1
fi

components_folder=./components
new_component_folder="$components_folder/$name"

if [ -d "$new_component_folder" ]; then  echo "Component $name already exists."  exit 1fi

Creating new files

Now for the final part, we're gonna use mkdir (to create the folder) and sed (to replace the contents of the templates and output the result in each file). But, what is sed? According to the manual (again, you can check it by running man sed):

The sed utility reads the specified files, or the standard input if no files are specified, modifying the input as specified by a list of commands. The input is then written to the standard output.

First, let's create the new folder:

#!/bin/bash
name="$1"

if [ -z "$name" ]; then
  echo "No component name supplied."
  echo "Example: ./new-component MyComponent"
  exit 1
fi

components_folder=./components
new_component_folder="$components_folder/$name"

if [ -d "$new_component_folder" ]; then
  echo "Component $name already exists."
  exit 1
fi

mkdir "$new_component_folder"

Now for sed, we're going to explain first what will happen.

  • We'll create a variable for the placeholder, so it's easy to change if needed.
  • We'll use the substitute command to change the placeholder in the files by the component name. The syntax works as follows:
sed "s/<pattern_to_change>/<replacement>/"

So in our case, it will look like this:

sed "s/$placeholder/$name/"
  • The second argument of sed will be our template file for each extension, so we're going to add another variable for the templates folder.
  • Then we'll "redirect" the output to a file by using the > operator. There we'll put the target directory + the name of the file (in this case, the argument name).
  • Finally, some feedback to the user to know that the operation ended successfully.

The final result looks like this.

#!/bin/bash
name="$1"

if [ -z "$name" ]; then
  echo "No component name supplied."
  echo "Example: ./new-component MyComponent"
  exit 1
fi

templates_folder=./scripts/templatesplaceholder=__COMPONENT_NAME__components_folder=./components
new_component_folder="$components_folder/$name"

if [ -d "$new_component_folder" ]; then
  echo "Component $name already exists."
  exit 1
fi

mkdir "$new_component_folder"

sed "s/$placeholder/$name/" "$templates_folder/component.html" > "$new_component_folder/$name.html"sed "s/$placeholder/$name/" "$templates_folder/component.css" > "$new_component_folder/$name.css"sed "s/$placeholder/$name/" "$templates_folder/component.js" > "$new_component_folder/$name.js"
echo "Component $name created successfully."

Testing

Script

./scripts/new-component Foo

Output

Component Foo created successfully.

components/Foo/Foo.html

<div class="Foo">
  Foo component
</div>

components/Foo/Foo.css

.Foo {
  padding: 0;
  margin: 0;
}

components/Foo/Foo.js

import template from './Foo.html'
import './Foo.css'

class Foo {}

Script - Final

#!/bin/bash
name="$1"

if [ -z "$name" ]; then
  echo "No component name supplied."
  echo "Example: ./new-component MyComponent"
  exit 1
fi

templates_folder=./scripts/templates
placeholder=__COMPONENT_NAME__
components_folder=./components
new_component_folder="$components_folder/$name"

if [ -d "$new_component_folder" ]; then
  echo "Component $name already exists."
  exit 1
fi

mkdir "$new_component_folder"

sed "s/$placeholder/$name/" "$templates_folder/component.html" > "$new_component_folder/$name.html"
sed "s/$placeholder/$name/" "$templates_folder/component.css" > "$new_component_folder/$name.css"
sed "s/$placeholder/$name/" "$templates_folder/component.js" > "$new_component_folder/$name.js"

echo "Component $name created successfully."

Further steps

  • Add logic to create recursive folders, so if you pass something like Navigation/Header, it will create the Navigation folder first.
  • Add more arguments as needed.
  • Add it to a Makefile or package.json, so other devs can use it easily.

Conclusions

As you can see, it's not hard to automate repetitive tasks in a project, such as boilerplate for components. You can modify the script as you want an apply it to any project, for example, adding another file for tests.

These kind of utilities really improve the developer experience and are independent of the IDE, and most important, without any dependencies. Give it a try.