Search code examples
clinuxshelldeadlocknamed-pipes

read then write to c programme from shell script


I have a short c programme that outputs 3 lines then reads from stdin, and I would like to use a shell script to interact with it as follows:

  1. read c output, line by line while read line; do ...; done <$out_
  2. save 2nd line if [ "$count" == 2 ]; then input=$line; fi
  3. send saved line back to programme echo $input >$in_

I'm using named pipes in the shell script to redirect the output and input of the c programme:

./iotest >$out_ <$in_ &

but I'm not getting the results I'd expect.

Problem

After creating the pipes out_ and in_ with mkfifo, I have a while read loop but it seems to lead to a deadlock. Nothing is read, and the shell script gets stuck on the read command.

I tried adding cat >$in_ & before launching iotest, as proposed here, to open in_for writing without actually writing anything. This unblocks the read but doesn't let me input to in_ (my c programme acts as if I'd written \x00 to stdin, and the echo $input >$in_ command hangs).

What I've tried

I've read here that read blocks if there is no writer, and wihtout cat >$in_ & I have iotest writing to out_, and read reading from it, I'm trying to redirect in_ to iotestwithout writing anything to it from the start.

But why adding the cat command makes the c programme fail at reading, I don't know. The same happens if I exchange cat >$in_ & with while true; do :; done >$in_& or exec 3>$in_ & (exec 3>$in_, as proposed here, also hangs).

Code

This is my c code:

$ cat iotest.c 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main (int argc, char* argv[])
{
    char A[9] = "";
    printf("hello. please send me the following line:\n");
    printf("input me\n");
    printf("now send me the line:\n");
    int ret = read(0,A,8);
    fprintf(stderr,"read returned %i\n",ret);
    if(ret != -1) {
        printf("You sent: %s\n", A);
        if (strcmp(A,"input me") == 0) {
            printf("correct!\n");
        } else {
            printf("wrong line.\n");
        }
    } else {
        printf("read failed.\n");
    }
    return 0;
}

and this is the shell script I've tested:

$ cat runiotest.sh 
#!/bin/bash

in_=/tmp/testpipein
out_=/tmp/testpipeout

mkfifo $in_
mkfifo $out_
count=0
input=""

./iotest >$out_ <$in_ &
while read line
do
    count=$((count+1))
    echo "line number $count:"
    echo $line
    if [ "$count" == 2 ]
    then
        input=$line
    fi
    if [ "$count" == 3 ]
    then
        echo "$input" >$in_
    fi
done < $out_

rm -f $in_
rm -f $out_

I've tested this on 32-bit debian and 64-bit ubuntu, compiling with gcc, and executing the shell script with bash.

Results

without the added cat command:

$ /bin/bash/ ./runiotest.sh
(hangs)

with added cat >$in_ &:

$ /bin/bash ./runiotest.sh
read returned 0
line number 1:
hello. please send me the following line:
line number 2:
input me
line number 3:
now send me the line:
line number 4:
You sent:
line number 5:
wrong line.

Summary

So my question is: What is wrong with my shell script, and how can I modify it to read then write to my c programme? I can't change the c code itself but can fiddle with the shell script.

Thank you for any help!


Solution

  • The problem was the buffering of the c programme output. Removing the buffering with stdbuf solved it:

    cat >$in_ &
    stdbuf -o0 ./iotest <$out_ >$in_ &
    

    With this change, the output is as it should:

    $ /bin/bash ./runiotest.sh 
    line number 1
    hello. please send me the following line:
    line number 2
    input me
    line number 3
    now send me the line:
    read returned 8
    line number 4
    You sent: input me
    line number 5
    correct!
    

    Just to add, using stdbuf doesn't work if the executable has the setuid bit set. In this case, you can use unbuffer from the expect utility.