How Do I Save Terminal Commands?

Published on April 30, 2025. Last updated on June 24, 2025.

Motivation

I often find myself in a situation where I want to remember a useful terminal command, but I have a poor memory and will forget it after a few days. I used to save all useful commands in Obsidian, which I use daily as my second brain for notes, planning, learning, and more.

However, whenever I need to use a command I previously bumped into and saved in the note, it is a bit tedious to jump out of the terminal, open Obsidian, search for the note and find the right command. On top of that, I sometimes include placeholders for command arguments which are dynamic case by case, so whenever I need to use such a command with placeholders, I have to copy it from the note, paste it into the terminal, and then manually fill in the appropriate values.

Therefore, I want to build a command manager that I can use directly within the terminal. It can detect placeholders in a saved command and prompt me to fill values in. It should be built with my existing daily tools namely built-in commands on macOS/Linux and fzf.

↑ Back to top

Build a simple command manager

Before we start, if you are not yet familiar with Bash, I highly recommend checking out Learn Bash in Y minutes or Bash scripting cheatsheet for a brief introduction.

The command manager is named cmd. A note that cmd supports macOS and Linux only.

↑ Back to top

Prerequisites

↑ Back to top

Features

Before we jump into building the command, it is helpful to define the list of features first:

↑ Back to top

Project structure

.
├── cmd
├── cmd-find
└── cmd-git

Remember to run the below command to make cmd executable:

chmod +x ./cmd

↑ Back to top

Command files

In a command file, each line is one command entry. The format is

description: command

The content of the cmd-git file looks like this:

git log oneline in graph: git log --oneline --graph
git rename current branch: git branch -m <new-name>
git go back n commit(s) from HEAD: git reset --<mode> HEAD~<n>

Let's break down the cmd-git file above:

Then, let's add one command entry to the cmd-find file:

find and delete empty directories: find <target-path> -type d -empty -delete

Now, let's build the command manager in Bash in the below sections. I will use in-line comments to explain what each line is.

I have a tip. If you do not understand what a command is and what their flags or options are, you can run the man command with a command name to read its manual. For example, if I want to read grep manual, I can run the below command:

man grep

↑ Back to top

Find command with fzf

#!/usr/bin/env bash

# The optional category passed as an argument
category="$1"

# The directory of all command files
cmd_dir="."

# The path to the command file based on the category
cmd_file="$cmd_dir/cmd-$category"

# The selected command based on the category
cmd=""

# === Find command with fzf ===

