how to make it convenient in the CLI

devmanual.gentoo.org. I'll give you the links right away: Key Concepts, Details about “compgen”, Details about “complete”. All 3 articles are quite compact, but allow you to begin to understand the issue.

Next I show “how” using my own example.

“Fish”:

_lun() {
  local cur="$2"
  local prev="$3"
  local obj cmd base keys key val
  local LIST=""
  local WWID=""
  local LUN=""
  local cmd="${COMP_WORDS[1]}"
  local DC="${COMP_WORDS[2]}"
…
} && complete -F _lun lun
  • complete -F calls a function _lun()when complements the command lun.

  • For simple manipulations, three arguments to the called function are enough (more details). Which command are we supplementing ($1), which we complement ($2) and what happened before ($3).

  • Keyword local is designed to protect variables inside the function from “leakage” outside. In general, all autocompletion in bash is lumped together, and with illiterate actions it is easy to break the work of someone else’s code.

  • For more complex manipulations, an array is available COMP_WORDS[] and a pointer to its last element COMP_CWORD. Above you can see how I extract a couple of “positional” arguments from it.

  • For “aerobatics” access to COMP_LINE And COMP_POINT (the entire line to be completed and the current cursor position).

First argument:

  if [ "${COMP_CWORD}" = "1" ]
  then
    # first level -> base objects
    base="/usr/local/sbin/lun.d"
    obj=$(cd ${base} && ls -1 *.py | cut -f 1 -d "." | sort -u)
    COMPREPLY=( $(compgen -W "help ${obj}" -- "${cur}") )
  • The Bashists in this place should hit my hands for cd. Because there is a couple pushd/popd.

  • Take a list of all modules *.pyadd another keyword help (output hint to wrapper lun) and from this we form a set of “words” for compgen -W. IN COMPREPLY a bash array is returned (what is inside the parentheses “( … )”).

  • At the end of the call compgen it is necessary to put the complement (${cur}). --“tell GNU utilities what's next -ключей will not be.

Second argument:

  elif [ "${COMP_CWORD}" = "2" ]
  then
    # second level -> commands
    DC=$(sudo lun get list-dc)
    if [ "${cmd}" = "get" ]; then
      COMPREPLY=( $(compgen -W "help ${DC} list-dc" -- "${cur}") )
    else
      COMPREPLY=( $(compgen -W "help ${DC}" -- "${cur}") )
    fi
  • Application sudo this is really important. The user has access to some privileged commands, but not sudo -i for everyone!

  • Call sudo lun will break auto-completion. Because the addition will be for sudoand the opinion of those who wrote sudo doesn't always match mine. For everything to work for the user, an alias is needed (somewhere in ~/.bashrc): alias lun="sudo lun"

