Grab exactly one byte from a FIFO, at random intervals

I want to develop a script of the following form:

#!/bin/bash

# Function 'listen' opens a data stream
# which stores all incoming bytes in
# a buffer, preparing them to be
# grabbed by a following function
# which appears at random
# intervals during the execution of
# the script

listen &
INPID=${!}

# To test if the script is really capable
# of grabbing bytes randomly, the
# following code is looped over:

PROMPT=n
while test ${PROMPT} != 'q' ; do
        printf 'y: print next byte\nn: don'"'"'t print next byte\nq: quit\n'
        read -n 1 PROMPT
        printf '\n'
        if test ${PROMPT} = 'y' ; then

# The function 'grab1byte' extracts
# ONE byte from the buffer being fed
# bytes by 'listen', and outputs it
# to stdout

                INBYTE=`grab1byte`
                echo The input byte is:
                printf "${INBYTE}" | xxd -cols 1 | sed 's/^.*: //'
        fi
done

kill ${INPID}

The way I've tried to implement this, is by using FIFOs. In one directory, I have 2 FIFOs, which I created with mkfifo; these are named 'INPUT' and 'FIFO'. In fluxbox, I open 2 instances of xterm; in one I run the following script:

#!/bin/bash

MY_INPUT=INPUT
MY_FIFO=FIFO

# This is 'listen':
(
        IFS=
        tail -f ${MY_INPUT} | while read -N 1 CHAR ; do
                printf "${CHAR}" > ${MY_FIFO}
        done
) &
READPID=${!}

PROMPT=n
while test ${PROMPT} != 'q' ; do
        printf 'y: print next byte\nn: don'"'"'t print next byte\nq: quit\n'
        read -n 1 PROMPT
        printf '\n'
        if test ${PROMPT} = 'y' ; then
# This is 'grab1byte':
                INBYTE=`cat ${MY_FIFO}`
                echo The input byte is:
                printf "${INBYTE}" | xxd -cols 1 | sed 's/^.*: //'
        fi
done

kill ${READPID}

On the other one, I run:

printf 'Hello, world!' > INPUT

Then, back on the first terminal, I type 'y' to the prompt, to test the bytegrabbing. The problem is: instead of getting only 1 byte, I sometimes get 1, 2, 3, 4, 5 bytes. A typical session looks something like:

y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
48  H
65  e
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
6c  l
6c  l
6f  o
2c  ,
20   
77  w
y: print next byte
n: don't print next byte
q: quit

But, what I want is something like:

y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
48  H
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
 65  e
y: print next byte
n: don't print next byte
q: quit

The mystery is: Why does FIFO spit out 2 or 4 bytes at a time, if I am only writing ONE byte at each iteration of the loop??:

        IFS=
        tail -n 1 -f ${MY_INPUT} | while read -N 1 CHAR ; do
                printf "${CHAR}" > ${MY_FIFO}
        done

The code seems to work with a 'sleep' delay of 0.2 right after 'printf "${CHAR}" > ${MY_FIFO}'. But... why?

In order for this script to be perfect, I would require it to ONLY use FIFOs: No ugly and slow hard-drive file buffers, please. And also, NO ugly time delays.

Another funny thing is how, when I run in one terminal:

( IFS= ; tail -f FIFO | while read -N 1 CHAR ; do printf "${CHAR}" | xxd -cols 1 ; printf '..\n' ; done )

And, from another, I do

printf 'Hello, world!' > FIFO

I get:

0000000: 48  H
..
0000000: 65  e
..
0000000: 6c  l
..
0000000: 6c  l
..
0000000: 6f  o
..
0000000: 2c  ,
..
0000000: 20   
..
0000000: 77  w
..
0000000: 6f  o
..
0000000: 72  r
..
0000000: 6c  l
..
0000000: 64  d
..
0000000: 21  !
..

Which goes to show that ${CHAR} never stores more than 1 byte at any given time. If it did, the output would look more like:

0000000: 48  H
0000001: 65  e
0000002: 6c  l
0000003: 6c  l
..
0000000: 6f  o
0000001: 2c  ,
0000002: 20   
..
0000000: 77  w
0000001: 6f  o
0000002: 72  r
0000003: 6c  l
0000004: 64  d
0000005: 21  !
..

