从Shell脚本中读取并写入C程序

4
我有一个简短的C程序,输出3行,然后从标准输入读取。我想使用以下shell脚本与其交互:
  1. 逐行读取c输出:while read line; do ...; done <$out_
  2. 保存第二行:if [ "$count" == 2 ]; then input=$line; fi
  3. 将保存的行发送回程序:echo $input >$in_
我在shell脚本中使用命名管道来重定向C程序的输出和输入:
./iotest >$out_ <$in_ &

但是我没有得到预期的结果。

问题

使用mkfifo创建管道out_in_后,我使用while read循环,但似乎导致死锁。读取不到任何内容,shell脚本在读取命令上陷入了困境。

我尝试在启动iotest之前添加cat >$in_ &,如此处所述,打开写入in_而不实际写入任何内容。这解除了阻塞,但我无法将输入写入in_(我的C程序好像已经将\x00写入stdin,echo $input >$in_命令挂起了)。

我尝试过的方法

在这里我读到如果没有写入者,读取会被阻塞,而没有cat >$in_ &时,iotest会写入out_,而read从中读取,我试图将in_重定向到iotest,并从一开始就不写任何内容。

但是为什么添加cat命令会使C程序在读取时失败,我不知道。如果我用while true; do :; done >$in_&exec 3>$in_ &(如此处所述)交换cat >$in_ &,也会出现相同的情况。

代码

这是我的C代码:

$ 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;
}

这是我测试过的shell脚本:

$ 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_

我已在32位Debian和64位Ubuntu上使用gcc进行编译,并使用bash执行shell脚本进行了测试。

结果

没有添加cat命令的情况下:

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

使用添加了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.

摘要

我的问题是:我的shell脚本有什么问题,我该如何修改它以读取并写入我的c程序?我不能更改c代码本身,但可以调整shell脚本。

非常感谢您的帮助!


如果可以的话,请尝试在strace -f下运行脚本(请参阅手册以获取输出选项等)。看到哪个进程在哪个文件描述符上阻塞真的很有帮助。 - Useless
1
打开一个FIFO进行写入,直到有其他进程打开它进行读取,反之亦然。 - Shawn
你的 C 程序需要输入(来自管道)。你的脚本必须将输入发送到程序(或其他进程应该提供它)。 - dash-o
谢谢@Useless。我会看一下strace! - borizzzzz
例如,如果您在脚本中字面上添加了行cat > $in_ &,但使用runiotest.sh <&-执行脚本,则cat将立即终止,并且iotest中的读取将返回0。(由于这里存在很多竞争条件取决于进程执行的顺序,因此将“将”改为“可能”) - William Pursell
显示剩余2条评论
2个回答

1
考虑如下内容:
./iotest >$out_ <$in_ &
while read line; do ...; done < $out_

在这里,子shell尝试打开$out_以进行写入和$in_以进行读取,只有当这两个FIFO都打开时,它才会执行iotest。如果没有添加cat,则子shell无法打开$in_,直到有一些写入程序,因此iotest甚至不会开始执行。当您添加cat时,子shell能够打开$in_,并且主shell充当$out_的读取器,因此子shell能够打开两个FIFO并执行iotest。至于您看到的行为,这是iotest.c中的一个错误。打印由read返回的值(输出到stderr!),包括对read的声明,并尝试使用!= -1而不是> -1。

谢谢您的回答。确实,我应该检查read(...) =! -1,因为这是错误时返回的值。在我的情况下,read函数返回0表示EOF,并且包括unistd.h以进行读取声明不会改变我在问题中描述的行为。不过,我会根据您的更正更新代码。 - borizzzzz

0
问题出在 C 程序输出的缓冲。使用 stdbuf 命令去除缓冲即可解决:
cat >$in_ &
stdbuf -o0 ./iotest <$out_ >$in_ &

通过这个更改,输出结果就是应该的:

$ /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!

补充一点,如果可执行文件设置了setuid位,则使用stdbuf是无效的。在这种情况下,您可以使用expect工具中的unbuffer


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接