LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 12-03-2013, 05:22 PM   #1
PTrenholme
Senior Member
 
Registered: Dec 2004
Location: Olympia, WA, USA
Distribution: Fedora, (K)Ubuntu
Posts: 4,187

Rep: Reputation: 354Reputation: 354Reputation: 354Reputation: 354
Use of coproc in bash - an example, not a problem.


I wanted to set up a general "spinner" system that I could use when I was running something that took a long while to complete, but which produced little output while running.

So, here's a code package I wrote that uses the new (in bash 4) coproc command to start a spinner (on stdout) running as a background process while the foreground process can run unchanged.

I post it here for comment and/or improvement, and because I could find few examples of coproc usage.

Code:
#!/bin/bash
##########################################################
#
# Display a "spinner"
#
# Argument: One of "start," "stop," or "toggle."
#           If no argument, "toggle" is assumed.
#
# Note: This function uses a globle variables, __Spinner...
#       to control its action.
#
##########################################################
#
# Function to get the state of the spinner.
#
# Arguments: none
#
# Return: 1 if the spinner is running, otherwise 0
#
#########################################################
function spinning()
{
  local running
  if [ -z "${__Spinner_State_PID}" ] || [ $((__Spinner_State_PID + 0)) -eq 0 ]
  then
    running=0
  else
    running=1
  fi
  return ${running}
}
##########################################################
#
# Create or kill a coprocess running a spinner on stdout
#
##########################################################
function toggle_spin()
{
  local running
  spinning
  running=$?
  # Is the spinner running?
  if [ ${running} -eq 0 ]
  then
    # No. Create it.
    exec 5<&0 # We want to use the parrent process' stdout
    # The coprocess is written as an inline function so it can be named
    coproc __Spinner_State \
    (sym="|/-\\-"
     i=-1
     while true
     do
       i=$(( (++i)%5 ))
       echo -n ${sym:$((i)):1} >&5
       sleep 0.1s
       echo -en $'\b' >&5
    done)
  else
    # Yes. Terminate it.
    eval "exec ${__Spinner_State[0]}>&-"
    eval "exec ${__Spinner_State[1]}>&-"
    eval 5>&-
    kill ${__Spinner_State_PID} &>/dev/null
    __Spinner_State_PID=0
  fi
}
#############################################################
#
# Master control function
#
#############################################################
function spinner()
{
  local running
  spinning
  running=$?
  if [ $# -gt 1 ] || [ "${1:0:1}" == "-" ]
  then
    cat <<EOF >&2
spinner: Start, stop, or toggle a "spinner" on /dev/stdout

Usage:   spinner {toggle | start | stop}
Default: toggle
Return:  0 if the spinner is running, otherwise 1.

Note: Generally one woud call spinner with no argument
      to toggle the spinner on or off. See the test function
      (commented out) at the end of this file.
EOF
  return ${running}
  fi
  case "${1:2:1}" in
    ("a"|"A") if [ ${running} == 1 ]
              then
                toggle_spin
              fi;;
    ("o"|"O") if [ ${running} == 0 ]
              then
                toggle_spin
              fi;;
    (*) toggle_spin;;
  esac
  spinning
  return $?
}
#
# Test program (Uncomment for testing)
#
#spinner
#if [ $? -ne 0 ]
#then
#  for ((i=1;i<6;++i))
#  do
#    sleep 5s
#    echo -n $'\b'". "
#  done
#  echo
#  spinner
#else
#  echo "The spinner failed to start."
#fi
 
Old 12-05-2013, 09:40 AM   #2
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Probably only niggly personal things, but just in case:

1. Why not keep to a theme when testing and using arithmetic? I find the jumping from [] or [[]] to (()) and back distracting and generally have to re-read to see what type of test is being done

2. Additional to above, the use of (()) inside [] or [[]] would seem to be a waste:
Code:
[ $((__Spinner_State_PID + 0)) -eq 0 ]
More simply this could just be:
Code:
(( 0 == __Spinner_State_PID + 0 ))
3. I also use the above format for assignment as well:
Code:
i=$(( (++i)%5 ))
Becomes:
Code:
(( i = (++i)%5 ))
4. Variables used in splices do not require additional expanding and can be used raw:
Code:
echo -n ${sym:$((i)):1} >&5
Becomes:
Code:
echo -n ${sym:i:1} >&5
5. I understand the need for eval when using a variable as the descriptor, but see no need for it in the below (unless I misunderstand the process here):
Code:
eval 5>&-
Shouldn't this be:
Code:
exec 5>&-
6. Instead of hoping the people have spelt 'start' and 'stop' correctly I prefer to use the whole string and to avoid user use of the shift or caps lock keys, force either upper or lower case:
Code:
case "${1,,}" in
    start) ...;;
    stop)  ...;;
       *)  ...;;
esac
happy if you take all with a pinch of salt
 
Old 12-05-2013, 11:37 PM   #3
PTrenholme
Senior Member
 
Registered: Dec 2004
Location: Olympia, WA, USA
Distribution: Fedora, (K)Ubuntu
Posts: 4,187

