Search code examples
bashechosttytput

Stop echo and consume all user input in Bash


TARGET

Until some sub-task in script completes its job:

  • stop echo;
  • disable cursor;
  • consume all user input;
  • do not block interrupts (Ctrl+C, etc.).

WHAT HAVE DONE

For now, using this answer, I create few functions for that, here they are:

function hide_input()
{
  if [ -t 0 ]; then
    stty -echo -icanon time 0 min 0
  fi
}

function reset_input()
{
  if [ -t 0 ]; then
    stty sane
  fi
}

function stop_interactive()
{
  trap reset_input EXIT
  trap hide_input CONT
  hide_input
  tput civis
}

function start_interactive()
{
  tput cnorm
  reset_input
}

function consume_input()
{
  local line
  while read line; do line=''; done
}

And here is the way they are used:

echo "Warn the user: the job will be started."
read -p "Continue? [yes/no] > "
if [ "$REPLY" == "yes" ]; then
  stop_interactive  # <== from here all input should be rejected
  echo "Notify the user: job starting..."

  # << ------ here goes some long job with output to terminal ------>

  echo "Notify the user: job done!"
  consume_input # <== here I trying to get all user input and put nowhere
  start_interactive # <== from here restore normal operation
else
  echo "Aborted!"
  exit 0
fi

THE PROBLEM

The problem is: current 'solution' does not work. When I press keys during long running job they appears on the screen, and pressing 'Enter' ruins all output with cursor movements. Furthermore, after 'start_interactive' function call all input appears on the terminal screen.

What is the right solution for this task?

SOLUTION

Final working solution is:

function hide_input()
{
  if [ -t 0 ]; then
    stty -echo -icanon time 0 min 0
  fi
}

function reset_input()
{
  if [ -t 0 ]; then
    stty sane
  fi
}

function consume_input()
{
  local line
  while read line; do line=''; done
}

function stop_interactive()
{
  trap reset_input EXIT
  trap hide_input CONT
  hide_input
  tput civis
}

function start_interactive()
{
  consume_input
  trap - EXIT
  trap - CONT
  tput cnorm
  reset_input
}

echo "Warn the user: the job will be started."
read -p "Continue? [yes/no] > "
if [ "$REPLY" == "yes" ]; then
  stop_interactive
  echo "Notify the user: job starting..."
  do_the_job &
  pid=$!
  while ps $pid > /dev/null ; do
    consume_input
  done
  echo "Notify the user: job done!"
  start_interactive
else
  echo "Aborted!"
  exit 0
fi

Solution

  • Based in your question, there are many questions "why" if I look at your code. If you do not want to change the behavior of ^C etc., then don't use traps. All your functions test whether the file descriptor 0 is a terminal. Do you plan to use the script in a pipe? Also, your consuming of user input will continue until end-of-file, so the script may never end.

    Based on your question, I would write something like this:

    echo "Warn the user: the job will be started."
    read -p "Continue? [yes/no] > "
    if [ "$REPLY" == "yes" ]; then
        stty -echo
        echo "Notify the user: job starting..."
        program_to_execute &
        pid=$!
        while ps $pid > /dev/null ; do
            read -t 1 line
        done
        echo "Notify the user: job done!"
    else
        echo "Aborted!"
    fi
    stty sane