/mkpipe

create a pipe from a shell script

Primary LanguageShellGNU General Public License v3.0GPL-3.0

Utility for creating a pipe from a shell script.

Description
===========
mkpipe  make a pipe, print to stdout own pid and wait until killed by a
caller.  mkpipe is intended to be used from a bash script when there is
a  need  to  collect  an  output  from  more  than one descriptor sepa-
rately(i.e. stdout and stderr). For example, if we have some program(or
another  script)  that  output to stdout some needed data on success or
output to stderr an error message on failure and these messages are in-
termixed.  Or  we  need  to run many programs in a subshell and process
stdout and stderr separately.

After start mkpipe create a pipe with descriptor 3 connected to it read
end and descriptor 4 connected to it write end.  After that, it reports
it own pid to stdout and wait with help of pause() call.

There is mkpipe.sh which is usually located in /usr/local/lib/mkpipe/.
This utility  script  contains  3  useful  functions which simplify
using of mkpipe in your scripts. See MKPIPE.SH section below.


Install
=======

make install command will install mkpipe binary in /usr/local/bin/
and mkpipe.sh in /usr/local/lib/mkpipe/.

Options
=======

-B       create pipe with O_NONBLOCK
-i       show actual pipe size
-s BYTES set pipe size to BYTES
-v       show program version
-h       show this help

Using
=====

Include mkpipe.sh in your script:

. /usr/local/lib/mkpipe/mkpipe.sh

Then use it:

# data channel
mkpipe 5 6
# error channel
mkpipe 7 8
# debug channel
mkpipe 9 10
({
  SOME
  SCRIPT
  HERE
} 1>&6 2>&8 3>&10)
read_all data <&5
read_all emsg <&7
read_all dbgmsg <&9

echo $data
if [ "$emsg" ]; then
	echo "ERR: $emsg"
fi
echo "DBG: $dbgmsg"

rmpipe 5 6
rmpipe 7 8
rmpipe 9 10

NOTE: for this style of using we must sure that pipe size is enough
to hold all data for a channel! Because, a child run synchronously and
only after it is finished we start to read all outputed data.

See ex_logger.sh for example of creation a longlive children with pipe
connection between it and a script.

Examples
========

See example.sh, ex_logger.sh.

Rationale
=========

Sometimes there is need to execute some commands in a subshell and we need to
collect an output from stdout and stderr(and sometimes from even more file
descriptors; e.g. our script need to collect an output from 4 fd - 1, 2, 3
and 4 - where 1 is used for a data, 2 for errors, 3 is used for error which
should be sent to a client and 4 for debug messages).
Thus, simple:

data=$(commands here)

isn't enough for this case. We can use files as a temporary storage for an output:

({commands here} >data.out 2>err.out 3>err_client.out 4>debug.out)

But with read-only fs this doesn't work.

We can use the next shell function(thanks to htamas from stackexchange):

# Create a pipe
#  $1 - read fd
#  $2 - write fd
mkpipe()
{
	local PID1 PID2
	local FD_READ FD_WRITE

	FD_READ=$1
	FD_WRITE=$2
	tail -f /dev/null | tail -f /dev/null &
	PID2=$!
	PID1=`jobs -p %+`
	# Is this enough to finish with children?
	sleep 1s
	eval "exec $FD_READ</proc/$PID2/fd/0 $FD_WRITE>/proc/$PID1/fd/1"
	disown $PID1
	kill $PID1 $PID2
}

But look at sleep call. Here we have a race condition.
How big it should be in order to be guaranteed to work correctly?
The problem is we can't guarantee that 2 tail binaries are launched
before we reach eval, due to concurrency. Here "sleep 1s" just
a try to solve this problem and give a bash enough time to complete
with 2 tail binaries.

So, we need something that do not require a magic with timeout
tweaking.

How it should be done right
===========================

While mkpipe can be used for long time data channel between a script and a
child, for a simple output collecting it better to use some virtual FS. Like
/dev/tcp. May be /dev/env or /dev/vars. I.e. script may looks like:

({commands here} >/dev/vars/data 2>/dev/vars/emsg 3>emsg_client 4>dbgmsg)

after that we have variables data, emsg, emsg_client and dbgmsg.

Sponsors
========

Z-Wave.Me