Sasgorilla
Sasgorilla

Reputation: 3130

How can I change output from a Bash script while waiting on user input?

I want to build a stopwatch in Bash, with a pause feature. It should display an incrementing counter, like this one does, but pause it when I hit the "p" key.

How should I implement that? If I wait for user input with read I can't refresh the counter on the screen at the same time. Putting the read inside a loop, with a timeout, is my best plan so far, but it's non-trivial to use a timeout less than one second, which is what I would need here. (It's not supported by read or GNU timeout.) Interrupts would work, but I'd like to support arbitrary keys like "p" and "x".

Is there a reasonably simple way to achieve this?

Upvotes: 3

Views: 921

Answers (3)

KamilCuk
KamilCuk

Reputation: 141891

Simple script with file descriptor and simple input redirection, leaving no temporary files to cleanup. The waiting is done by using read parameter -t.

counter() {
        while ! read -t 0.05 -n1 _; do
                printf '\r\t%s' "$(date +%T.%N)"
        done
}

{
        IFS= read -p "Your name, Sir?"$'\n' -r name
        echo >&3
} 3> >(counter "$tmp")

echo "Sir $name, we exit"

Example output:

Your name, Sir?
    2:12:17.153951623l
Sir Kamil, we exit

Upvotes: 1

jschnasse
jschnasse

Reputation: 9588

Print to console while waiting for user input

  1. Write one function that creates the output (example with: counter or if you like spin).
  2. Write one function to read in user commands (readCommand)
  3. Call both functions in a loop
  4. Set timeouts so, that key presses are read soon enough. (sleep .1 and read -t.1)
function readCommand(){
   lastCommand=$1
   read -t.1 -n1 c;
   if [ "$c" = "p" ]
   then
     printf "\n\r"; 
     return 0
   fi
   if [ "$c" = "g" ]
   then
     printf "\n\r"; 
     return 1
   fi
  return $lastCommand
}

function spin(){
    for i in / - \\ \| ;
        do 
            printf "\r$i"; 
            sleep .1;
        done
}

function countUp(){
    currentCount=$1
    return `expr $currentCount + 1`
}

function counter(){
    countUp $count
    count=$?
    printf "\r$count"
    sleep .1;
}

command=1
count=0
while : 
do 
   if [[ $command == 1 ]]
   then
      counter
   fi
   readCommand $command
   command=$?
done 

The counter will stop if user presses 'p' and go on if user presses 'g'

Upvotes: 1

aafirvida
aafirvida

Reputation: 541

I have made a change in the code you refer.

...
while [ true ]; do
    if [ -z $(cat /tmp/pause) ]; then
        STOPWATCH=$(TZ=UTC datef $DATE_INPUT $DATE_FORMAT | ( [[ "$NANOS_SUPPORTED" ]] && sed 's/.\{7\}$//' || cat ) )
        printf "\r\e%s" $STOPWATCH
        sleep 0.03
    fi
done

So what you need to do now is a shell script that waits the "p" char from stdin and writes 1 > /tmp/pause or clean /tmp/pause to get he stopwatch paused or working.

something like:

while read char;
do 
   if [ $char == "p" ]; then
       if [ -z $(cat /tmp/pause) ];then
           echo 1 > /tmp/pause
       else
           echo > /tmp/pause
       fi
       char=0 
   fi
done < /dev/stdin

Upvotes: 0

Related Questions