交互式外壳止动管

时间:2012-04-26 23:15:35

标签: c linux shell pipe

我的计划如下:

//... init fd[2] as pipe ...
if (child==0){
        close(fd[1]);
        dup2(fd[0], 0);
        execlp("/bin/sh","sh",NULL);
} else {
        close(fd[0]);
        char *line; int nbytes=100; int bytes=0;
        line=(char*) malloc(nbytes+1);
        while ( (bytes = getline((char **)&line,&nbytes,stdin))!= -1 ){
            write(fd[1],line, bytes);
        }
}

运行正常,但是当我尝试用exec("/bin/sh","sh",NULL)替换exec("/bin/sh","sh","-i",NULL)来强制交互式shell时,我的程序在执行第一个命令后停止。

我是管道新手,所以请帮助我理解原因并使交互式shell工作......我也觉得我的代码读取行并传递给子管道有点奇怪..有没有更好的方法实现相同的行为?

1 个答案:

答案 0 :(得分:1)

您应该在孩子的close(fd[0]);之后dup2()。如果您提供绝对路径或相对路径,例如"/bin/sh",那么使用execlp()就没有意义了;它只会进行基于PATH的裸文件名(程序名称)搜索。调用getline()的演员阵容应该是不必要的;尽可能避免这种演员。您应该在exit(1);之后至少包含execlp()以防它失败;诊断信息也是一个好主意。您应该在父项循环后close(fd[1]);向孩子表明EOF。 (只有一次,如果你没有从malloc()检测到错误返回并不重要;将指针保存为NULL的指针的地址传递给getline()函数是合法的,然后它将尝试分配内存本身。当然,如果主程序无法分配内存,getline()很可能也无法分配内存。)

这些变化导致:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
    int fd[2];
    pid_t child;

    if (pipe(fd) != 0)
        perror("pipe");
    else if ((child = fork()) < 0)
        perror("fork");
    else if (child == 0)
    {
        close(fd[1]);
        dup2(fd[0], 0);
        close(fd[0]);
        execl("/bin/sh", "sh", NULL);
        perror("oops");
        exit(1);
    }
    else
    {
        close(fd[0]);
        size_t nbytes = 100;
        int bytes = 0;
        char *line = (char*)malloc(nbytes+1);
        while ((bytes = getline(&line, &nbytes, stdin)) != -1)
        {
            write(fd[1], line, bytes);
        }
        close(fd[1]);
    }
    return(0);
}

在严格的编译标志下编译没有投诉:

gcc -O3 -g -std=c99 -Wall -Wextra xf.c -o xf

使用上面的代码运行(在Mac OS X 10.7.3上)(在没有sh选项的情况下调用-i),事情表现得相当理智。您可以键入命令,shell执行它们。您可以键入'exit'并退出shell,但是在我键入新命令之前,您编写的程序(我称之为xf)不会退出。然后由于SIGPIPE信号写入当前无读取器管道而退出。此shell没有提示,因为它的标准输入不是终端(它是管道)。

当使用-i选项运行子shell时,作业控制shell之间似乎存在关于哪个shell负责终端的争用。当我运行它时,我得到:

$ ps -f
  UID   PID  PPID   C STIME   TTY           TIME CMD
  503   381   372   0 Wed08PM ttys001    0:00.07 -sh
  503 21908   381   0  9:32PM ttys001    0:00.01 sh
$ ./xf
sh-3.2$ 

[1]+  Stopped(SIGTTIN)        ./xf
$ 
$ ps -f
  UID   PID  PPID   C STIME   TTY           TIME CMD
  503   381   372   0 Wed08PM ttys001    0:00.07 -sh
  503 21908   381   0  9:32PM ttys001    0:00.01 sh
  503 22000 21908   0  9:36PM ttys001    0:00.00 ./xf
  503 22001 22000   0  9:36PM ttys001    0:00.00 sh -i
$ ls
awk.data           osfile-keep.c      pthread-2.c        send.c             xf
const-stuff.c      perl.data          pthread-3.c        so.8854855.sql     xf.c
fifocircle.c       piped-merge-sort.c quine.c            strandsort.c       xf.dSYM
madump.c           powa.c             recv.c             unwrap.c           xxx.sql
makefile           pthread-1.c        regress.c          vap.c              yyy.sql
$ jobs
[1]+  Stopped(SIGTTIN)        ./xf
$ fg %1
./xf
exit
$

(最初的-sh是终端窗口的登录shell。在那里,我运行sh来获取子shell,并设置了提示符{{1}使提示与众不同。)

AFAICT,PS1='$ '提示来自sh-3.2$ shell。父shell似乎正在读取输入,并将sh -i程序转储到后台,后者对它不是很文明。 xf输出未显示ps -f命令,这是一个麻烦。我确实设法让ps命令显示在一次运行的ls列表中,它是原始shell的子项,而不是ps sh -i运行的xf }。当我将xf带到前台时,它立即退出(可能是它从标准输入读取0个字节,表示EOF,因此getline()返回-1,一切都关闭了商店。{{1来自exit;它回应它。它从来没有得到任何输入,因为sh -i shell接受命令而不是让sh控制终端。这非常难以理解。我我不确定为什么会这样,但我觉得它不应该像那样发生。