How Do I Use Charmbracelet Gum to Improve My Scripts

As someone who works on terminal everyday, I write a lot of shell scripts to help my work. Although the magic variables like $1, $2 get the job done, I often struggle to add more features to my scripts because it is hard to remember how many variables I can pass in and which one does what if I don’t use the script often enough.

To solve this issue, I need to write helper function and use flags. This works really well but helper function and flags are not the easiest thing to write. Also if I don’t use a script a lot, I might forget what flags I can use and I have to check the source code. What I want for my scripts is that I can choose to pass in variable. If I don’t pass in anything, my script should guide me through.

To achieve this goal, I was thinking to rewrite all my scripts using Golang or Python because there are many existing frameworks that I can benefit from. I never got the time to do it though until I discovered Gum not long ago from YouTube.

This is a CLI tool that helps people like myself to capture user input, filter lists or files, provide selections out of box and can be easily integrated with existing shell scripts. This seems to fit my use case perfectly so I tested it immediately. I was HOOKED right away and rewrite all my script with it in the next few days. In today’s post, I want to walk you through on how I use this tool to improve my scripts.

Installation is pretty simple, if you are using homebrew, you can just brew install gum. Here is official installation guide.

Let’s get started.

Intro

This CLI tool provides many subcommands. Normally if you are not sure how to use one of the subcommand, you can run gum <OPTION> -h for all the flags you can use.

Let’s use gum confirm --help as an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❯ gum confirm --help
Usage: gum confirm [<prompt>]

Ask a user to confirm an action

Arguments:
[<prompt>] Prompt to display.

Flags:
-h, --help Show context-sensitive help.
-v, --version Print the version number

--affirmative="Yes" The title of the affirmative action
--negative="No" The title of the negative action
--default Default confirmation action
--timeout=0 Timeout for confirmation ($GUM_CONFIRM_TIMEOUT)

Style Flags
--prompt.foreground="" Foreground Color ($GUM_CONFIRM_PROMPT_FOREGROUND)
--selected.foreground="230" Foreground Color ($GUM_CONFIRM_SELECTED_FOREGROUND)
--unselected.foreground="254" Foreground Color ($GUM_CONFIRM_UNSELECTED_FOREGROUND)

We can see gum command helper consists with 4 sessions.

  1. Usage explains what this subcommand is used for.
  2. (Optional) Arguments what to be used with the subcommand.
  3. Flags can be used to config subcommand options from the functional perspective.
  4. Style flags are used to customized the subcommand output.

Let’s move on and see how I use these subcommands to improve my scripts.

Input

I use bash read command previously when I need to ask for a user input. This command is powerful but it is not very intuitive to use.

For example, if I want to give a hint or use a placeholder, I need to write something like below.

1
2
echo -n "Enter your name and press [ENTER]: "
read username

This is not as easy to read as username=$(gum input --placeholder "Enter your name and press [ENTER]: ") which clearly indicate user input will be stored in variable username.

More over, if I need to mask sensitive input or customize the input prompt, it is not possible to do that with read command.

In my opinion gum input reads the user input in a more modern way. It supports

  • Placeholder
  • Customize prompt
  • Default value
  • Mask sensitive input

Currently I am using environment variable GUM_INPUT_PROMPT to set user input prompt globally. On my public certificate request script, I am using domain_input=$(gum input --placeholder "test.fomm.au") to store the domains I want to use on my certificate.

My prompt looks like below.

1
2
gum input --placeholder "Enter your name and press [ENTER]: "
🦫 Enter your name and press [ENTER]:

Filter

To filter files or data with bash script, I used to use sed, awk, perl depending on what result I could find on the internet. 🤗

gum filter comes to rescue I use it for two things.

Filter photos to resize

One of my script use squoosh-cli to resize photos in a directory. I can use this subcommand to prompt the user which file(s) needs to be resize.

I use FILES=$(gum filter --no-limit --placeholder="Please select photos to be resized...") to store files names. This gives me below prompt and I can use the Tab key to select the photos I want to resize.

This prompt also supports partial match which is EXTREMELY useful.

