A Practical Shell Scripting Introduction for JavaScript Engineers
Tags: bash, javascript
Published: 2021-11-06 13:53:10

> Compact rundown though shell basics focusing on practical use cases in JavaScript projects.

When developing scripts, I always had a tendency to go for Node.js instead of shell, even for relatively simple things. Shell scripting has always been challenging for me, until I dedicated some time in learning the basics. Now I understand that, when used correctly, shell scripts can really simplify the logic. So, here are some core concepts and practical use cases that you can learn and hopefully apply in your projects.

## Essential Commands

Here is a list of some of the more common shell commands that you'd encounter in JS projects:

- `echo` - prints text terminal window (e.g., `echo Build is complete!`)
- Files and folders:
  - `mkdir` creates directories (e.g. create directories recursively `mkdir -p dist/code`)
  - `rm` - removes files or directories (e.g. forcibly and recursively remove `dist` directory - `rm -rf dist`)
  - `cp` copy files and directories (e.g., `cp -f dist/404/index.html dist/404.html`)
  - `mv` move or rename files or directories (e.g., `mv -f gen/robots.txt gen/manifest.json dist`)
- `tar` - archiving utility (e.g., create a gzipped archive and write it to a file - `tar -czf bundle.tar.gz dist/`)

There are many more commands (`ls`, `cd`, `cat`, etc.), but when encountered you can look the up in the manual pages (e.g., `man tar`). There's also a very nifty community-driven tool called [tldr](https://tldr.sh/), that aims to simplify the beloved [man pages](https://en.wikipedia.org/wiki/Man_page) with practical examples (e.g., `tldr tar`).

## Essential Operators

### Command Chaining Operators

Usage of a single command is rarely enough, so here some chaining operators:

- `&&` (the AND operator) - executes second command only if the first one **succeeds** (e.g., `yarn && yarn build && yarn publish`)
- `;` (the semicolon operator) - runs several commands, despite if previous one succeeded or not (e.g., `yarn build && mv -f gen/sitemap.xml dist; echo Build done!`)

There are many more shell operators, but these and `npm-run-all` should cover the majority of your chaining needs.

### Output `>` and Output Append `>>` Operators

Both, output `>` and output append `>>` operators redirects content to a destination, but only `>>` appends to the target. E.g., creating a `.env` file in your CI pipeline:

```bash
echo "PORT=${PRODUCTION_PORT}" > .env
echo "API_URL=${PRODUCTION_API_URL}" >> .env
```

## Command Substitution

Command substitution is a mechanism by which the shell performs a given set of commands and then exchanges their output in the place of the commands. E.g., combine Node.js script evaluation and command substitution to have JavaScript output in shell environment:

```bash
echo Version - $(node -e "console.log(require('./package.json').version)")
```

## Conditional Statements

Just like JavaScript, shell scripts can have `if` statements. They can be written as both multi and single line statements. E.g., performing an optimized `yarn` install for CI environment only:

```bash
#!/bin/bash

if [[ $CI ]]; then
	yarn --cache-folder $PWD/.yarn --prefer-offline --frozen-lockfile
else
	yarn
fi
```

## Environment Variables

Environment variables are a common way of passing dynamically configurable values. Here are some use cases:

- **Configurable environment values with defaults** - e.g., optional `PORT` environment variable with defaults from npm config:

```json
{
  "config": {
    "port": 1234
  },
  "srcipts": {
    "start": "serve -l ${PORT:-$npm_package_config_port}"
  }
}
```

- **Reusing `.env` file in shell sessions** - e.g., a localhost release script for Lerna project that loads `.env`, and performs some necessary checks:

```bash
#!/bin/bash

set -a; source .env; set +a

if [[ ! $GH_TOKEN ]]; then
    echo "🚨 Missing GH_TOKEN env variable" && exit 1
fi

if [[ ! "$(npm whoami)" ]]; then
    echo "🚨 Not logged in to npm" && exit 1
fi

if [[ ! "$(git status --porcelain)" ]]; then
    git checkout main && git pull && yarn release
else
    git status --porcelain
    echo "🧹 Working directory not clean" && exit 1
fi
```

## Conclusion

Shell scripting is a powerful and elegant way to perform some common operations. Dedicate some time and give it a proper try - it's a versatile skill to have at your tool belt. This blog post scratches only the surface of what's possible, so here are some resources for further reading:

- [github.com/dylanaraps/pure-bash-bible](https://github.com/dylanaraps/pure-bash-bible)
- [mywiki.wooledge.org/BashGuide](http://mywiki.wooledge.org/BashGuide)