Build a dead simple CLI in Bash
Building a simple CLI in Bash may seem like a herculean task; however, getopts
provides an easy-to-use interface out of the box! For this tutorial, we’ll be using the https://pokeapi.co/ to build a simple CLI for fetching resources from the Pokemon world.
What is getopts
?
Given from the documentation,
The getopts utility shall retrieve options and option-arguments from a list of parameters. It shall support the Utility Syntax Guidelines 3 to 10, inclusive, described in XBD Utility Syntax Guidelines.
getopts
is a command that makes defining options and option-arguments seamless using a list of parameters.
Use getopts
to get pokemon
Let’s start by making the boilerplate:
1#!/bin/bash
2
3usage() {
4 echo "Usage: $0 -p <pokemon>" 1>&2
5 exit
6}
7
8while getopts "hp:" o; do
9 case "${o}" in
10 p) pokemon_name="$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]')" ;;
11 h) usage ;;
12 esac
13done
14
15[[ -z "$pokemon_name" ]] && usage
As seen above, getopts
accepts a parameter list – ie. hn:
. Colons (:
) are used to denote that an argument requires a value. In this case, h
is as an option (-h
) while n
is as an option-argument (-n <name>
). If $name
is not provided, then the usage
function is called.
Within the case
statement, $OPTARG
contains the option-argument value that is passed from the user. For good measure, it is piped through tr
to lowercase it (click here for more information about casing strings in Bash).
Now, let’s add a simple curl command that calls the /pokemon
api given the name:
1#!/bin/bash
2
3usage() {
4 echo "Usage: $0 -p <pokemon>" 1>&2
5 exit
6}
7
8pokemon() {
9 local name="$1"
10 curl -o "$name.json" https://pokeapi.co/api/v2/pokemon/$name \
11 -H "Accept: application/json"
12 exit
13}
14
15while getopts "hp:" o; do
16 case "${o}" in
17 p) pokemon_name="$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]')" ;;
18 h) usage ;;
19 esac
20done
21
22[[ -z "$pokemon_name" ]] && usage
23[[ "$pokemon_name" ]] && pokemon "$pokemon_name"
The pokemon
function is called with the $pokemon_name
that was passed in.
1./poke.sh -p pikachu
The output is a json
file that contains pikachu’s information.
Use getopts
to get items
Building upon the last example, we can easily capture items
:
1#!/bin/bash
2
3usage() {
4 echo "Usage: $0 [-p <pokemon>] [-i <item>]" 1>&2
5 exit
6}
7
8request() {
9 local path="$1"
10 local file_name=$(echo $path | sed 's|.*/||')
11 curl -o "$file_name.json" https://pokeapi.co/api/v2/$path \
12 -H "Accept: application/json"
13}
14
15pokemon() {
16 request "pokemon/$1"
17 exit
18}
19
20item() {
21 request "item/$1"
22 exit
23}
24
25while getopts "hp:i:" o; do
26 case "${o}" in
27 p) pokemon_name="$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]')" ;;
28 i) item_name="$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]')" ;;
29 h) usage ;;
30 esac
31done
32
33[[ -z "$pokemon_name" && -z "$item_name" ]] && usage
34[[ "$pokemon_name" ]] && pokemon "$pokemon_name"
35[[ "$item_name" ]] && item "$item_name"
A few things happened:
- I abstracted the
curl
to its own function namedrequest
- The
file_name
is parsed from the path. Ie./items/master-ball
->master-ball.json
- The
pokemon
anditem
both callrequest
i:
was added to thegetopts
string to allow the-i <item>
option-argument.
What’s next?
getopts
is useful for building plain / simple CLIs. If something more advanced is necessary, bash
is not the best language for the task.