在C中编写shell

时间:2013-09-10 21:53:02

标签: c shell

我正在尝试用C编写shell,但遇到了问题。 shell应该在循环中运行,每次都提示用户,每次都从stdin读取和解析文本。然后将参数划分为标记,并将每个标记放入参数向量中。然后代码分叉一个子代,然后使用参数向量作为参数运行命令。然后代码等待子进程终止,并打印有关子进程的统计信息(运行时等)。问题是,每当我们运行程序时,当我们输入ls / home(例如)时,它不会列出主目录,而是列出我们当前所在的目录。此外,如果我们尝试添加新目录变量到代码,代码停止工作。知道如何解决这个问题吗?

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <string.h>

#define TRUE 1
#define BUFFERSIZE 129

int main (int argc, char* argv[]){

    int status;
    int who = RUSAGE_CHILDREN;
    struct rusage usage;
    struct rusage before_usage;
    struct timeval start, end;
    while(TRUE){
    printf("==>:  ");
    char input_string[BUFFERSIZE];
    memset(input_string, '\0', BUFFERSIZE);
    fgets(input_string, BUFFERSIZE-1, stdin);

        int i = 0;
        char* input_arguments[32]; //Pointers to what the commands will be
        char* token; //The specific piece of the input "ls" or "/home"
    char* program;
    int run_exec = 1; //To say whether execvp will run


    token = strtok(input_string, " ");
    char* prog = malloc(strlen(token)+1);
    memset(prog, '\0', strlen(token));
    strncpy(prog, token, strlen(token));
    program = prog;
    printf("PROGRAM: %s\n", prog);
    token = strtok(NULL, " ");


    if(strncmp(program, "exit", 4) ==0)
        exit(0);

        while(token != NULL && i < 32){

        printf("TOKEN: %s \n", token);
        char* tmp = malloc(strlen(token)+1);
        memset(tmp, '\0', strlen(token)+1);
        strncpy(tmp, token, strlen(token));
        input_arguments[i]=tmp;
        printf("INPUT: %s \n", input_arguments[i]);

        if(strcmp(program, "cd") == 0){
            chdir(input_arguments[0]);
        run_exec = 0;
        printf("EXEC: %d\n", run_exec);
    }

    printf("%d\n", i);
    i++;
    size_t tmp2 = 50;
    printf("%s\n", getcwd(tmp, tmp2));

    token = strtok(NULL, " ");

    printf("IN LOOP\n");
}
input_arguments[i] = NULL;
printf("AFTER LOOP\n");

if(fork() != 0){
    int start_time = gettimeofday(&start, NULL);
    if(run_exec == 1){
        getrusage(who, &before_usage);
            waitpid(-1, &status, 0);
            int end_time = gettimeofday(&end, NULL);
            double wall_time_passed = (end.tv_sec -start.tv_sec)*1000
                + (end.tv_usec - start.tv_usec)/1000;
            getrusage(who, &usage);
            double user_time = (usage.ru_utime.tv_sec*1000 +
                usage.ru_utime.tv_usec/1000);
            double system_time = (usage.ru_stime.tv_sec*1000 
                + usage.ru_stime.tv_usec/1000);

//long page_faults = 0;
//long soft_faults = 0;
//long invol = 0;
//long vol = 0;
            printf("Number of Page Faults: %ld \n", usage.ru_majflt -before_usage.ru_majflt);
        //printf("soft_faults: %lu\n", soft_faults);
        printf("Number of Page Reclaims: %ld \n",                                usage.ru_minflt - before_usage.ru_minflt);
        //printf("soft_faults: %lu\n", soft_faults);
        printf("Number of times preempted involuntarily: %ld \n",                usage.ru_nivcsw - before_usage.ru_nivcsw);
        printf("Number of times preempted Voluntarily: %ld \n",                     usage.ru_nvcsw - before_usage.ru_nivcsw);
            printf("User Time: %f \n", user_time);
            printf("System Time: %f \n", system_time);
            printf("Wall-Time: %f \n", (wall_time_passed));
    }
}

else{
    if(run_exec == 1){
    printf("RUNNING EXEC: %s, %s \n", program, *input_arguments); 
    printf("EXEC: \n", execvp(program, input_arguments));
    }

}
}

return 0;

}

