freq

Offer to run a complex linux command from among those listed in a file in the current directory, .frequents .

This facility makes it easy to package common actions in particular contexts. That can be a huge memory aid, preserving a record of the actions I have available in particular contexts, including intricate details of those actions.

Last update: 2021-04-25

#!/bin/bash

# Offer to run a complex command from among those in `./.frequents`

# - A command line parameter is taken as the chosen line's number or label.
# - Otherwise, a consolidated list of comments and commands are presented,
#   and a prompt is issued for a line number or label of command to run.
# - In either case the choice is echoed and the command line is evalled.
#   - Using eval means the command line can contain multiple commands in a
#     pipeline, sequence, with backgrounded components, etc.
# - Lines that begin with a "#" hash mark are taken as commentary and
#   labels for the following lines.
#   - The comments are presented in the listing but not included in the
#     command-numbering count.
#   - The comment lines can contain a ":" delimited word, which is used
#     as a label for the succeeding command line.
#     - The word may not contain any whitespace.

# Copyright Ken Manheimer, ken.manheimer@gmail.com,  2007-2019
# Licensed under Gnu Public License v3, 


unset lines
unset comments
unset labels

declare -A labels

arg="$1"

NEWLINE="
"

read_from () {
  carry=""
  if [ -r "$1" ]; then
    exec 3< "$1"
    while read curline <&3; do
      if [ -n "$carry" ]; then
        curline="$carry $curline"
        carry=""
      fi
      case $curline in
        *" "\\ ) cume="$curline"
                 carry="${curline:0:-2}";;
        * ) build "$curline";;
      esac
    done
    if [ -n "$carry" ]; then
      build "$carry"
    fi
    exec 3<&-
  fi
}

build () {
  curline="$1"
  if [ -n "$curline" ]; then
    case $curline in
      \#* ) if [ -n "${comments[${#lines[@]}]}" ]; then
              comments[${#lines[@]}]+="${NEWLINE} $curline"
            else
              comments[${#lines[@]}]="  $curline"
            fi;;
      * )
        label=$(echo "${comments[${#lines[@]}]}" | \
                sed -n -e 's/.*:\([^: ]*\):.*/\1/p')
        if [ -n "$label" ]; then
          # XXX debug:
          #echo $label
          labels[${label}]="$curline"
        fi
        lines[${#lines[@]}]="$curline";;
    esac
  fi
}

if [ ! .frequents -ef ~/.frequents ]; then
  frequents=.frequents
else
  frequents=~/.frequents
fi
if [ "$(tail -c 1 $frequents)" != "$(echo -e '\n')" ]; then
  echo "(NOTE: $frequents lacks a trailing newline)" 1>&2
fi
read_from $frequents

size=${#lines[@]}

if [ "$size" = 0 ]; then
  echo "no entries"
  exit
fi

which=""

if [ -n "$arg" ]; then
  which="$arg"
else
  echo
  incr=0
  for i in "${lines[@]}"; do
    if [ -n "${comments[$incr]}" ]; then
      echo "${comments[$incr]}"
    fi
    echo $((incr++)): $i
  done
  echo
  echo -n "execute command from line number[empty input to cancel]: "
  read which
fi

mode=""
case "$which" in
  [0-9] | [0-9][0-9] | [0-9][0-9][0-9]) mode=number ;;
  * ) mode=alpha;;
esac

if [ "x$mode" = "xnumber" ]; then
  if [ -z "${lines[$which]}" ]; then
     echo no match
     exit 1
  else
     echo "$which: ${lines[$which]}"
     eval "${lines[$which]}"
  fi
else
  if [ -z "$which" ]; then
     echo no match
     exit 1
  elif [ -z "${labels[$which]}" ]; then
     echo no match
     exit 1
  else
     echo "$which: ${labels[$which]}"
     eval "${labels[$which]}"
  fi
fi