3

Pressing Enter still does its delimiter job but the read command just ends quietly, abstaining from messing with the console scrolling. Basically a read -s that affects only the endline.

  • Going by your previous question, it looks like you're trying to get read to do something weird and specific. What is the actual problem you're trying to solve here? – Olorin Jan 28 '18 at 10:01
  • that seems basically impossible read doesn't echo anything. the terminal driver does it. so you can't do what you want using read. – Jasen Jan 28 '18 at 10:55
  • 1
    Would stty eol2 ' '; var=$(dd bs=9999 count=1 2> /dev/null) instead of read var and use space instead of enter to accept the input be an option? – Stéphane Chazelas Jan 28 '18 at 11:07
  • 1
    @Jasen, in bash, if you use read -e (or in ksh93 if you set the emacs/gmacs/vi options or with zsh's vared), it's read that does the outputting instead of the terminal driver. There might be a way to configure those not to output CRLF upon CR input. One could at least record the cursor position and restore it afterwards. – Stéphane Chazelas Jan 28 '18 at 11:09
  • @StéphaneChazelas I found the dd approach pretty interesting. – glarry Jan 28 '18 at 19:17
0

You could invoke zsh's line editor (which is fully configurable and generally a lot more advanced than readline (which bash can invoke with read -e)) like:

var=$(
  saved_tty=$(stty -g)
  var=default-value zsh -c '
    zle-line-finish() { # hook run upon leaving the line editor (zle)
      CURSOR=$#BUFFER # move the cursor to the end
      zle -R          # force a redraw of the editor
      printf %s $BUFFER # output value on stdout
      kill $$ # kill ourself to prevent zle cleanup
    }
    zle -N zle-line-finish
    vared -p "Text before [" var'
  # we need to restore the tty settings by ourselves, as we prevented zsh
  # from doing so when killing it:
  stty "$saved_tty"
)
printf '] Text after\n'

printf 'var = "%s"\n' "$var"

Upon running, that gives:

Text before [value edited] Text after
var = "value edited"

While bash now lets you bind keys to shell code widgets, it clears the content of the current line prior to executing the widget, so you'd have to redraw the prompt and value upon your Return handler:

var=$(prompt="Text before [" var=default-value bash -c '
  bind -x '\''"\r":printf >&2 %s "$prompt$READLINE_LINE";  printf %s "$READLINE_LINE"; exit'\'' 2> /dev/null
  IFS= read -rep "$prompt" -i default-value')

printf '] Text after\n'
printf 'var = "%s"\n' "$var"
  • What's wrong with var=$(bash -c 'bind -x '\''"\r":printf >&2 %s "$READLINE_LINE"; printf %s "$READLINE_LINE"; exit'\'' 2> /dev/null; IFS= read -re') ? I (think I) know the answer, but somebody has to ask. Also, how come \r and not \n? It's not like I'm on a Mac. – glarry Jan 29 '18 at 22:37
  • @glarry, the return key sends CR regardless of the system and terminal. When the terminal device line discipline is in the icanon mode, it is translated to LF on input by the line discipline, but here we're not in icanon mode as zle/readline implement their own line editor. – Stéphane Chazelas Jan 30 '18 at 9:38
  • @glarry, about what's wrong...?, since readline clears the whole line, we need to redraw both the prompt and the text you entered. Also note that (contrary to the zsh approach), if you've entered control characters (like with ctrl+V ctrl+H), they would appear as ^H while editing, but when we redisplay them that ^H aka BS character would be sent as-is and mangle the display. – Stéphane Chazelas Jan 30 '18 at 9:42
  • The zsh zle is not returning the stty to what it was. Running your code removes the echo of stty. Add an stty -a at the end to see it – Isaac Jan 31 '18 at 16:41
  • @issac, good point, that's because we're killing zsh so it can't restore the settings. See edit. – Stéphane Chazelas Jan 31 '18 at 16:47
2

Knowing that read sets the read variable (with the n1 option) to an empty value if the read character is an enter you can do something like:

#!/bin/bash

while IFS= read -srn1 a ;do
    [[ "${a+x$a}" = "x" ]] && break
    var=$var$(printf '%s' "$a")
    printf '%s' "$a"
done
printf '\n%s\n' "$var"

Note that characters that are captured by the stty or some others will not be translated to a byte value:

All control characters except:

  1. ^C (ASCII 03 ETX )
  2. ^J (ASCII 0A LF )
  3. ^M (ASCII 0D CR )
  4. ^Z (ASCII 1A SUB )
  5. ^\ (ASCII 1C FS )

To actually "see" the characters backspace over the previous ones add this loop to print var (just after the code from above):

echo
while IFS= read -srn1 a; do
    printf '%s' "$a"
    sleep 0.5
done <<<"$var"

Edit 3

To get the backspace to erase one character, not capture such character and print the modified string try this:

#!/bin/bash

while IFS= read -srn1 a ;do
    [[ "${a+x$a}" = "x" ]] && break

    if [[ $a = $'\x7f' || $a = $'\x08' ]]; then
        var=${var%?}
        [[ $a = $'\x7f' ]] && printf '\x0d%s \x08' "$var"
        [[ $a = $'\x08' ]] && printf '\x0d%s ' "$var"
    else
        var=$var$(printf '%s' "$a")
    fi

    printf '%s' "$a"
done
printf '\n%s\n' "$var"
printf '%s' "$var" | od -An -tx1

while IFS= read -srn1 a; do
    printf '%s' "$a"
    sleep 0.5
done <<<"$var"
  • Actually I have no idea how to use your answer. How on earth do you store... that in a variable? Please show me. I didn't ask this question because I wanted read to behave differently while I watched it, but I was planning to... use read? :) – glarry Jan 28 '18 at 20:14
  • I was afraid you'd say that. (I sent that comment because I found it funny and I didn't want to lose its window of opportunity.) The thing is, unlike that question, this one actually wants backspace to be shown as deleting characters so it dislikes the read -n1 approach, which is either a dead end or it involves/requires further complication. – glarry Jan 28 '18 at 23:07
  • Again, this question has nothing to do with the other one. @isaac During the input typing/editing before pressing enter, I want backspace to actually delete text; and I want the user to see the deleting. Even more clearly put, I want the normal read behavior with the endline requirement as the only exception; no \x7fs or \x08s in the resulting variable. With your script, backspace doesn't delete anything and ^H just goes back one character without deleting anything (but I don't want the user to have to use ^H instead of backspace anyway). – glarry Jan 29 '18 at 8:02
  • @glarry That is simply not possible. The character ^H does not erase any character, just goes one position backwards. To do what you are asking for, you need to special case every special character in the loop. And some characters will not be captured in the variable (like the ones erased by a ^H). I really fail now to understand what is it that you want captured and what not. – Isaac Jan 29 '18 at 12:52
  • @glarry Please read Edit 3 (and could you remove the now old comments). – Isaac Jan 29 '18 at 13:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.