n: Record and query notes

Demo

$ n vim
 _  _     _
| \| |___| |_ ___ ___
| .` / _ \  _/ -_|_-<
|_|\_\___/\__\___/__/

vim: /lala\C # Make search case sensitive (..\c - insensitive) [sed, regex]
vim: :%!sort -R # Random order - calling external app to order [sed, regex]
vim: \a (\A) # alphabetic character [A-Za-z]. Uppercase: non ... [sed, regex]
vim: \d \x (\D \X) # digit [0-9], hex digit [0-9A-Fa-f]. Uppercase: non... [sed, regex]
vim: \l \u # lowercase letter [a-z], uppercase letter [sed, regex]
vim: \w \s (\W \S) # word character [0-9A-Za-z_], whitespace char (space, tab). Uppercase: non... [sed, regex]
vim: s/de/la&da/ # & in replacement is the matched text
$ 
$ n wi
 _  _     _
| \| |___| |_ ___ ___
| .` / _ \  _/ -_|_-<
|_|\_\___/\__\___/__/

Windows: WIN-H - turn on dictation
column: column -J -s"," -N col1,col2 # format csv as JSON with attribute names col1, col2
column: column -t -s"," -N col1,col2 # format csv as a table with headings col1, col2
$ 

Script

#!/usr/bin/env bash
help_text="
NAME
  n - Notes

USAGE
  n [options] [<text - consider wrapping in quotes> <text2> <text3>]

OPTIONS
  -h|--help
    Show this help message.

  -a|--add
    Add the <text> as a new note.

  -e|--edit
    Edit the notes text file.

  -m|--markdown
    Generate a markdown version of the notes file.

DESCRIPTION
  Record and query notes.

  Any note which contains the text "[P]" is a Private note. Private notes are not 
  included in the markdown extract.

AUTHOR
  mjnurse.github.io - 2026
"

help_line="Record and query notes"
web_desc_line="Record and query notes"

# Terminal Colours
cdef=$'\e[39m' # default colour
cbla=$'\e[30m'; cgra=$'\e[90m'; clgra=$'\e[37m'; cwhi=$'\e[97m'
cred=$'\e[31m'; cgre=$'\e[32m'; cyel=$'\e[33m'; cblu=$'\e[34m'; cmag=$'\e[35m'; ccya=$'\e[36m';
clred=$'\e[91m'; clgre=$'\e[92m'; clyel=$'\e[93m'; clblu=$'\e[94m'; clmag=$'\e[95m'; clcya=$'\e[96m'

nf=~/.notes.txt

function display_notes() {
  w1="${1:-dummydummydummy}"
  w2="${2:-$w1}"; w3="${3:-$w1}"; w4="${4:-$w1}"; w5="${5:-$w1}"
  if [[ "$w1" == "dummydummydummy" ]]; then
    notes="$(cat "$nf")"
  else
    if [[ ${#w1} -lt 2 || ${#w2} -lt 2 || ${#w3} -lt 2 || ${#w4} -lt 2 || ${#w5} -lt 2 ]]; then
      echo "Error: Search terms must be at least 2 characters long."
      return
    fi
    notes="$(cat "$nf" | grep --ignore-case "$w1" | grep --ignore-case "$w2" | grep --ignore-case "$w3" | grep --ignore-case "$w4" | grep --ignore-case "$w5")"
  fi
  echo -e $cyel' _  _     _'
  echo -e      '| \| |___| |_ ___ ___'
  echo -e      '| .` / _ \  _/ -_|_-<'
  echo -e      '|_|\_\___/\__\___/__/'$cdef
  echo
  echo "$notes" | sed "
    s/^\(\w*:\)/CCYA\1CDEF/; 
    s/\(${w1}\)/CGRE\1CDEF/Ig;
    s/\(${w2}\)/CGRE\1CDEF/Ig;
    s/\(${w3}\)/CGRE\1CDEF/Ig;
    s/\(${w4}\)/CGRE\1CDEF/Ig;
    s/\(${w5}\)/CGRE\1CDEF/Ig;
    s/\(# .*\)/CMAG\1CDEF/I;
    s/\(.*\)\[P\] *\(.*\)/CGRE\[P\]CCYA \1\2/;
    :a; s/\(# .*\)CDEF/\1CMAG/; ta;
    s/$/CDEF/;
    s/CCYA/${clcya}/g; s/CGRE/${clgre}/g; s/CMAG/${clmag}/g; s/CDEF/${cdef}/g
  "
}

if [[ "$1" == "" ]]; then
  display_notes
  echo
  echo "Usage: n [options] [<note text - consider wrapping in quotes>]"
  echo "Suggested Format: \"<application>: <command> # <description>\""
  echo "Try:  \"n -h\" for more information."
  exit
fi

case ${1-} in
  -a|--add)
    shift
    if [[ -z "$*" ]]; then
      printf "Suggested Format: \"${clcya}<application>: ${cdef}<command> ${clmag}# <description>${cdef}\"\n"
      printf "\n${clmag}Note: ${cdef}"
      read note
      set -- "$note"
    fi
    note="$*"
    if [[ "$note" != *:*#* ]]; then
      printf "\n${cred}NOTE!${cdef} Does not match the suggested Format: \"${clcya}<application>: ${cdef}<command> ${clmag}# <description>${cdef}\"\n"
    fi
    printf "\n${clmag}Add note: ${cdef}$* ${clmag}[yN]: ${cdef}"
    read yn
    if [[ "${yn^}" == "Y" ]]; then
      echo "$*" >> "$nf"
    fi
    ;;
  -e|--edit)
    vi "$nf"
    ;;
  -m|--markdown)
    # Generate a markdown version of the notes file
    echo "# Miscellaneous Notes"
    echo
    echo "The notes captured in the Linux notes tool = \`n\`."
    echo
    cat "$nf" \
    | sed '1s/^/\n/;
           /\[P\]/d;
           s/|/###BAR###/g;
           s/</\&lt;/g;
           s/>/\&gt;/g;
          ' \
    | sed '/^$/{N;/^\n$/D;}' \
    | sed '/^$/{ N; 
           s/\n\([^ \t][^ \t]*\)/<\/table>\n\n### \1\n\n<table>\n\1/ }' \
    | sed '1,2d;
           s/^\(###.*\):/\1/;
           s/^[^:]\+: *//;
           s/^\(.*\) # \(.*\)$/<tr><td><code>\1<\/code><\/td><td>\2<\/td><\/tr>/;
           s/^\([^|#<].*\)$/<tr><td>\1<\/td><\/tr>/;
           $s/$/\n<\/table>/;
           s/###BAR###/\\|/g;
          '
    ;;
  -h|--help)
    echo "$help_text"
    exit
    ;;
  *)
    display_notes "$1" "$2" "$3"
    exit
    ;;
esac

sort "$nf" | sed '/^$/d' > "$nf".tmp

# Group lines by their first word, inserting a blank line whenever the first word changes.
awk '
    {
        split($0, words, " ")
        if (NR == 1) {
            prev = words[1]
            print
        } else {
            if (words[1] != prev) {
            print ""
            prev = words[1]
            }
            print
        }
    }' "$nf".tmp > "$nf"
rm -f "$nf".tmp