1

I'm looking for a tool for displaying inline menus in the shell which can be navigated with arrow-keys and enter. By "inline", I mean that the menu is displayed within the normal flow of stdout text, not in a pop-up dialog on top of everything.

I only found that post trying to address that, but it only mentions either custom scripting or non-inline/pop-up solutions (like dialog or zenity).

What I am looking for is a robust package that I could simply install within a Docker image using apt-get or npm install -g and invoke from my scripts with a list of choices and get back the user's selected item.

In nodeJS, I am using Inquirer which offers not only that kind of menus, but also all sorts of inputs.

Here's an example screenshot of such an inline menu.

The tool does not have to be written in shell script. It can be a binary/script written in any language, as long as it's rather easy to install using apt-get/curl. Even a nodeJS tool would be fine, as long as it's invokable from a shell script to pass it the choices.

3 Answers3

1

A very basic approach to this would be to use bash's select statement; no need to install anything (else). Here's an example I've just got at hand:

#!/bin/bash

[...]

sourceBranch=
targetBranch=
# Force one option per line
columnsBackup=${COLUMNS}
COLUMNS=40

echo "Select source and target branch:"
select branches in \
    "testing -> release-candidate" \
    "release-candidate -> stable-release" \
    "stable-release -> stable"
do
    if [ -z "${branches}" ]; then
        echo "Invalid selection"
        continue
    fi

    sourceBranch="${branches%% -> *}"
    targetBranch="${branches##* -> }"
    break
done

COLUMNS=${columnsBackup}
echo "Releasing from ${sourceBranch} to ${targetBranch}"

[...]

Output:

Select source and target branch:
1) testing -> release-candidate
2) release-candidate -> stable-release
3) stable-release -> stable
#? 1   
Releasing from testing to release-candidate

You'll probably do some case ... esac handling in the do ... done block instead.

Murphy
  • 2,649
  • Indeed, that would minimally get the job done, but as I pointed out in my question, I'm looking for something more modern, using up/down arrow-keys to move a highlight and choosing the highlighted item with ENTER. Have a look at this animated gif for a good example of such a prompt. – Mathieu Frenette Sep 05 '19 at 19:16
  • +1 for Murphy's suggestion. @MathieuFrenette: short of rolling your own, I don't quite see what more you could ask for. OP is fine, although not quite in style with what is usually asked here, but really there would be only minimal work involved in adapting Murphy's solution to what you want. In fact you could even think of sprucing the given script up by add dynamic key bindings to suit your needs if you reallly, .... really want arrow movements and arrow selection instead of typing in a number. There is a place called "diminishing returns" though, which you might want to avoid. – Cbhihe Sep 05 '19 at 21:33
1

I used to use iselect for this, many years ago.

A very basic example:

$ sel="$(iselect -a 'foo' 'bar')"
$ echo $sel
foo

From man iselect:

iSelect is an interactive line selection tool for ASCII files, operating via a full-screen Curses-based terminal session. It can be used either as an user interface frontend controlled by a Bourne-Shell, Perl or other type of script backend as its wrapper or in batch as a pipe filter (usually between grep and the final executing command). In other words: iSelect was designed to be used for any types of interactice line-based selections.

Input Data

Input is read either from the command line (line1 line2 ...) where each argument corresponds to one buffer line or from stdin (when no arguments are given) where the buffer lines are determined according to the newline characters.

You can additionally let substrings displayed in Bold mode for non-selectable lines (because the selectable lines are always displayed bold) by using the construct "<b>"..."</b>" as in HTML.

cas
  • 78,579
  • BTW, before compiling and installing iselect from source, check to see if it's already packaged for your distribution (it is for debian). Install the package if one is available. – cas Sep 06 '19 at 01:17
  • Tried that and liked it; it works much like bash select meets less, and the options are much more convenient compared to dialog or whiptail. However it doesn't show the text "within the normal flow of text" as required by the OP, as any other curses based dialog I know. – Murphy Sep 06 '19 at 07:21
  • yeah, it's pretty simple and good at what it does. I don't know what, exactly, the OP means by "within the normal flow of text" except, by example, not like dialog or zenity. iselect is quite unlike both dialog and zenity. and whiptail. And if he wants arrow-keys for cursor movement, that means ncurses (or equivalent). – cas Sep 06 '19 at 07:36
  • My guess on the intention is that he wants the output of the selection dialog to be displayed within the other output (echos etc.) of the script, and to my knowledge select is the only option here. – Murphy Sep 06 '19 at 07:39
  • I will accept this answer, as it's the closest to what I'm looking for. Despite not displaying the menu inline, it's interactive and very usable. I particularly like that it outputs the selected value to stdout, so it can be piped into another command. Thanks cas and @Murphy for taking the time and effort to reply and don't hesitate to upvote my question if you feel like it, as it's not obvious to gain the first few reputation points as a newcomer! ;) – Mathieu Frenette Sep 06 '19 at 15:04
  • I was able to compile iselect on my Gentoo system at home. However it has issues: The OSSP FTP server isn't available anymore, so I had to get the source tarball from Ubuntu. The code has last been touched 2007, there's a known buffer overflow, and the configure script needs to be patched to address a missing keypad reference (add -ltinfo to the libs). – Murphy Sep 06 '19 at 23:51
  • 1
    The exploit seems to be of the order of "if you have a shell and run iselect with bad args, you can get it to give you a shell". no privilege escalation...would be bad if combined with sudo but if sudo is configured to allow an untrusted user to run something obscure like iselect, it's probably configured to allow any command to be run with sudo, including sudo bash. it's not good, but probably not worth worrying about. – cas Sep 07 '19 at 00:22