# If no argument is provided, i.e. no category
if [[ $# -eq 0 ]]; then
  # Get commands in all command files whose name
  # starts with "cmd-" in the "cmd_dir" directory
  cmds=$(find "$cmd_dir" -maxdepth 1 -name "cmd-*" -exec cat {} '+')

  # Find a command entry across all found command files
  cmd=$(echo "$cmds" | fzf)
# If the command file exists based on the category
elif [[ -e "$cmd_file" ]]; then
  # Find a command entry within the command file
  cmd=$(cat "$cmd_file" | fzf)
# If the command file is not found based on the category
else
  # Print the not found error message
  echo "$cmd_file: No such file"

  # Exit with the error code
  exit 1
fi

# If the "cmd" variable is empty,
# i.e. no command entry is selected
if [[ -z "$cmd" ]]; then
  # Exit with the success code
  exit 0
fi

# Remove the "description: " prefix to get
# the command from the selected command entry
cmd=$(echo "$cmd" | cut -d ':' -f 2-)

# Trim leading spaces
cmd=${cmd/#[[:space:]]/}

# Trim trailing spaces
cmd=${cmd/%[[:space:]]/}

# Print the selected command
echo "Selected command: $cmd"

↑ Back to top

Detect placeholders and fill in values

#!/usr/bin/env bash

...

# The selected command based on the category
cmd=""

# === Find command with fzf ===
...

# === Detect placeholders and fill in values ===

# Get all placeholders in the selected command
placeholders=$(echo "$cmd" | grep -oE "<(\w|-)+>")

# If the "placeholders" variable is not empty,
# i.e. there is at least one placeholder
if [[ -n "$placeholders" ]]; then
  echo "----------------------"

  # Loop through each placeholder
  for k in $placeholders; do
    # Prompt to input a value for the placeholder
    read -r -p "$k: " v

    # Replace the placeholder in the selected command
    # with the input value
    cmd=${cmd//$k/$v}

    # Print the updated command after the replacement
    echo "Command: $cmd"
  done

  echo "----------------------"
fi

↑ Back to top

Perform action on command

#!/usr/bin/env bash

...

# The selected command based on the category
cmd=""

# === Find command with fzf ===
...

# === Detect placeholders and fill in values ===
...

# === Perform action on command ===

# Keep prompting until a valid action is chosen
while true; do
  # Prompt to input a value for the action
  read -r -p "copy (c), execute (e) or quit (q)? " action

  # Transform the input value to lowercase
  # for easier matching
  action=$(echo "$action" | tr "[:upper:]" "[:lower:]")

  # If the selected action is to copy
  if [[ "$action" == c* ]]; then
    # Get the operating system
    os=$(uname -s)

    case "$os" in
      # If OS is macOS
      Darwin*)
        # Copy the selected command to clipboard
        echo -n "$cmd" | pbcopy
        ;;
      # If OS is Linux
      Linux*)
        # Copy the selected command to clipboard
        echo -n "$cmd" | xclip -selection clipboard
        ;;
    esac

    # Print a message that the selected command
    # has been copied
    echo "Copied to clipboard: $cmd"

    # Exit with the success code
    exit 0
  # If the selected action is to execute
  elif [[ "$action" == e* ]]; then
    # Print a message that the selected command
    # is being executed
    echo "Executing: $cmd"

    # Execute the selected command
    eval "$cmd"

    # Exit with the success code
    exit 0
  # If the selected action is to quit
  elif [[ "$action" == q* ]]; then
    # Exit with the success code
    exit 0
  else
    # Print a warning message to enter a valid action
    echo "Only copy (c), execute (e) or quit (q)?"
  fi
done

↑ Back to top

Full implementation

#!/usr/bin/env bash

# The optional category passed as an argument
category="$1"

# The directory of all command files
cmd_dir="."

# The path to the command file based on the category
cmd_file="$cmd_dir/cmd-$category"

# The selected command based on the category
cmd=""

# === Find command with fzf ===

# If no argument is provided, i.e. no category
if [[ $# -eq 0 ]]; then
  # Get commands in all command files whose name
  # starts with "cmd-" in the "cmd_dir" directory
  cmds=$(find "$cmd_dir" -maxdepth 1 -name "cmd-*" -exec cat {} '+')

  # Find a command entry across all found command files
  cmd=$(echo "$cmds" | fzf)
# If the command file exists based on the category
elif [[ -e "$cmd_file" ]]; then
  # Find a command entry within the command file
  cmd=$(cat "$cmd_file" | fzf)
# If the command file is not found based on the category
else
  # Print the not found error message
  echo "$cmd_file: No such file"

  # Exit with the error code
  exit 1
fi

# If the "cmd" variable is empty,
# i.e. no command entry is selected
if [[ -z "$cmd" ]]; then
  # Exit with the success code
  exit 0
fi

# Remove the "description: " prefix to get
# the command from the selected command entry
cmd=$(echo "$cmd" | cut -d ':' -f 2-)

# Trim leading spaces
cmd=${cmd/#[[:space:]]/}

# Trim trailing spaces
cmd=${cmd/%[[:space:]]/}

# Print the selected command
echo "Selected command: $cmd"

# === Detect placeholders and fill in values ===

# Get all placeholders in the selected command
placeholders=$(echo "$cmd" | grep -oE "<(\w|-)+>")

# If the "placeholders" variable is not empty,
# i.e. there is at least one placeholder
if [[ -n "$placeholders" ]]; then
  echo "----------------------"

  # Loop through each placeholder
  for k in $placeholders; do
    # Prompt to input a value for the placeholder
    read -r -p "$k: " v

    # Replace the placeholder in the selected command
    # with the input value
    cmd=${cmd//$k/$v}

    # Print the updated command after the replacement
    echo "Command: $cmd"
  done

  echo "----------------------"
fi

# === Perform action on command ===

# Keep prompting until a valid action is chosen
while true; do
  # Prompt to input a value for the action
  read -r -p "copy (c), execute (e) or quit (q)? " action

  # Transform the input value to lowercase
  # for easier matching
  action=$(echo "$action" | tr "[:upper:]" "[:lower:]")

  # If the selected action is to copy
  if [[ "$action" == c* ]]; then
    # Get the operating system
    os=$(uname -s)

    case "$os" in
      # If OS is macOS
      Darwin*)
        # Copy the selected command to clipboard
        echo -n "$cmd" | pbcopy
        ;;
      # If OS is Linux
      Linux*)
        # Copy the selected command to clipboard
        echo -n "$cmd" | xclip -selection clipboard
        ;;
    esac

    # Print a message that the selected command
    # has been copied
    echo "Copied to clipboard: $cmd"

    # Exit with the success code
    exit 0
  # If the selected action is to execute
  elif [[ "$action" == e* ]]; then
    # Print a message that the selected command
    # is being executed
    echo "Executing: $cmd"

    # Execute the selected command
    eval "$cmd"

    # Exit with the success code
    exit 0
  # If the selected action is to quit
  elif [[ "$action" == q* ]]; then
    # Exit with the success code
    exit 0
  else
    # Print a warning message to enter a valid action
    echo "Only copy (c), execute (e) or quit (q)?"
  fi
done

Remember to run the below command to make cmd executable:

chmod +x ./cmd

Now run it with all categories:

./cmd

Or, run it with the git category:

./cmd git

To make the cmd command accessible from anywhere, you can either add the path to the directory containing it to your $PATH environment variable,

export PATH="$PATH:path-to-directory-containing-cmd"

or move it along with the cmd-* files into a directory that is already included in your $PATH.

↑ Back to top

Final words

I hope you enjoy the process of building and learn something along the way. I often build something simple using tools already available on my machine, if the existing solutions are more complex than necessary for my needs. As a result, I have a chance to learn something new while producing fairly-good-and-simple tools to serve my daily tasks.

↑ Back to top

Cheers,
Nam Nguyen

$ cd ~/writing