9

Not sure if I should put this on stackoverflow or unix.stackexchange but I found some similar questions here, so here it goes.

I'm trying to create a script to be called by .bashrc that allows me to select one of two options based on a single keystroke. That wouldn't be hard normally but I want the two keys corresponding to the two options to be space and enter.

Here's what I got so far:

#!/bin/bash

SELECT=""
while [[ "$SELECT" != $'\x0a' && "$SELECT" != $'\x20' ]]; do
    echo "Select session type:"
    echo "Press <Enter> to do foo"
    echo "Press <Space> to do bar"
    read -s -N 1 SELECT
    echo "Debug/$SELECT/${#SELECT}"
    [[ "$SELECT" == $'\x0a' ]] && echo "enter" # do foo
    [[ "$SELECT" == $'\x20' ]] && echo "space" # do bar
done

The following output is what I get if I press enter, space, backspace and x:

:~$ bin/sessionSelect.sh
Select session type:
Press <Enter> to start/resume a screen session
Press <Space> for a regular ssh session
Debug//0
Select session type:
Press <Enter> to start/resume a screen session
Press <Space> for a regular ssh session
Debug//0
Select session type:
Press <Enter> to start/resume a screen session
Press <Space> for a regular ssh session
Debug//1
Select session type:
Press <Enter> to start/resume a screen session
Press <Space> for a regular ssh session
Debug/x/1

So both enter and space result in an empty SELECT. No way to distinguish the two. I tried to add -d 'D' to the read options, but that didn't help. Maybe someone can point me in the right direction.

The bash version would be 4.2 btw.

Community
  • 1
  • 1
a.peganz
  • 318
  • 1
  • 4
  • 11

3 Answers3

5

Try setting the read delimiter to an empty string then check the builtin $REPLY variable:

read -d'' -s -n1

For some reason I couldn't get it to work specifying a variable.

Cole Tierney
  • 9,571
  • 1
  • 27
  • 35
  • This works nicely, even without changing the delimiter with -d. Both enter and space are correctly detected. – a.peganz Apr 03 '14 at 17:58
  • 1
    We've probably got different flavors of `read`. If I leave out -d'', I don't get the enter key. – Cole Tierney Apr 03 '14 at 21:56
  • I only just noticed that we use -n and -N respectively. That must be the reason for that, when specifying the length with -N newlines aren't treated specially. – a.peganz Apr 04 '14 at 04:09
  • I was going to test with the -N option, but my read doesn't have it. – Cole Tierney Apr 04 '14 at 11:21
4
#!/bin/bash
SELECT=""
# prevent parsing of the input line
IFS=''
while [[ "$SELECT" != $'\x0a' && "$SELECT" != $'\x20' ]]; do
  echo "Select session type:"
  echo "Press <Enter> to do foo"
  echo "Press <Space> to do bar"
  read -s -N 1 SELECT
  echo "Debug/$SELECT/${#SELECT}"
  [[ "$SELECT" == $'\x0a' ]] && echo "enter" # do foo
  [[ "$SELECT" == $'\x20' ]] && echo "space" # do bar
done
Andrey
  • 2,503
  • 3
  • 30
  • 39
  • `IFS=''` is not required. `read -N` will return input as-is, ignoring IFS (see man page). Tested this without `IFS=''`, works fine. Great answer! Used this to implement my own version of '--More--'. – Fonic May 10 '19 at 19:53
  • @Maxxim It might be in a newer Bash version. At least for Centos 7.6 with bash-4.2.46(2) the `IFS=''` is required. – Andrey May 13 '19 at 13:46
  • You are correct. The change was introduced in **Bash 4.4**. In the `man` page on `read -N nchars`, the following was added: _[...] The result is not split on the characters in IFS; the intent is that the variable is assigned exactly the characters read (with the exception of backslash; see the -r option below)._ Care to edit your answer and point that out, would help others? – Fonic May 14 '19 at 17:17
  • @Maxxim based on this (unofficial) [list of changes](https://github.com/bminor/bash/blob/master/CHANGES), it's hard to say how exactly the given bash instance behaves (there were many bugs with the IFS treatment). Also do not forget about [backporting](https://access.redhat.com/security/updates/backporting). – Andrey May 20 '19 at 16:14
3

There are a couple of things about read that are relevant here:

  • It reads a single line
  • The line is split into fields as with word splitting

Since you're reading one character, it implies that entering Enter would result into an empty variable.

Moreover, by default rules for word splitting, entering Space would also result into an empty variable. The good news is that you could handle this part by setting IFS.

Change your read statement to:

IFS= read -s -n 1 SELECT

and expect a null string instead of $'\x0a' when entering Enter.

devnull
  • 118,548
  • 33
  • 236
  • 227
  • This works as well and with setting IFS only for the read command is basically as clean a solution. I still marked putnamhill's as the correct one because it can't produce accidental side effects through a changed IFS. Upvoted though! Btw, with -N instead of -n SELECT is not empty when sending enter but actually contains the expected value. – a.peganz Apr 03 '14 at 18:00