1

Here is my take on that question using the bash.

#!/usr/bin/env bash

# Renders a text based list of options that can be selected by the
# user using up, down and enter keys and returns the chosen option.
#
#   Arguments   : list of options, maximum of 256
#                 "opt1" "opt2" ...
#   Return value: selected index (0 for opt1, 1 for opt2 ...)


figlet -st "$COLUMNS" "Welcome $USER"
printf '\n?%s\n?%s\n?%s\n\n' "What's the name of your website simple-site" "What's the description of your website(optional):" "Please choose lincense":

# Change the value of options to whatever you want to use.
options=("MIT" "Apache-2.0" "GPL-3.0" "Others")

select_option (){
  # little helpers for terminal print control and key input
  ESC=$(printf '%b' "\033")

  cursor_blink_on() {
    printf '%s' "$ESC[?25h"
  }

  cursor_blink_off() {
    printf '%s' "$ESC[?25l"
  }

  cursor_to() {
    printf '%s' "$ESC[$1;${2:-1}H"
  }

  print_option() {
    printf '   %s ' "$1"
  }

  print_selected() {
    printf '  %s' "$ESC[7m $1 $ESC[27m"
  }

  get_cursor_row() {
    IFS=';' read -sdR -p $'\E[6n' ROW COL; printf '%s' ${ROW#*[}
  }

  key_input() {
    read -s -n3 key 2>/dev/null >&2
    if [[ $key = $ESC[A ]]; then
      echo up
    fi
    if [[ $key = $ESC[B ]]; then
      echo down
    fi
    if [[ $key = ""  ]]; then
      echo enter
    fi
  }

   # initially print empty new lines (scroll down if at bottom of screen)
   for opt; do
     printf "\n"
   done

   # determine current screen position for overwriting the options
   local lastrow=$(get_cursor_row)
   local startrow=$(($lastrow - $#))

   # ensure cursor and input echoing back on upon a ctrl+c during read -s
   trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
   cursor_blink_off

   local selected=0
   while true; do
     # print options by overwriting the last lines
     local idx=0
     for opt; do
       cursor_to $((startrow + idx))
       if [[ $idx == $selected ]]; then
         print_selected "$opt"
       else
         print_option "$opt"
       fi
       ((idx++))
     done

     # user key control
     case $(key_input) in
       enter) break;;
       up)    ((selected--));
         if (( $selected < 0 )); then selected=$(($# - 1)); fi;;
         down)  ((selected++));
           if (( selected > $# )); then selected=0; fi;;
         esac
       done

       # cursor position back to normal
       cursor_to $lastrow
       printf "\n"
       cursor_blink_on

       return "$selected"
}


select_option "${options[@]}"
choice=$?

index=$choice
value=${options[$choice]}

case $value in 
  MIT)  ## User selected MIT
   read -rp "Really use? $value [Y/N] " answer
   [[ $answer ]] || { echo "No answer!" >&2; exit 1; }
   if [[ $answer == [Yy] ]]; then
     : ## User selected Y or y, what are you going to do about it?
   elif [[ $answer == [Nn] ]]; then
     : ## User selected N or n what are you going to do about it?
   fi
   printf '%s\n' "You have choosen $answer";;
  Apache-2.0)
   read -rp "Really use? $value [Y/N] " answer
   [[ $answer ]] || { echo "No answer!" >&2; exit 1; }
   if [[ $answer == [Yy] ]]; then
     :
   elif [[ $answer == [Nn] ]]; then
     :
   fi
   ;;
  GPL-3.0)
   read -rp "Really use? $value [Y/N] " answer
   [[ $answer ]] || { echo "No answer!" >&2; exit 1; }
   if [[ $answer == [Yy] ]]; then
     :
   elif [[ $answer == [Nn] ]]; then
     :
   fi
   ;;
  Others)
   read -rp "Really use? $value [Y/N] " answer
   [[ $answer ]] || { echo "No answer!" >&2; exit 1; }
   if [[ $answer == [Yy] ]]; then
     :
   elif [[ $answer == [Nn] ]]; then
     :
   fi
   ;;
esac

The output is more or less what is in the https://i.stack.imgur.com/RO9E5.png The : does nothing which is inside the if statement, Replace : with your code/process or whatever you want to do if the answer is [Yy] or [Nn] That should be enough hand holding to get you started. By the way that code that captures the movement I found posted somewhere I've just expanded/rewrite some syntax.

Jetchisel
  • 1,264