So... my question is... basically: What is the deal with this FIFO glitch? If the problem is not in the loop, then: Where is it?

This might sound stupid, but can you replace this section:

# This is 'grab1byte':
                INBYTE=`cat ${MY_FIFO}`
                echo The input byte is:
                printf "${INBYTE}" | xxd -cols 1 | sed 's/^.*: //'

with

# This is 'grab1byte':
                echo The input byte is:
                xxd -cols 1 ${MY_FIFO} | sed 's/^.*: //'

and give it another try? Let us know the result please.

The output is the same with that change, but thanks for trying anyway. After removing the sed filter from the original script I posted, the output looks something like:

y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
0000000: 48  H
0000001: 65  e
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
0000000: 6c  l
0000001: 6c  l
0000002: 6f  o
0000003: 2c  ,
0000004: 20   
0000005: 77  w
y: print next byte
n: don't print next byte
q: quit

So this means that the FIFO buffer contains several bytes instead of just one... Which is weird, because, I thought 'printf "${CHAR}" > ${MY_FIFO}"' was supposed to pause the loop execution, UNTIL FIFO was emptied by something like 'cat ${MY_FIFO}'. After cat-ing FIFO, I thought the loop would freeze again at 'printf "${CHAR}" > ${MY_FIFO}"', until another instance of 'cat ${MY_FIFO}', but, apparently it doesn't. Apparently, it just feeds FIFO a random amount of bytes... WHY???

---------- Post updated at 04:52 PM ---------- Previous update was at 04:44 PM ----------

I'm thinking, maybe, cat opens up the FIFO for a longer time than it takes the loop to iterate, so the loop iterates several times, spitting several bytes into FIFO, until cat stops reading from FIFO (that is, until FIFO's output is closed)... Does this make any sense to you? And, if that is the case: How would I prevent that from happening?

I don't know what magic it is but I'm getting it right!!

y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
48  H
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
65  e
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
6c  l
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
6c  l
y: print next byte
n: don't print next byte
q: quit
y
The input byte is:
6f  o
y: print next byte
n: don't print next byte
q: quit
q

I tested it on GNU/Linux and shell /bin/bash

I recommend you to run ps and verify if you have any previous instance of this script & tail command running because I see the listening section is not getting gracefully terminated after selecting option q and I see this is causing the behavior that you mentioned.

# ps
  PID TTY          TIME CMD
 4787 pts/0    00:00:00 tail
 4788 pts/0    00:00:00 tfifo.sh
26804 pts/0    00:00:00 ps
29197 pts/0    00:00:00 bash
1 Like

I'm currently using bash in a mac, but I was testing the code from the original post also in GNU/Linux, in bash too.

I see how several unterminated instances of the 'listen' section would cause the behavior. I used ps to check, and killed all undesired instances of the listen section, but that didn't seem to solve it. So I split the script into two scripts:

listen.sh

#!/bin/bash

# Listen

	IFS=
	tail -f INPUT | while read -n 1 CHAR ; do
		printf "${CHAR}" > FIFO
#		sleep 0.2
	done

EDIT: The behavior only happens without the sleep delay. But I would like to have a working script that does not require the sleep delay.

prompt.sh

#!/bin/bash

PROMPT=n
while test ${PROMPT} != 'q' ; do
	printf 'y: print next byte\nn: don'"'"'t print next byte\nq: quit\n'
	read -n 1 PROMPT
	printf '\n'
	if test "${PROMPT}" = 'y' ; then

# Grab 1 byte

		BYTE=`cat FIFO`
		printf 'The input byte is:\n'
		printf "${BYTE}" | xxd -cols 1
	fi
done

So, on one terminal, I run ./listen.sh, and I leave it there, on a second one, I run ./prompt.sh, and leave it there also, and finally, on a third terminal, I run "printf 'Hello, world!' > INPUT".

I go back to the second terminal, and type 'y', and get the same undesired behavior. This time, I made sure there were no undesired unterminated instances of the listen section. bipinajith: Are you using the exact same code from my original post, or did you add any changes to yours?

Here is the code:

#!/bin/bash

MY_INPUT=INPUT
MY_FIFO=FIFO