Original Poster
Rep: Reputation: 354Reputation: 354Reputation: 354Reputation: 354
Well, gail, if I didn't need a little salt, would I have posted here?

Rather than addressing all your points, let me just say that I learned my bash many years ago, and old habits hard can be hard to break. I did make most of the changes you suggested, and the test code worked as well after those changes.

You point (6) is well taken, but I thought that the most common usage would be the "toggle" default. I was working on an awk implementation of the Double Metaphone algorithm when I distracted myself with this exercise. Since I was looking at some C++ code that was jumping through hoops to find single characters, I thought an illustration of how to pull single characters from a string in bash might not be remiss.

As to your point (5), yes, the eval is unnecessary. In fact, since the coproc in killed, I'm not sure if closing the pipes and the stdout copy is even necessary. I typically close files after I finish with them since not doing so can sometimes cause strange things to happen, but killing a process at one end of the pipe usually closes the pipe (and any other resources used by the process).

Oh, the fifth "spin" element was an error (it made the wheel jerk), and sleeping for 0.2s made it look somewhat smoother.

All that being said, here, with appreciation, is a revised version:
Code:
#!/bin/bash
##########################################################
#
# Display a "spinner" until called a second time.
#
# Argument: none
#
# Note: This function uses global variables, __Spinner...
#       to control its action.
#
##########################################################
#
# Function to get the state of the spinner.
#
# Arguments: none
#
# Return: 1 if the spinner is running, otherwise 0
#
#########################################################
function spinning()
{
  local running
  if [ -z "${__Spinner_State_PID}" ] || ((0 == __Spinner_State_PID + 0))
  then
    running=0
  else
    running=1
  fi
  return ${running}
}
##########################################################
#
# Create or kill a coprocess running a spinner on stdout
#
##########################################################
function toggle_spin()
{
  local running
  spinning
  running=$?
  # Is the spinner running?
  if ((running==0))
  then
    # No. Create it.
    exec 5<&0 # We want to use the parent process' stdout
    # The coprocess is written as an inline function so it can be named
    coproc __Spinner_State \
    (sym="|/-\\"
     i=-1
     while true
     do
       (( i = (++i)%4 ))
       echo -n ${sym:i:1} >&5
       sleep 0.2s
       echo -en $'\b' >&5
    done)
  else
    # Yes. Terminate it.
   kill ${__Spinner_State_PID} &>/dev/null
    __Spinner_State_PID=0
  fi
}
#############################################################
#
# Master control function
#
#############################################################
function spinner()
{
  local running
  spinning
  running=$?
  if [ $# -gt 0 ] || [ "${1:0:1}" == "-" ]
  then
    cat <<EOF >&2

Usage:   spinner [--help]
Return:  0 if the spinner is running, otherwise 1.

EOF
  return ${running}
  fi
  toggle_spin
  spinning
  return $?
}
#
# Test program (Uncomment for testing)
#
#spinner
#if [ $? -ne 0 ]
#then
#  for ((i=1;i<6;++i))
#  do
#    sleep 5s
#    echo -n $'\b'". "
#  done
#  echo
#  spinner
#else
#  echo "The spinner failed to start."
#fi
If anyone's interested, here's the timing of the last run I did of the test program:
Code:
$ time bash Spinner 
..... 

real    0m25.077s
user    0m0.001s
sys     0m0.062s
 
Old 12-06-2013, 03:08 AM   #4
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Glad I could help

It is funny but you are quite right that people just don't like to sit and wait for something to finish and the act of a spinner or progress bar just seems to put everyone at ease (myself included).

I'll be interested to see if coproc can help in some of my work too.
 
Old 12-06-2013, 05:20 AM   #5
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
at about 20 years ago there was a bash function (named dot). It was something like that:
Code:
function dot { 
  while true; 
  do {
    echo -n .
    sleep 1;
 }
done
};

# and you only wrote:
dot&

# do something

kill %1
And it was good enough....
 
Old 12-06-2013, 02:12 PM   #6
PTrenholme
Senior Member
 
Registered: Dec 2004
Location: Olympia, WA, USA
Distribution: Fedora, (K)Ubuntu
Posts: 4,187

Original Poster
Rep: Reputation: 354Reputation: 354Reputation: 354Reputation: 354
Well, yes, dot was similar. I was looking for examples of coproc usage, and this one seemed easy.

A more practical program would use the input and output pipes to send messages to the coprocess and receive replies from it, much like a DBUS system in user space. In fact, since bash has a dbus interface, there may be no real need for coproc, but it might be useful in some cases. For example, if you wanted to simulate a windowing system on a terminal display, you might use coproc and curses to create different "windows" in a more modular way then existing systems (like, e.g., emacs, mc, etc.,) currently use to solve that problem.
 
Old 12-13-2013, 12:17 PM   #7
bigearsbilly
Senior Member
 
Registered: Mar 2004
Location: england
Distribution: Mint, Armbian, NetBSD, Puppy, Raspbian
Posts: 3,515

Rep: Reputation: 239Reputation: 239Reputation: 239
Unfortunately, it has a major bug!
if you terminate ctrl-C the spinner keeps going!


for ever!

dot is the same.

we've all tried it!

Last edited by bigearsbilly; 12-13-2013 at 12:22 PM.
 
Old 12-14-2013, 12:57 AM   #8
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Could you work around that with a trap?
 
Old 12-15-2013, 03:06 PM   #9
mina86
Member
 
Registered: Aug 2008
Distribution: Debian
Posts: 517

Rep: Reputation: 229Reputation: 229Reputation: 229
If we're nitpicking, the whole “spinning” function colud be rewritten as:
Code:
spinning() {
  [ -n "${__Spinner_State_PID}" ] && ((0 != __Spinner_State_PID + 0))
}
Similarly in “spinner” you do not have to “return $?” at the end.

On the topic of “spinner”, why is this function calling “spinning” twice? it could be easily reworked to use if-then-else-fi without the need to have two call places of “spinning”.

And to test if spinning, just do:
Code:
if spinning; then
  : …
else
  : …
fi
Code:
exec 5<&0 # We want to use the parent process' stdout
Except 0 is stdin. Besides you should be able to manage without exec by simply adding “5>&1” at the end of “coproc” invocation.

Code:
(( i = (++i)%4 ))
This is just confusing. Use addition like normal people:
Code:
(( i = (i + 1) % 4 ))
Lastly, drop the “function” keyword, it's cleaner.

grail, your third point is a matter of preference. I myself find “i=$(( (i + 1) % 4 ))” nicer. Plus it's POSIX, whereas “((…))” isn't.
 
Old 12-17-2013, 03:22 AM   #10
bigearsbilly
Senior Member
 
Registered: Mar 2004
Location: england
Distribution: Mint, Armbian, NetBSD, Puppy, Raspbian
Posts: 3,515

Rep: Reputation: 239Reputation: 239Reputation: 239
Here's what I use, just one way of doing it

Code:
/*
 * (c) bigearsbilly
 *
 *    need LDLIBS =  -lrt -lpthread
 */
#define _POSIX_C_SOURCE 199309L
#include <time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


typedef void (*timer_callback) (union sigval);
static int status;

int set_timer(timer_t * timer_id, float delay, float interval, timer_callback func, void * data) 
{
    struct itimerspec ts;
    struct sigevent se;

    se.sigev_notify = SIGEV_THREAD;
    se.sigev_value.sival_ptr = data;
    se.sigev_notify_function = func;
    se.sigev_notify_attributes = NULL;

    if (timer_create(CLOCK_REALTIME, &se, timer_id) != 0) {
        perror(__FILE__ " timer_create");
        return 0;
    }

    ts.it_value.tv_sec = abs(delay);
    ts.it_value.tv_nsec = (delay-abs(delay)) * 1e09;
    ts.it_interval.tv_sec = abs(interval);
    ts.it_interval.tv_nsec = (interval-abs(interval)) * 1e09;

    if (timer_settime(*timer_id, 1, &ts, 0) != 0) {
        perror(__FILE__ " timer_settitme");
        return 0;
    }

    return 1;
}

void callback(union sigval si)
{
    char * msg = (char *) si.sival_ptr;
    printf("\r%s", msg);
    fflush(stdout);
}

int main(int argc, char ** argv)
{
    timer_t tick;
    timer_t tock;

    if (argv[1] == NULL){
        fprintf(stderr, "Wtf?\n");
        return 1;
    }

    if (fork() > 0) {
        set_timer(&tick, 1, 1, callback, (void *) "+ tick -" );
        set_timer(&tock, 1.5, 1, callback, (void *) "- tock +" );
        wait(&status);
    } else {
        execvp(argv[1], argv+1);
    }
    printf("\n");

    return 0;
}
 
Old 12-17-2013, 03:42 AM   #11
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
a bit offtopic, but:
Quote:
Originally Posted by bigearsbilly View Post
Code:
/*
 * (c) bigearsbilly
 *
 *    need LDLIBS =  -lrt -lpthread
 */
The correct solution would be to use -pthread both for compiling and linking and do not use -lpthread at all
 
Old 12-17-2013, 03:48 AM   #12
bigearsbilly
Senior Member
 
Registered: Mar 2004
Location: england
Distribution: Mint, Armbian, NetBSD, Puppy, Raspbian
Posts: 3,515

Rep: Reputation: 239Reputation: 239Reputation: 239
correct?

if you say so.
 
Old 12-17-2013, 03:54 AM   #13
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
No, it is not (only) me. If you want to discuss it just open a new thread about it.
http://unix.stackexchange.com/questi...-pthread#33398
 
  


Reply

Tags
bash coproc example



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Bash problem : -bash: [: /bin/bash: unary operator expected J.A.X Linux - Software 1 09-22-2011 05:52 AM
[SOLVED] bash : getopts problem in bash script. angel115 Programming 2 03-02-2009 10:53 AM
Reading a bash variable in bash scripting problem freeindy Programming 3 11-27-2008 02:29 AM
a problem about bash naihe2010 Linux - Enterprise 1 10-15-2005 11:06 AM
bash problem cL4YmAN Linux - Newbie 1 06-07-2004 01:32 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 09:42 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration