1

in shell script when you have the following :

read my_variable

Enter is the key that saves your input.

is there a way to make Tab accomplish the same as Enter without removing Enter's functionality?

2
  • Using read -d $'\t' variable would use a tab instead of a newline as the delimiter, but that's not what you want, right?
    – Kusalananda
    Jul 19, 2019 at 21:35
  • no I'd like both ideally. but thanks for the tip!
    – tatsu
    Jul 19, 2019 at 22:17

2 Answers 2

2

It may be overkill but you could obtain that by using read -e, which enables the Readline facility on the read utility. At that point your desired result would be only one key-binding away.

Careful though that Readline brings along many other functionalities too, like completion, history, etc., which you might not want for a simple read my_variable. If those are undesirable, you have to explicitly clear the key-bindings and disable the functionalities you don't want for your read -e.

Sample proof-of-concept from command-line:

(bind 'TAB: accept-line'; IFS= read -re var && echo "$var" || echo ko)

You can do that in a script too, although bind will give a warning (which you can still mute by redirecting 2>/dev/null).

An alternative to bind commands in a script is to provide a custom inputrc file prior to invoking the script that you want to be affected. It's not necessary to have a real file, a Here Document suffices.

The above example made through scripts:

#!/bin/bash

export INPUTRC=/dev/fd/3

script2.sh 3<<EOF
TAB: accept-line
set history-size 0
EOF
# this example 'inputrc'-like file also disables history support

The above script prepares the custom inputrc file as a Here Document on file-descriptor 3, which the shell running script2.sh will read as indicated by the INPUTRC environment variable.

Then script2.sh:

#!/bin/bash

echo start
bind -q accept-line 2>/dev/null  # shows which keys are configured to accept input
IFS= read -re var && echo "$var" || echo ko
echo end

Before waiting for input on the read, the script will print something like:

accept-line can be invoked via "\C-i", "\C-j", "\C-m".

showing that Tab (i.e. Ctrl-I shown above as \C-i) accepts a line just as well as a Return (i.e. Ctrl-M, carriage-return) or a newline (Ctrl-J).

For a more "real world" example:

#!/bin/bash

bind 'TAB: accept-line' &>/dev/null

echo "enter your name:"
IFS= read -re var 

echo "your name is: $var"

If you go down this path, have a look at Readline user's guide, at least the reduced one in your man bash. The set convert-meta off setting among others may worth a particular mention in order to have better support for non-ascii characters.

2
  • here's what I used : #!/bin/bash; bind 'TAB: accept-line' &>/dev/null; echo "enter your name:"; IFS= read -e var; echo "your name is: $var" thank you very much. It blows my mind that this is actually possible, yes there are caveats but I think I can work around them. what does IFS do?
    – tatsu
    Jul 20, 2019 at 22:05
  • 1
    @tatsu try without the IFS= and see what happens when you type leading or trailing spaces ;-) Actually, I had also forgot a worthy -r to the read, which prevents it interpret backslashed characters. (Normally one wants them untouched in cases like yours)
    – LL3
    Jul 20, 2019 at 23:30
1

The way that could be done in general is by setting the termios(3)/stty(1) VEOL/eol special char ("alternate end-of-line") to Tab. On systems which support it, VEOL2/eol2 could be used, too.

Unfortunately, the read shell built-in will always try to read up to a newline (or another delimiter in bash if -d was used, with no support for multiple delimiters).

One way that could be worked around is by using a var=$(dd count=1 bs=big) command substitution instead of read. Here is an example of such a kludge:

while :; do

        t=$(stty -g)
        stty eol ^I
        trap : INT
        var=$(dd bs=10k count=1 2>/dev/null; echo x)
        stty "$t"
        trap - INT
        var=${var%x}
        [ "$var" ] || break
        var=${var%[$'\n\t']}

        printf '{%s}\n' "$var"

done

The t=(stty -g) and stty "$t" will save and restore the terminal settings.

The trap : INT and trap - INT will prevent a Control-C from killing the shell/loop (with all the complications and shell-specific behavior that that entails) and let it only kill the dd and the command substitution.

The $(...; echo x) followed by ${var%x} is a trick to prevent a trailing newline from being stripped from the command substituition; this is necessary in order to be able to differentiate between the user having pressed just Enter for an empty variable, and Control-C or Control-D to exit the loop.

Finally, the ${var%[$'\n\t']} will remove the trailing newline or tab from the variable. In shells which do not support $'...' strings like dash or yash, that could be replaced with:

nltab='<literal tab here>
'
...
var=${var%[$nltab]}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .