12

Is there a standard (POSIX) way of asking the user some data from within a shell script, with read for example, while allowing live edition of the text being typed (what readline does)?

I know bash has read -e varname that allows for the person launching the script to use keyboard arrows for exemple, to edit or correct what has just been typed without deleting the last entered characters with backspace.

However, read -e is bash specific. And still, it is pretty cumbersome to delete all that has been written if you realise you made a mistake at the beginning of your long sentence...

2

1 Answer 1

11

The terminal driver does have line editing capabilities on most systems. You'll notice that you can use Backspace, Ctrl-U, sometimes Ctrl-W.

readline is a GNU library maintained alongside bash. There's nothing POSIX about it. POSIX defines an optional line editor (with vi key binding) for sh, but no provision to use it outside of sh.

The ksh93 shell uses that vi-style line editor (also supports emacs or gmacs-style) for its read builtin when both stdin and stderr are a terminal and the corresponding option has been set: set -o emacs; IFS= read -r var for instance for read to use an emacs-style line-editor.

POSIX does specify the vi editor though (optional), so you could invoke vi to edit the content of a temporary file.

The zsh equivalent of bash's read -e is vared (a lot more advanced as it's using zsh's zle (zsh line editor)).

In other shells, you can use some wrappers around readline or other line-editing libraries (like rlwrap), or you can invoke bash -c 'read -e...' or zsh -c 'vared...'.

What you could also do is give the opportunity to the user to launch an editor.

Like:

if ! IFS= read -r var; then if [ -n "$var" ]; then tmp=$(create_tempfile) # create_tempfile left as an exercise printf '%s\n' "$var" > "$tmp" "${VISUAL:-${EDITOR:-vi}}" -- "$tmp" var=$(cat < "$tmp") rm -f -- "$tmp" else exit 1 # real EOF? fi fi 

Then the user can press Ctrl-D twice to launch an editor on what he has already entered.

Otherwise, I once wrote that function that should work on most terminals on most Unices that implements a simple line editor.

LE() { # shell Line Editor. Extremely slow and stupid code. However it # should work on ansi/vt100/linux derived terminals on POSIX # systems. # Understands some emacs key bindings: CTRL-(A,B,D,E,F,H,K,L) # plus the CTRL-W and CTRL-U normal killword and kill. # no Meta-X key, but handling of <Left>, <Right>, <Home>, <End> # <Suppr>. # # Args: # [1]: prompt (\x sequences recognized, defaults to "") # [2]: max input length (unlimited if < 0, (default)) # [3]: fill character when erasing (defaults to space) # [4]: initial value. # Returns: # 0: OK # 1: od(d) error or CTRL-C hit LE_prompt=$1 LE_max=${2--1} LE_fill=${3-" "} LE_backward() { LE_s=$1 while [ "x$LE_s" != x ]; do printf '\b%s' "$2" LE_s=${LE_s%?} done } LE_fill() { LE_s=$1 while [ "x$LE_s" != x ]; do printf %s "$LE_fill" LE_s=${LE_s%?} done } LE_restore='stty "$LE_tty" LC_COLLATE='${LC_COLLATE-"; unset LC_COLLATE"} LE_ret=1 LE_tty=$(stty -g) LE_px=$4 LE_sx= LC_COLLATE=C stty -icanon -echo -isig min 100 time 1 -istrip printf '%b%s' "$LE_prompt" "$LE_px" while set -- $(dd bs=100 count=1 2> /dev/null | od -vAn -to1); do while [ "$#" -gt 0 ]; do LE_k=$1 shift if [ "$LE_k" = 033 ]; then case "$1$2$3" in 133103*|117103*) shift 2; LE_k=006;; 133104*|117104*) shift 2; LE_k=002;; 133110*|117110*) shift 2; LE_k=001;; 133120*|117120*) shift 2; LE_k=004;; 133106*|117106*) shift 2; LE_k=005;; 133061176) shift 3; LE_k=001;; 133064176) shift 3; LE_k=005;; 133063176) shift 3; LE_k=004;; 133*|117*) shift while [ "0$1" -ge 060 ] && [ "0$1" -le 071 ] || [ "0$1" -eq 073 ]; do shift done;; esac fi case $LE_k in 001) # ^A beginning of line LE_backward "$LE_px" LE_sx=$LE_px$LE_sx LE_px=;; 002) # ^B backward if [ "x$LE_px" = x ]; then printf '\a' else printf '\b' LE_tmp=${LE_px%?} LE_sx=${LE_px#"$LE_tmp"}$LE_sx LE_px=$LE_tmp fi;; 003) # CTRL-C break 2;; 004) # ^D del char if [ "x$LE_sx" = x ]; then printf '\a' else LE_sx=${LE_sx#?} printf '%s\b' "$LE_sx$LE_fill" LE_backward "$LE_sx" fi;; 012|015) # NL or CR LE_ret=0 break 2;; 005) # ^E end of line printf %s "$LE_sx" LE_px=$LE_px$LE_sx LE_sx=;; 006) # ^F forward if [ "x$LE_sx" = x ]; then printf '\a' else LE_tmp=${LE_sx#?} LE_px=$LE_px${LE_sx%"$LE_tmp"} printf %s "${LE_sx%"$LE_tmp"}" LE_sx=$LE_tmp fi;; 010|177) # backspace or del if [ "x$LE_px" = x ]; then printf '\a' else printf '\b%s\b' "$LE_sx$LE_fill" LE_backward "$LE_sx" LE_px=${LE_px%?} fi;; 013) # ^K kill to end of line LE_fill "$LE_sx" LE_backward "$LE_sx" LE_sx=;; 014) # ^L redraw printf '\r%b%s' "$LE_prompt" "$LE_px$LE_sx" LE_backward "$LE_sx";; 025) # ^U kill line LE_backward "$LE_px" LE_fill "$LE_px$LE_sx" LE_backward "$LE_px$LE_sx" LE_px= LE_sx=;; 027) # ^W kill word if [ "x$LE_px" = x ]; then printf '\a' else LE_tmp=${LE_px% *} LE_backward "${LE_px#"$LE_tmp"}" LE_fill "${LE_px#"$LE_tmp"}" LE_backward "${LE_px#"$LE_tmp"}" LE_px=$LE_tmp fi;; [02][4-7]?|[13]??) # 040 -> 177, 240 -> 377 # was assuming iso8859-x at the time if [ "$LE_max" -ge 0 ] && LE_tmp=$LE_px$LE_sx \ && [ "${#LE_tmp}" -eq "$LE_max" ]; then printf '\a' else LE_px=$LE_px$(printf '%b' "\0$LE_k") printf '%b%s' "\0$LE_k" "$LE_sx" LE_backward "$LE_sx" fi;; *) printf '\a';; esac done done eval "$LE_restore" REPLY=$LE_px$LE_sx echo return "$LE_ret" } 

To be used as:

LE 'Prompt: ' 

Or:

LE 'Prompt: [....]\b\b\b\b\b' 4 . DEF 

if you want a maximum length and/or different filling character and/or an initial value.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.