1
2
3
4
5
6
7
8
9
10
> Please select photos to be resized...
◉ SLY02482.jpg
○ SLY02487.jpg
○ SLY02488.jpg
◉ SLY02489.jpg
○ SLY02494.jpg
◉ SLY02495.jpg
○ SLY02497.jpg
◉ SLY02500.jpg
• ○ SLY02502.jpg

Filter TMUX sessions

Sometimes I create a few TMUX session and can’t remember all names. I write below function to help me select which TMUX session I want to attach to.

1
2
3
4
5
6
7
8
9
10
11
12
13
tsa(){
tmux ls -F \#S > /dev/null 2>&1
if [ $? -eq 0 ]
then
SESSION=$(tmux ls -F \#S | gum filter --placeholder "Attach to a TMUX session...")

if [ ! -z "$SESSION" ]; then
tmux attach -t "$SESSION"
fi
else
echo "There is no existing TMUX session."
fi
}

Now when I run tsa on my terminal, I will get below prompt to choose which session I want to attach to.

1
2
3
> Attach to a TMUX session...
• demo
test

Choose

This subcommand allows the user to choose single or multiple options from a list of choices. I use this subcommand in two ways.

Provide options

I have a script to request public certificates. My script supports getting certificate from Let’s Encrypt, Buypass, ZeroSSL or Google. I use below to choose store the CA I want my certificate to be issued from.

1
CA_SELECTED=$(gum choose --limit 1 Letsencrypt Buypass Zerossl Google | awk '{print tolower($0)}')

Confirmation

Another way I use gum choose is to confirm how I want my script to continue running.

For example, I use variable CHOICE and RESIZE_CHOICE to understand how I want to resize the photo(s).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
YES="Yes, please!"
NO="No, just a few."
echo "Do you want to process all photos?"
CHOICE=$(gum choose "$YES" "$NO")

RESIZE_YES="Yes, please!"
RESIZE_NO="No"
echo "Do you need to resize your photos?"
RESIZE_CHOICE=$(gum choose "$RESIZE_YES" "$RESIZE_NO")

if [ "$CHOICE" == "$YES" ]; then
if [ "$RESIZE_CHOICE" == "$RESIZE_NO" ]; then
...
fi
fi

Confirm

gum confirm seems to be similar to how I use gum choose from above. The difference is gum confirm exit the process after making a choice and gum choose simply store user’s choice in a variable.

I use some environment variable to style so I can have a consistent confirmation box and it looks like below.

1
2
3
4
5
6
7
8
9
10
11
❯ gum confirm "Continue to resize photos?"
╭────────────────────╮
│ │
│ │
│ Continue to │
│ resize photos? │
│ │
│ │
╰────────────────────╯

Yes No

Spin

gum spin is an interesting subcommand, users can use it to hide the output of running script (you can choose to display output) and exit when it is done. Currently I use it when I need to make sure kubernetes deployment is ready. For example, I use below to check deployment of MetalLB is ready.

1
gum spin --title "Waiting for MetalLB to be installed..." -- kubectl rollout status -n metallb deployment metallb-controller

Style

The last subcommand I am using is gum style. This is probably my favourite subcommand that I use the most. If I want to add some highlight a word in a sentence, I need to write something like below. To me this is no readable at all.

1
echo -e '\033[1mNo\033[0m token.'

If we want to do the same with gum style we can write it as below which is much more readable IMO.

1
echo $(gum style --bold No) token

gum style supports adding border, padding, margin as well. I write below script called gum_style so I can pass in different options for different task.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env bash

TEXT=$1

if [ -n "$2" ]; then
if [ "$2" = "banner" ]; then
gum style --foreground=212 --border-foreground=111 --border=rounded --align=center --width=50 --margin="1 2" --padding="2 4" "$TEXT"
fi
else
gum style --foreground=#c462fc "$TEXT"
fi

For example, I use below to create banner for installing MetalLB

1
2
3
4
5
6
7
8
9
❯ gum_style 'Installing metallb LoadBalancer.' 'banner'

╭──────────────────────────────────────────────────╮
│ │
│ │
│ Installing metallb LoadBalancer. │
│ │
│ │
╰──────────────────────────────────────────────────╯

That’s all I want to show you today. I hope it helps you to start using this amazing tool to improve your scripts.

See you next time.