【发布时间】:2020-01-04 01:18:13
【问题描述】:
这是工具dc的前端;这个想法是键入一个中缀表达式 (2 + 3),将其映射到相应的后缀符号 (2 3 +) 并将其发送到dc。这就是bc 所做的。
我正在使用管道执行此操作,但前端挂起等待输出。
这是代码;我将在下面继续评论它。我的dc 中的“h”命令未实现,因此我正在寻找 dc 的输出结尾。
TL;DR:进程挂起是因为dc 中的stdout 没有被刷新,或者这就是我认为已经找到的。每次写入后如何读取或强制刷新?
我的发现在下面评论。
#define DC_EOF_RCV "dc: 'h' (0150) unimplemented"
#define DC_EOF_SND "h\n"
static FILE *sndfp, *rcvfp;
int dcinvoke() {
int pfdout[2], pfdin[2];
pid_t pid;
pipe(pfdout);
pipe(pfdin);
switch (pid = fork()) {
case -1: exit(1);
case 0:
dup2(pfdout[0], STDIN_FILENO);
dup2(pfdin[1], STDOUT_FILENO);
close(pfdout[0]);
close(pfdout[1]);
close(pfdin[0]);
close(pfdin[1]);
execlp("dc", "dc", "-", NULL);
}
close(pfdout[0]);
close(pfdin[1]);
sndfp = fdopen(pfdout[1], "w");
rcvfp = fdopen(pfdin[0], "r");
return 1;
}
void dcsnd(const char *s) {
fputs(s, sndfp);
fflush(sndfp);
}
void dcrcv(char *buf, size_t max) {
fgets(buf, max, rcvfp); // <<<<< HANGS HERE
}
int turnaround() {
dcsnd(DC_EOF_SND); fflush(sndfp);
}
int rcvall() {
char buf[256];
turnaround();
for (;;) {
dcrcv(buf, sizeof(buf));
if (! strcmp(buf, DC_EOF_RCV)) {
break;
}
printf("%s", buf);
}
return 1;
}
int prompt(const char *msg, char *res, size_t resmax, int *eofp) {
char *p;
printf("\n%s ", msg);
if (!fgets(res, resmax, stdin)) {
*eofp = 1;
} else {
if (p = strrchr(res, '\n')) {
*p = 0;
}
*eofp = 0;
}
return 1;
}
int main() {
char buf[128], line[128];
int eof;
dcinvoke();
dcsnd("2 3 +\n");
dcsnd(DC_EOF_SND);
rcvall();
for (;;) {
prompt("Expression", buf, sizeof(buf), &eof);
if (eof) {
break;
}
snprintf(line, sizeof(line), "%s\n", buf);
dcsnd(line);
rcvall();
}
dcsnd("q\n");
return 0;
}
为了简单起见,我删除了错误检查。
前端挂在行:
fgets(buf, max, rcvfp);
因为我真的不知道如何找到问题,所以我编写了自己的dc,它什么都不做,只是正确响应“h”命令并将它获取的所有内容输出到文件中(这样我就可以调试它;我我不知道任何其他方式)。有用的话我可以加在这里。
我发现在(我的)dc 中对fflush(stdout) 的调用会恢复前端,因为对fgets 的调用最终会返回。
我无法更改dc 本身。怎么能挂不上fgets?
我的一些想法:
使用另一个线程刷新
rcvfp(stdoutofdc)使用伪终端编写
我正在寻找建议或避免这两种情况的简单方法。
我尝试过:
【问题讨论】:
-
可能的原因:因为 dc 没有 tty,它的 stdout 处于完全缓冲模式。您需要给它一个 pty 以使输出行缓冲,或者在每次操作后使其退出以强制刷新其标准输出。
-
旁白:由于关于输出刷新的规则不同,在
printf("\n%s ", msg);之后,考虑fflush(stdout);以确保输出发生在fgets()之前。 -
您应该在
execlp()之后有错误代码(即使它只是exit(EXIT_FAILURE);)——它可能会失败,然后退出而不是尝试继续很重要。您不需要测试来自execlp()的返回值;如果成功则不会返回;如果返回,则失败。你也可以在退出前向stderr写一个错误。 -
bc的现代版本通常不使用dc。 POSIX 没有标准化dc的使用(dc不是 POSIX 的一部分,但bc是;基本原理提到dc及其在bc的某些实现中的使用)。 -
@JonathanLeffler 每个调用都包含错误检查宏,甚至是那个 exec,但正如我在问题中所说的,
I have removed the error checks for simplicity of this question.。没有必要使用dc除了作为使用管道的练习......我认为它会更容易,发现它比最初想象的更有趣