Third argument:

  elif [ "${COMP_CWORD}" = "3" ]
  then
    # third level -> keys
    keys=`sudo lun ${cmd} args`
    if [[ "${cmd}" =~ get|add|edit ]]; then
      if [ "${DC}" = "list-dc" ]; then
        return
      fi
      WWID=$(sudo lun get ${DC} --fields=wwid --compact | cut -d ']' -f 1 | cut -d ' ' -f 2 | tail -n 5)
      COMPREPLY=( $(compgen -W "${keys} ${WWID}" -- "${cur}" | awk '{ if ($0 !~ "^-") {print "'\''"$0"'\''"} else print $0 }') )
    elif [[ "${cmd}" == vm || "${cmd}" == attach || "${cmd}" == resize ]]; then
#      compopt -o nospace
      LIST=`sudo lun vm ${DC} list "${cur}" --cached`
      COMPREPLY=( $(compgen -W "${LIST}" -- "${cur}" ) )
    else
      COMPREPLY=( $(compgen -W "${keys}" -- "${cur}" | awk '{ if ($0 !~ "^-") {print "'\''"$0"'\''"} else print $0 }') )
    fi
  • About nospace will be lower.

  • Each module is trained to output a list of accepted arguments by key args. We show it in the general case.

  • list-dc Instead of the data center abbreviation, it displays a list of known DCs. There is nothing further to add, return.

  • For teams get|add|edit Additionally, we show a list of the five most recently added LUNs. Not a super good solution, because… in the process it pulls out a listing of all LUNs in the DC. It would be more correct to push the restriction in a non-Unix way lun get.

  • This is the trick with quotes for WWN (who is not clear what is written here, googles “bash escape quotes”): {print "'''"$0"'''"}. It is for auto-completion of the divided “:“. Because “:” is included in the list of word separators by default. I was too lazy to dig deeper in this place.

  • For teams vm, attach, resize We supplement the VM name from the list cached in the local database. The above comparison was through “=~“, and here it is like this. Simply because at first the team was alone.

  • Autocomplete well, very desirable from somewhere in the “fast” cache. Don't be like writers yum/dnf and others like him. Long requests via ssh fail due to timeout. I didn't find this place in bash-completion, but I didn't try too hard.

  • For all other commands, we display only a list of keys.

Other arguments:

  else
    # other level -> options
    if [ "${COMP_WORDS[COMP_CWORD]}" = "=" ]; then
      key=$((COMP_CWORD - 1))
    elif [ "${COMP_WORDS[COMP_CWORD-1]}" = "=" ]; then
      key=$((COMP_CWORD - 2))
    fi

For non-Boolean arguments, keys of the form --key=valuewithout spaces. I didn't do much with the spaces. We will consider this “homework” for better mastery of the material.

Let's go add:

    if [ ! -z "${key}" ]; then
      if [ "${COMP_WORDS[$key]}" = "--fields" ]; then
        val="alias host vm scsi blkdeviotune ,"
        local list=$(echo "${cur}" | egrep -o '([a-z]+,)+')
        cur="${cur/[[:alpha:]]*,/}"
        COMPREPLY=( "${list}"$(compgen -W "${val}" -- "${cur}") )
        compopt -o nospace
      elif [ "${COMP_WORDS[$key]}" = "--file" ]; then # [ "${cmd}" = "edit" ]
        compopt -o filenames
        COMPREPLY=( $(compgen -f -- "${cur}") )
      elif [ "${COMP_WORDS[$key]}" = "--vm" ]; then # [ "${cmd}" = "get" ]
        LUN=`sudo lun vm ${DC} list --cached`
        COMPREPLY=( $(compgen -W "${LUN}" -- "${cur}" ) )
      elif [ "${COMP_WORDS[$key]}" = "--lun" ]; then # [ "${cmd}" = "attach"|"resize" ]
        local vm="${COMP_WORDS[3]}"
        LUN=`sudo lun vm ${DC} ${vm} --cached --luns --json | jq -r ".luns[]"`
        COMPREPLY=( $(compgen -W "${LUN}" -- "${cur}" ) )
      else
        val="<`echo ${COMP_WORDS[$key]} | tr -d '=-'`>"
        COMPREPLY=( $(compgen -W "${val}" -- "${cur}") )
        compopt -o nospace
      fi
    else
      keys=`sudo lun "${COMP_WORDS[1]}" args`
      COMPREPLY=( $(compgen -W "${keys}" -- "${cur}") )
    fi
  • If you haven’t started entering the key, we show a list of keys for the module (lower block else). It would be correct to exclude already used keys from the list. Inside is Python’s “argparse”, it will stupidly take the last one it comes across.

  • Key --fields takes a list of fields as input via “,“. This solution again goes against the default delimiter settings for libreadline, so the next step is the trick: cur="${cur/[[:alpha:]]*,/}". We cut everything down to the last “comma”. In general, in bash foot wraps I try to limit myself to bash means. Because he himself then threatens (if something happens) to unwind the audit logs.

  • At this point one should also exclude everything previously listed through “,“fields from autocompletion.

  • For --file you need to autocomplete file names from the current directory. For this we order compopt -o filenamesotherwise it will not allow it to “fall through” into subdirectories. Somewhere it is written that this can be inserted directly into the call compgen -f. But this doesn’t work (for me, bash 4.4.20 from Oracle Linux 8).

  • For --lun take a list of LUNs related to the specified VM. Request jq -r ".luns[]" retrieves values ​​(LUN names) from the dictionary provided in json. JSON and “jq” are generally quite convenient when parsing what is sent to the CLI. For those utilities that know how to write JSON.

  • Everything else (after else) – we don’t know how to supplement. Using the “tabulator” we display the name of the key in angle brackets (--key=<key>).

To autocomplete after “=“, please do not add a space:

  if [[ "${COMPREPLY[@]}" =~ =$ ]]; then
    # Add space, if there is not a '=' in suggestions
    compopt -o nospace
  fi

All. I told him as best I could. Good luck improving the UI/UX for command line tools!

  • This is my first experience of writing (not presented as a lecture) educational material for adult aunts and uncles. I would be extremely grateful for constructive criticism. And, as it becomes available, I will try to finalize this article to improve readability.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *