# ================= # read_chars.bash # ================= # # Bash library for reading from tty and writing to standard out. # Can be suspended and resumed from bash (or with unix job control in # any shell). # # exporting for users: # # read_chars() <-- calls readchar in a loop # # readchar() <-- reads one character # # readchar_init() <-- must be called before readchar() in the same subshell # # The function read_chars is also available as the executable read-tty cfmakeraw() { cmd=(stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8) "${cmd[@]}" "$@" >/dev/null } i() { if [ "$DEBUG" ] then printf 'I: %s\n' "$*" >&2 fi } x() { if [ "$DEBUG" ] then printf '+ %s\n' "${*@Q}" >&2 fi "$@" } readchar_init() { declare -g INIT_TTY=y declare -g t declare -g -a readchar_termopts [ -t 1 ] && t=y || t= readchar_termopts=( opost onlcr isig intr undef quit undef ) trap "i CONT; INIT_TTY=y" CONT for sig in TSTP TTIN TTOU do trap "i $sig; stop_self" $sig done for sig in INT TERM do trap "i $sig; exit" $sig done } stop_self() { INIT_TTY=y kill -STOP $$ $BASHPID } check_foreground() { read _pid _comm _state _ppid pgrp _session _tty_nr tpgid _rest < /proc/self/stat [ "$tpgid" = "$pgrp" ] } readchar() { declare -n REPLY="${1:-CHARACTER}" if [ -t 0 ] then if ! check_foreground then stop_self return fi fi if [ "$INIT_TTY" ] then x cfmakeraw "${readchar_termopts[@]}" INIT_TTY= fi if POSIXLY_CORRECT=y read -n 1 -d '' -r -s then if [ "$t" ] then printf -v quoted_reply "%s" "${REPLY@Q}" if (( (count += 1 + ${#quoted_reply}) < ${COLUMNS:-80} - 10 )) then seperate=' ' else seperate=$'\n' count=0 fi printf "%s" "$seperate" "$quoted_reply" else if [ "$REPLY" ] then printf "%s" "$REPLY" else printf "\0" fi fi else r=$? if [ "$r" = 148 ] then INIT_TTY=y fi [ $r -gt 128 ] fi } read_chars() { readchar_init while readchar do continue done }