(
tail -f ${MY_INPUT} | while IFS= read -n 1 CHAR ; do
        printf "${CHAR}" > ${MY_FIFO}
done
) &
READPID=$!

PROMPT=n
while [ "${PROMPT}" != "q" ]
do
        printf 'y: print next byte\nn: don'"'"'t print next byte\nq: quit\n'
        read -n 1 PROMPT
        printf '\n'
        if [ "${PROMPT}" = "y" ]
        then
# This is 'grab1byte':
                echo The input byte is:
                xxd -cols 1 ${MY_FIFO} | sed 's/^.*: //'
        fi
done

kill $READPID

I tried this very code and I am still getting the undesired behavior from my original post. It's funny how the amount of bytes I get from FIFO seems to be system-dependent; more specifically, it seems hardware-dependent. When I tried the code in a very fast mac, with bash, cat-ing FIFO dumped the whole string 'Hello, world!' to stdout. On my (slower) yeeloong notebook, cat-ing FIFO only dumps the first few bytes of the string, and then a few more bytes, etc. Maybe the software in a macbook is designed differently, although the shell is the same (bash), I don't know. I have no clue as to why this happens. This is just so strange.

What about FIFOs being buffered?

Ok, I rewrote the whole script, and came up with this:

#!/bin/bash

#    Functions:

function makedir() {
    DIR_NAME=${1}
    if test -e "${DIR_NAME}" -a ! -d "${DIR_NAME}" ; then
        printf 'File "'"${DIR_NAME}"'" exists, but is not a directory.\n'
        while test -e "${DIR_NAME}" -a ! -d "${DIR_NAME}" ; do
            printf '\nChoose a different name: '
            read DIR_NAME
        done
        mkdir "${DIR_NAME}"
        printf 'New directory "'"${DIR_NAME}"'" created.\n'
    elif test -d "${DIR_NAME}" ; then
        printf 'Directory "'"${DIR_NAME}"'" already exists.\n Use it\n[c] Create a new one'
        CHOICE='x'
        while test "${CHOICE}" != 'c' -a "${CHOICE}" != 'u' ; do
            printf '\n\tType "c" or "u": '
            read -n 1 CHOICE
        done
        if test "${CHOICE}" = 'c' ; then
            while test -d "${DIR_NAME}" -o -z "${DIR_NAME}" ; do
                printf '\n\tChoose a different name: '
                read DIR_NAME
            done
            mkdir "${DIR_NAME}"
            printf 'New directory "'"${DIR_NAME}"'" created.\n'
        else
            printf '\nUsing directory "'"${DIR_NAME}"'".\n'
        fi
    else
        mkdir "${DIR_NAME}"
        printf 'New directory "'"${DIR_NAME}"'" created.\n'
    fi
}

function makefifo() {
        FIFO_DIR=${1}
        FIFO_NAME='./'"${FIFO_DIR}"'/'"${2}"
        if test ! -p "${FIFO_NAME}" -a -e "${FIFO_NAME}" ; then
                printf 'File "'"${FIFO_NAME}"'" exists, but is not a named pipe.\n'
                while test \( ! -p "${FIFO_NAME}" -a -e "${FIFO_NAME}" \) -o -z "${FIFO_NAME}" ; do
                        printf 'Choose a different name: '
                        read FIFO_NAME
                        FIFO_NAME='./'"${FIFO_DIR}"'/'"${FIFO_NAME}"
                done
                if test -p "${FIFO_NAME}" ; then
                        printf 'Using named pipe "'"${FIFO_NAME}"'".\n'
                else
                        mkfifo "${FIFO_NAME}"
                        printf 'New named pipe "'"${FIFO_NAME}"'" created.\n'
                fi
        elif test -p "${FIFO_NAME}" ; then
                printf 'Using named pipe "'"${FIFO_NAME}"'".\n'
        else
                mkfifo "${FIFO_NAME}"
                printf 'New named pipe "'"${FIFO_NAME}"'" created.\n'
        fi
}

function keep_fifo_open() {
    INPUT=${1}
    KEEP_PAUSE=${2}
    while : ; do
        cat "${KEEP_PAUSE}"
        break
    done > "${INPUT}"
}

