【问题标题】:Shell seems to recognize commands but doesn't execute themShell 似乎可以识别命令但不执行它们
【发布时间】:2015-12-09 20:50:55
【问题描述】:

我正在尝试在类 unix xv6 操作系统的 shell 程序中实现基本命令执行。我正在编辑的部分 shell 代码是 runcmd 函数,我在其中使用 execvp 命令执行终端中使用的命令。该程序在我编译时编译没有错误,但是当我尝试在命令行上键入命令时没有任何反应。我已经阅读了 exec 命令的man pages,但我仍然不明白这些参数需要在 exec() 命令中传递的正确方式,或者何时使用哪个版本的 exec()我对操作系统编程还是很陌生。

我在这里没有实现哪些需要添加才能执行命令?我有下面 runcmd 函数的代码:

编辑:

我刚刚为每个命令添加了更多带有二进制文件路径的 exec 语句;但是,只有第一个 exec 命令有效(在本例中为 cd)。当我使用任何其他命令时,命令行会像执行 CD 一样执行它。如何让它适用于多个命令?

struct cmd {
  int type;          //  ' ' (exec), | (pipe), '<' or '>' for redirection
};
struct
 execcmd {
  int type;              // ' '
  char *argv[MAXARGS];   // arguments to the command to be exec-ed
};

// Execute cmd.  Never returns.
void
runcmd(struct cmd *cmd)
{
  int p[2], r;
  struct execcmd *ecmd;
  struct pipecmd *pcmd;
  struct redircmd *rcmd;

  if(cmd == 0)
    exit(0);
  
  switch(cmd->type){
  default:
    fprintf(stderr, "unknown runcmd\n");
    exit(-1);

  case ' ':
    ecmd = (struct execcmd*)cmd;
    
    if(ecmd->argv[0] == 0)
      exit(0);
    //fprintf(stderr, "exec not implemented\n");
     execvp("/bin/cd" , ecmd->argv );
     execvp("/bin/grep" , ecmd->argv );
     execvp("/bin/echo" , ecmd->argv );
     execvp("/bin/cat" , ecmd->argv );
     execvp("/bin/ls" , ecmd->argv );
    break;

  case '>':
  case '<':
    rcmd = (struct redircmd*)cmd;
    //fprintf(stderr, "redir not implemented\n");
    execvp("/bin" , ecmd->argv );
    runcmd(rcmd->cmd);
    break;

  case '|':
    pcmd = (struct pipecmd*)cmd;
    fprintf(stderr, "pipe not implemented\n");
    int execl(const char *path, const char *arg, ...);
    break;
  }    
  exit(0);
}

【问题讨论】:

  • 好吧,对于初学者来说,int execl(const char *path, const char *arg, ...); 声明函数execl,它不会调用它。这个“xv6”操作系统有&lt;unistd.h&gt;吗?
  • @zwol 是的,它有 并且我还没有替换该行,因为我认为我可以等到我必须实施管道。
  • 重新修改问题,exec 系列函数仅在失败时返回;你需要一个fork 在某个地方。此外,cd 是一种特殊命令,必须作为 shell 内置实现,而不是作为外部可执行文件;我希望/bin/cd 不存在。
  • Re execl,你说得对,它没有造成任何伤害,我读错了代码部分。很抱歉造成混乱。

标签: c linux shell operating-system xv6


【解决方案1】:

您似乎正在尝试执行“/bin”目录。

您应该将 exec 调用的第一个参数设为用户想要运行的二进制文件。

当命令失败时,使用 perror 函数也会给你有用的输出。

【讨论】:

  • “二进制”是指每个命令的文件位置的路径吗?如果是这样,那不是让它只执行位于那个位置的命令吗?我将如何使它可以执行任何命令(例如 ls、grep、cat 等......)。
【解决方案2】:

认为这是你真正需要的:

case ' ':
    ecmd = (struct execcmd*)cmd;
    if (ecmd->argv[0] == 0)
        _exit(0);
    execvp(ecmd->argv[0], ecmd->argv);
    /* if control reaches this point, execvp failed */
    perror(ecmd->argv[0]);
    _exit(127);

您可能想知道为什么execvp 将可执行文件作为与参数向量分开的参数加载,而您只是将其交给argv[0]。这是遗留功能;如果我今天从头开始设计这个 API,我认为我不会包含它。然而,这个想法是,一个程序的行为可能会有所不同,具体取决于它的argv[0] 是什么。例如,在过去,exvi相同的可执行文件(两个指向同一个 inode 的硬链接——符号链接尚未发明),它们决定了启动的模式基于其argv[0]。而且,自从 Unix 诞生以来,login(1) 拥有invoked shells with the first character of argv[0] set to '-';没有/bin/-sh,因此它实际上需要能够将程序调用与参数向量分开指定。

我目前无法想到 shell 会使用除 argv[0] 以外的任何内容作为 execvp 的第一个参数的情况。

关于您的代码的其他更改的说明:

  • 切勿在 fork 的子端调用 exit,仅调用 _exit
  • 当系统调用失败时,总是打印出问题文件的名称和strerror(errno),到stderrnotstdoutperror 是此操作的便捷简写。
  • 为了安全起见perror 调用,您必须在fork 之前立即在父级中调用fflush(0),并且您的C 库必须实现stderr 的行缓冲,正确。我提到这一点是因为 XV6 似乎是一个教学操作系统,你必须自己实现它的一些部分,而且我不知道 stdio 是否是你的责任。

【讨论】:

  • 谢谢,这是一个非常清晰易懂的答案!是的,我很困惑为什么必须将 ecmd->argv 作为另一个参数。所以你基本上是说它的标准是在大多数情况下使用 argv 数组的第一个元素,除了一些我假设需要额外索引的登录?
  • @loremIpsum1771 对。由于您很好奇,我将在答案正文中对此进行一些扩展。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-22
  • 2011-02-20
  • 2018-01-24
  • 1970-01-01
  • 2018-12-19
  • 1970-01-01
相关资源
最近更新 更多