2 个答案:

答案 0 :(得分:1)

尝试根据以下示例重新格式化变量:

....
if(run_exec == 1){
   //printf("RUNNING EXEC: %s, %s \n", program, *input_arguments); 
   //printf("EXEC: \n", execvp(program, input_arguments));
   char* args[] = { "ls", "/home" };
   execvp(args[0], args);
}
.....

您的代码已被注释,并替换为简单数组“ls”,“/ home”

或者,更具体一点 - 你必须设置 input_arguments [0]到程序名,并从input_arguments [0]开始添加程序参数。 e.g。

input_arguments[0] = "ls"
input_arguments[1] = "/var/log/"

这是您的代码补丁:

27c27
<         int i = 0;
---
>         int i = 1;
38a39
>     input_arguments[0]=prog;
106a108
> 

答案 1 :(得分:1)

这里有几件事:

  1. 每当你点击进入你的程序时,它就会跨越一个孩子,最终会导致waitpid。这是可取的吗?
  2. char* tmp = malloc(strlen(token)+1);
    memset(tmp, '\0', strlen(token)+1);
    strncpy(tmp, token, strlen(token));
    

    主要称为strdup

  3. 顺便说一下,如果你实际使用strlen(token)并且返回,那么token [strlen(token)]已经是'\ 0',因为strlen知道如何因此,上面的memset是不必要的(如果你用那个长度做strncpy)。

  4. 您的主要问题是您没有正确理解execvp man page。 它陈述如下:

      

    当作为此调用的结果执行C语言程序时,它应作为C语言函数调用输入,如下所示:

         

    int main(int argc,char * argv []);

    后来,它继续说:

      

    arg0,...表示的参数是指向以null结尾的字符串的指针。这些字符串应构成新过程映像可用的参数列表。该列表由空指针终止。 参数arg0应该指向一个exec函数与正在启动的进程相关联的文件名

    这显然是你错过的一点。 argv参数的第一个参数必须与路径参数传递的值相同(或允许的变体)。

  5. 更多关于这一点,通常shell可以探索PATH变量本身并通过execve调用替换过程映像(最后一个因为shell通常允许你修改环境变量),并且它们作为* path传递实际路径文件在PATH中找到,argv [0]参数由用户输入

    关于这一点,POSIX标准说

      

    对严格符合的POSIX应用程序的要求还规定,作为第一个参数传递的值是与正在启动的进程关联的文件名。 [...]在某些情况下,传递的文件名不是文件[...]

    的实际文件名

    所以,如果你有这样的可执行文件:

    #include <stdio.h>
    int main(int argc, char *argv[]) {
      printf("I was called as %s\n", argv[0]); return 0;
    }
    

    您可以将其编译为test,但将其符号链接到another_test

    test运行时,它应显示I was called as test,但不能作为another_test运行。这是有道理的,因为程序可能需要知道它们是如何被调用的。例如,busybox需要发生这种情况才能知道用户想要什么。

    但你发布的代码不仅仅是添加一些input_arguments[0] = program并在1中启动“int i”。我上面提到的那些东西也是如此:不要重新发明轮子,strdup已经存在了你应该使用而不是malloc + memset + strncpy。它减少了三个函数调用,代码所做的概念更加浓缩。

    每次循环结束时它会产生一个孩子,并且它不介意程序是否没有产生(可能程序不存在)。 检查run_exec后为什么不分叉? 为什么不等待你生成的那个特定线程(fork()在父级上返回的pid_t)?

    如果无法生成图像,为什么不杀死子进程? 目前,如果子进程在execvp调用中失败,那么它将要求更多命令,并且一旦被杀死,父进程将打印其统计信息并继续该作业。这可能是无意的,而且肯定是违反直觉的。

    此外,我不知道你是否注意到了,但你没有检查stdin中的EOF,如果你是从文件中提取而不是交互式(或者如果用户输入EOF [Ctrl] 。+ d])。您可能也应该将退出操作绑定到EOF,否则您将无法完成“使用”脚本文件。