function listen() {
    INPUT=${1}
    FIFO=${2}
    LISTEN_PAUSE=${3}
    cat "${INPUT}" | while IFS= read -n 1 BYTE ; do
        printf "${BYTE}" > "${FIFO}"
        EXIT=`cat "${LISTEN_PAUSE}"`
        if test "${EXIT}" = '1' ; then
            break
        fi
    done
}

function yap_randomly() {
    INPUT=${1}
    SLEEP_RANGE=${2}
    while : ; do
        BYTE=`echo "ibase=10;obase=16;(${RANDOM}%256)" | bc`
        printf "\x${BYTE}"
        sleep `echo "(${RANDOM}%${SLEEP_RANGE})" | bc`
    done > "${INPUT}"
}

function grab_bytes() {
    FIFO=${1}
    LISTEN_PAUSE=${2}
    KEEP_PAUSE=${3}
    printf '[p] Print next byte\n[q] Quit\n'
    PROMPT='n'
    while test "${PROMPT}" != 'q' ; do
        printf 'Type "p" or "q": '
        read -n 1 PROMPT
        printf '\n'
        if test "${PROMPT}" = 'p' ; then
            printf 'Next byte is:\n'
            cat "${FIFO}" | xxd -cols 1
            printf '0' > "${LISTEN_PAUSE}"
        fi
    done
    cat "${FIFO}" > /dev/null
    printf '1' > "${LISTEN_PAUSE}"
    printf '1' > "${KEEP_PAUSE}"
}

#    Directories:

PIPES_DIR='pipes'

makedir "${PIPES_DIR}"

PIPES_DIR="${DIR_NAME}"

#    Named pipes:

INPUT='INPUT'
FIFO='FIFO'
KEEP_PAUSE='KEEP_PAUSE'
LISTEN_PAUSE='LISTEN_PAUSE'

makefifo "${PIPES_DIR}" "${INPUT}"
INPUT="${FIFO_NAME}"
makefifo "${PIPES_DIR}" "${FIFO}"
FIFO="${FIFO_NAME}"
makefifo "${PIPES_DIR}" "${KEEP_PAUSE}"
KEEP_PAUSE="${FIFO_NAME}"
makefifo "${PIPES_DIR}" "${LISTEN_PAUSE}"
LISTEN_PAUSE="${FIFO_NAME}"

#    Variables:

MAXDELAY=3

#    Main script:

keep_fifo_open "${INPUT}" "${KEEP_PAUSE}" &
KEEPPID="${!}"
printf '\nBackgrounded keep_fifo_open process...\n'
printf '\tkeep_fifo_open '"${INPUT}"' '"${KEEP_PAUSE}"' &\n'
printf 'PID is: '"${KEEPPID}"'\n'

listen "${INPUT}" "${FIFO}" "${LISTEN_PAUSE}" &
LISTENPID="${!}"
printf '\nBackgrounded listen process...\n'
printf '\tlisten '"${INPUT}"' '"${FIFO}"' '"${LISTEN_PAUSE}"' &\n'
printf 'PID is: '"${LISTENPID}"'\n'

yap_randomly "${INPUT}" "${MAXDELAY}" &
YAPPID="${!}"
printf '\nBackgrounded yap_randomly process...\n'
printf '\tyap_randomly '"${INPUT}"' '"${MAXDELAY}"' &\n'
printf 'PID is: '"${YAPPID}"'\n'

printf '\nBeginning byte-grabbing loop!\n'
grab_bytes "${FIFO}" "${LISTEN_PAUSE}" "${KEEP_PAUSE}"

printf '\nKilling all backgrounded processes...\n'
printf '\tkill -9 '"${KEEPPID}"' '"${LISTENPID}"' '"${YAPPID}"'\n'
kill -9 "${KEEPPID}" "${LISTENPID}" "${YAPPID}"

sleep 1

printf '\nCheck for any unterminated processes...\n'
printf '\tps -e | grep -e '"${KEEPPID}"' -e '"${LISTENPID}"' -e '"${YAPPID}"'\n'
ps -e | grep -e "${KEEPPID}" -e "${LISTENPID}" -e "${YAPPID}"

This exits gracefully, but the problem is, I still get more than 1 byte when requesting 1 byte:

