4

So here's my deal: working in BASH, I have already built out a function which works just fine that accepts an array or any number of parameters, and spits out an interactive menu, navigable by arrows up or down, and concluding with the user hitting enter, having highlighted the menu item they desire (the output of which is either the index or the menu item's value, depending on how the menu is initiated):

Sample output of menu method

That's all working fine; I render the menu, then respond to the events parsed from the user's input to the invisible prompt of a read command (auto-triggered after the collection of 3 characters): read -s -n 3 key 2>/dev/null >&2 The output, having been fed into a $key variable is then run through a case statement evaluating against the predicted acceptable inputs: \033[A (up) \033[A (down) "" (enter) which in turn fire the behaviors desired.

However, it then dawned of me that with the introduction of 7+ menu items (we may presuppose it shall not exceed 10) it'd be nice to let the user tap the numeric entry of the desired menu item, which would highlight the item in question without submitting it.

My problem is this: I got that working just fine too, BUT the user, having typed the numeric key desired (5 in the case of this example) is then obliged to hit the enter key for the read statement to trigger its effect, courtesy of my -n 3 modifier flags on my read. This is counter to the usability model already established, unless, of course, they hit their numeric selection thrice, thereby triggering the 3-char minimum requirement (which is equally counterintuitive).

The issue is that \033[A is treated as 3 characters, thereby necessitating the -n 3. 0-9 are treated as SINGLE characters (meaning if I change that to a -n 1, THEY behave as expected, but now the arrow keys fail, collecting only the escape character).

So, I guess what I'm wondering is: is there a way to listen for a -n 1 {OR} 3 (whichever comes first)? I cannot seem to send a \n or \r or similar, as until the read has resolved, they have no effect (meaning I have found no means to simply leave the -n 3 while running a parallel process to check if the entered value is a 0-9 should it prove a single character).

I'm NOT MARRIED to this approach. I'm fine with using awk or sed, or even expect (though that last one I'm confused about still). I don't care if it's a read that does the collecting.

Edit:

SOLUTION

read -n1 c
case "$c" in
    (1) echo One. ;;
    (2) echo Two. ;;
    ($'\033') 
        read -t.001 -n2 r
        case "$r" in
            ('[A') echo Up. ;;
            ('[B') echo Down. ;;
        esac
esac

Status: Resolved

@choroba to the rescue!

Solution Explanation

I'll do my best to paraphrase:

His solution involved nesting the two read statements (I'd been trying them sequentially) It was this, coupled with the -t.001 (thereby setting a near-instant timeout on the function) that enabled the carryover read.

My problem was that the escape keys I'd been monitoring were 3 characters in length (hence my setting the -n3 flag). It wasn't until afterwards that it occurred to me that accepting certain single-character inputs would be advantageous, too.

His solution was to suggest a **($'\033') case:

Basically

  1. 'Upon reading the escape character...' (**($'\033'))
  2. Create anotherread`` (this time awaiting TWO characters), and set to timeout after a nanosecond and precluding backslashes on escape chars.

Since the behavior of read apparently is to "spillover" the remaining input into the next read statement, said statement started its countdown-to-timeout with the sought value having already been seeded. Since that met the defined requirement flags for the read, then it became a simple matter of testing the second set of characters for the case result (and since the initializing function is still getting the response its expecting, albeit from a different statement, the program carries on as though it had gotten the results I'd been trying to puzzle my way to in the first place.

  • Have you considered using things like dialog/whiptail or shells with builtin support for ncurses like zsh. – Stéphane Chazelas Nov 06 '18 at 12:48
  • Alas, the organization I'm writing the piece for is a BASH shop (I'm running ZSH on my MBP and MP at home, and this has already proven to be a hill I'm not prepared to die on). I've been able to bullyrag them into upgrading to 4.4.23, but I have a sneaking suspicion that it'd be faster to recode the whole damn thing onto punchcards than to persuade them into performing what should otherwise be a routine and expedient upgrade. (Note: I've been a developer for 25 years; it's just BASH I'm new to, lol) – ZenAtWork Nov 06 '18 at 19:27
  • 2
  • Appreciate the thoroughness mate, but as I stated in the description: I have had the stuff covered in your possible duplicate reference working long before posting my inquiry. It was the ability to detect inputs of varying byte-lengths from within the same read statement that had me stumped (and which @choroba, below, was kind enough to show me how to resolve). – ZenAtWork Nov 07 '18 at 02:24

1 Answers1

4

You can read for -n 1, and read the following two if the first one is \033 and react accordingly. Otherwise, handle the number directly.

#!/bin/bash

read -n1 c
case "$c" in
    (1) echo One. ;;
    (2) echo Two. ;;
    ($'\033') 
        read -t.001 -n2 r
        case "$r" in
            ('[A') echo Up. ;;
            ('[B') echo Down. ;;
        esac
esac
choroba
  • 47,233
  • 1
    How do I ingest the following two? Just test for the presence of the first one and instantly kick off another read, assuming the initial one was an escape instead of a numeric? – ZenAtWork Nov 06 '18 at 19:23
  • 1
    A few quick tests later... I'm trying to react to the equivalent of the user's keyup event. If I test for 1 character, the interactivity behavior for said character appears to be correct, assuming it's a single character input. That is, if I hit 5, it reacts instantly. Great. on the 3-character inputs, however, the read -n1 appears to discard the other 2. That is, I can capture the ESC but the rest of the user input seems to have been discarded. I can read again, but then the other 2 chars (which are not impending that I can see), the numerics break again. What am I missing? – ZenAtWork Nov 06 '18 at 19:48
  • 1
    @ZenAtWork: Check the update. – choroba Nov 06 '18 at 23:05
  • 1
    THAT was crystal-clear; thank you, sir! 'd been thinking sequentially, not nested. This is precisely what I was struggling with. Appreciate the assist, truly! – ZenAtWork Nov 07 '18 at 02:17