Beginning byte-grabbing loop!
[p] Print next byte
[q] Quit
Type "p" or "q": p
Next byte is:
0000000: 97  .
Type "p" or "q": p
Next byte is:
0000000: df  .
0000001: 58  X
Type "p" or "q":

I just can't figure out why that happens... it makes no sense to me. Isn't LISTEN_PAUSE supposed to be pausing the listen loop? I don't know...

You seemed to have a misunderstanding about how pipe works. A pipe has a typical 4k buffer so you can write more than one byte to it. You tried hard to write one byte a time to the pipe (which is not necessary), but in your grab_a_byte function, you used "cat $INPUT" to drain everything which should be just a byte at a time. Either use "read -n 1" as you did in other places or use "dd bs=1 count=1" to grab one byte from the pipe.

No, you see: I haven't tried to "write one byte at a time", what I'm trying to do is READ one byte at a time. cat ${INPUT} is not "in the grab_a_byte" function, it's in the listen function. cat ${INPUT} does dump the contents of INPUT, but does not close INPUT afterwards (because of keep_fifo_open), and I did pipe it into while IFS= read -n 1 in the listen function.

---------- Post updated at 05:12 PM ---------- Previous update was at 05:07 PM ----------

In case you didn't notice: the listen function is supposed to dump one byte from ${INPUT} at a time into ${FIFO}; therefor, in grab_bytes, cat ${FIFO} SHOULD output only ONE byte.

---------- Post updated at 05:16 PM ---------- Previous update was at 05:12 PM ----------

The idea is basically to have a data stream, a named pipe in this case, constantly open for input. This data stream has to be available at all times, to be able to receive input. And the script is supposed to be able to grab one byte at a time from this stream, without ever closing it. Those are the conditions I'm trying to satisfy with this script: all of them.

---------- Post updated at 07:02 PM ---------- Previous update was at 05:16 PM ----------

Hmm... interestingly, when I limit yap_randomly to [a-z] bytes, thus:

function yap_randomly() {
        INPUT=${1}
        SLEEP_RANGE=${2}
        while : ; do
#               BYTE=`echo "ibase=10;obase=16;(${RANDOM}%256)" | bc`
                BYTE=`echo "ibase=10;obase=16;(${RANDOM}%25)+97" | bc`
                printf "\x${BYTE}"
                sleep `echo "(${RANDOM}%${SLEEP_RANGE})" | bc`
        done > "${INPUT}"
}

...the script works as it should. Maybe there is a set of specific bytes (from 0x00 to 0xFF) which cause the script to malfunction.

---------- Post updated 12-31-12 at 03:10 AM ---------- Previous update was 12-30-12 at 07:02 PM ----------

Ok. This pretty much solves the "forbidden bytes" problem:

function listen() {
        INPUT=${1}
        FIFO=${2}
        LISTEN_PAUSE=${3}
        cat "${INPUT}" | while IFS= read -d '' -n 1 BYTE ; do
                printf "%c" "${BYTE}" > "${FIFO}"
                EXIT=`cat "${LISTEN_PAUSE}"`
                if test "${EXIT}" = '1' ; then
                        break
                fi
        done
}

The script is now perfect. Thanks everyone for your support and constructive criticism.

---------- Post updated at 03:24 AM ---------- Previous update was at 03:10 AM ----------

Here's a final, fully functional, version of the script, in case anybody wants to put it to some good use, or to modify it:

EDIT: The script isn't perfect... the character '\' (0x5C) doesn't get printed for some reason... it's probably getting parsed. Well, I'll look into that some other time. I guess this is enough for now.

EDIT2: read just needs the -r option. NOW it's ready.

EDIT3: nah, not perfect. byte 0xC5 also fails...

#!/bin/bash

#    Functions:

function makedir() {
    DIR_NAME=${1}
    if test -e "${DIR_NAME}" -a ! -d "${DIR_NAME}" ; then
        printf 'File "'"${DIR_NAME}"'" exists, but is not a directory.\n'
        while test -e "${DIR_NAME}" -a ! -d "${DIR_NAME}" ; do
            printf '\nChoose a different name: '
            read DIR_NAME
        done
        mkdir "${DIR_NAME}"
        printf 'New directory "'"${DIR_NAME}"'" created.\n'
    elif test -d "${DIR_NAME}" ; then
        printf 'Directory "'"${DIR_NAME}"'" already exists.\n Use it\n[c] Create a new one'
        CHOICE='x'
        while test "${CHOICE}" != 'c' -a "${CHOICE}" != 'u' ; do
            printf '\n\tType "c" or "u": '
            read -n 1 CHOICE
        done
        if test "${CHOICE}" = 'c' ; then
            while test -d "${DIR_NAME}" -o -z "${DIR_NAME}" ; do
                printf '\n\tChoose a different name: '
                read DIR_NAME
            done
            mkdir "${DIR_NAME}"
            printf 'New directory "'"${DIR_NAME}"'" created.\n'
        else
            printf '\nUsing directory "'"${DIR_NAME}"'".\n'
        fi
    else
        mkdir "${DIR_NAME}"
        printf 'New directory "'"${DIR_NAME}"'" created.\n'
    fi
}

function makefifo() {
    FIFO_DIR=${1}
    FIFO_NAME='./'"${FIFO_DIR}"'/'"${2}"
    if test ! -p "${FIFO_NAME}" -a -e "${FIFO_NAME}" ; then
        printf 'File "'"${FIFO_NAME}"'" exists, but is not a named pipe.\n'
        while test \( ! -p "${FIFO_NAME}" -a -e "${FIFO_NAME}" \) -o -z "${FIFO_NAME}" ; do
            printf 'Choose a different name: '
            read FIFO_NAME
            FIFO_NAME='./'"${FIFO_DIR}"'/'"${FIFO_NAME}"
        done
        if test -p "${FIFO_NAME}" ; then
            printf 'Using named pipe "'"${FIFO_NAME}"'".\n'
        else
            mkfifo "${FIFO_NAME}"
            printf 'New named pipe "'"${FIFO_NAME}"'" created.\n'
        fi
    elif test -p "${FIFO_NAME}" ; then
        printf 'Using named pipe "'"${FIFO_NAME}"'".\n'
    else
        mkfifo "${FIFO_NAME}"
        printf 'New named pipe "'"${FIFO_NAME}"'" created.\n'
    fi
}

function keep_fifo_open() {
    INPUT=${1}
    KEEP_PAUSE=${2}
    while : ; do
        cat "${KEEP_PAUSE}"
        break
    done > "${INPUT}"
}

function listen() {
# Some bytes, such as 0xC5 don't get stored in BYTE with this
# Use od instead (scroll further down in this post to see how)
    INPUT=${1}
    FIFO=${2}
    LISTEN_PAUSE=${3}
    cat "${INPUT}" | while IFS= read -r -d '' -n 1 BYTE ; do
        printf '%c' "${BYTE}" > "${FIFO}"
        EXIT=`cat "${LISTEN_PAUSE}"`
        if test "${EXIT}" = '1' ; then
            break
        fi
    done
}

function yap_randomly() {
    INPUT=${1}
    SLEEP_RANGE=${2}
    while : ; do
        BYTE=`echo "ibase=10;obase=16;(${RANDOM}%256)" | bc`
        printf "\x${BYTE}" > "${INPUT}"
        sleep `echo "(${RANDOM}%${SLEEP_RANGE})" | bc`
    done
}

function yap_in_sequence() {
    INPUT=${1}
    COUNT=0
    while test "${COUNT}" -lt 256 ; do
        BYTE=`echo "obase=16;ibase=10;${COUNT}" | bc`
        printf "\x${BYTE}" > "${INPUT}"
        COUNT=`expr "${COUNT}" + 1`
    done
}

function grab_bytes() {
    FIFO=${1}
    LISTEN_PAUSE=${2}
    KEEP_PAUSE=${3}
    printf '[p] Print next byte\n[q] Quit\n'
    PROMPT='n'
    while test "${PROMPT}" != 'q' ; do
        printf 'Type "p" or "q": '
        read -n 1 PROMPT
        printf '\n'
        if test "${PROMPT}" = 'p' ; then
            printf 'Next byte is:\n'
            cat "${FIFO}" | xxd -cols 1
            printf '0' > "${LISTEN_PAUSE}"
        fi
    done
    cat "${FIFO}" > /dev/null
    printf '1' > "${LISTEN_PAUSE}"
    printf '1' > "${KEEP_PAUSE}"
}

#    Directories:

PIPES_DIR='pipes'

makedir "${PIPES_DIR}"
PIPES_DIR="${DIR_NAME}"

#    Named pipes:

INPUT='INPUT'
FIFO='FIFO'
KEEP_PAUSE='KEEP_PAUSE'
LISTEN_PAUSE='LISTEN_PAUSE'

makefifo "${PIPES_DIR}" "${INPUT}"
INPUT="${FIFO_NAME}"
makefifo "${PIPES_DIR}" "${FIFO}"
FIFO="${FIFO_NAME}"
makefifo "${PIPES_DIR}" "${KEEP_PAUSE}"
KEEP_PAUSE="${FIFO_NAME}"
makefifo "${PIPES_DIR}" "${LISTEN_PAUSE}"
LISTEN_PAUSE="${FIFO_NAME}"

#    Variables:

MAXDELAY=3

#    Main script:

keep_fifo_open "${INPUT}" "${KEEP_PAUSE}" &
KEEPPID="${!}"
printf '\nBackgrounded keep_fifo_open process...\n'
printf '\tkeep_fifo_open '"${INPUT}"' '"${KEEP_PAUSE}"' &\n'
printf 'PID is: '"${KEEPPID}"'\n'

listen "${INPUT}" "${FIFO}" "${LISTEN_PAUSE}" &
LISTENPID="${!}"
printf '\nBackgrounded listen process...\n'
printf '\tlisten '"${INPUT}"' '"${FIFO}"' '"${LISTEN_PAUSE}"' &\n'
printf 'PID is: '"${LISTENPID}"'\n'

yap_randomly "${INPUT}" "${MAXDELAY}" &
YAPPID="${!}"
printf '\nBackgrounded yap_randomly process...\n'
printf '\tyap_randomly '"${INPUT}"' '"${MAXDELAY}"' &\n'
printf 'PID is: '"${YAPPID}"'\n'

printf '\nBeginning byte-grabbing loop!\n'
grab_bytes "${FIFO}" "${LISTEN_PAUSE}" "${KEEP_PAUSE}"

printf '\nKilling all backgrounded processes...\n'
printf '\tkill -9 '"${KEEPPID}"' '"${LISTENPID}"' '"${YAPPID}"'\n'
kill -9 "${KEEPPID}" "${LISTENPID}" "${YAPPID}"

sleep 1

printf '\nCheck for any unterminated processes...\n'
printf '\tps -e | grep -e '"${KEEPPID}"' -e '"${LISTENPID}"' -e '"${YAPPID}"'\n'
ps -e | grep -e "${KEEPPID}" -e "${LISTENPID}" -e "${YAPPID}"

---------- Post updated at 03:55 PM ---------- Previous update was at 03:24 AM ----------

Well, I wasn't aware of this, but apparenty read forbids a certain set of bytes from being stored in a variable, and dd has trouble receiving newline characters when used thus:

cat FIFO | while IFS= BYTE="`dd bs=1 count=1`" ; do
       printf "${BYTE}" | xxd -cols 1
done

So, I opted for od , and now the script takes in ANY byte (from 0x00 to 0xFF):

function listen() {
        INPUT=${1}
        FIFO=${2}
        LISTEN_PAUSE=${3}
        cat "${INPUT}" | while BYTE="`od -An -tx1 -N1 | sed 's/ //g'`" ; do
                printf "\x${BYTE}" > "${FIFO}"
                if test "`cat \"${LISTEN_PAUSE}\"`" = '1' ; then
                        break
                fi
        done
}

Yup... it's pretty much perfect now. Perhaps with a few boundary issues, such as: what happens if someone requesets more bytes than have been inputted into the INPUT named pipe (i.e., pressing "p" one too many times)? Currently, the script just gets stuck indefinitely waiting for more input to be dumped into INPUT; you can "un-stuck" it by shooting more bytes into INPUT from another process, but, well, maybe I should add a timeout or something like that. Anyway: it's finished. You can try it if you want, just remember to substitute the listen function with the one that uses od .

here's what I meant
https://github.com/vomv1988/bash_IPC