我想在这个问题前面说我是C的新手并因此很糟糕,所以我提前为任何明显的错误或糟糕的风格道歉。此外,我不确定如何在向您显示我的代码之前引入问题,所以这里是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int MAX_INPUT_SIZE = 200;
volatile int running = 1;
while (running)
{
char input[MAX_INPUT_SIZE];
char *tokens[100];
const char *cmds[] = { "wait", "pwd", "cd", "exit" };
char *cmdargs[100];
printf("shell> ");
fgets(input, MAX_INPUT_SIZE, stdin);
// remove newline character at end
int nl = strlen(input) - 1;
if (input[nl] == '\n')
{
input[nl] = '\0';
}
// tokenize input string, put each token into an array
char *space;
space = strtok(input, " ");
tokens[0] = space;
int i = 1;
while (space != NULL)
{
space = strtok(NULL, " ");
tokens[i] = space;
++i;
}
// copy tokens after first one into string
int noargscheck;
if (tokens[1] != NULL)
{
noargscheck = 0;
strcpy((char *)cmdargs, tokens[1]);
for (i = 2; tokens[i] != NULL; i++)
{
strcat((char *)cmdargs, " ");
strcat((char *)cmdargs, tokens[i]);
}
}
else
{
noargscheck = 1;
}
// compare tokens[0] to list of internal commands
int isInternal = -1;
for (i = 0; i < 4; i++)
{
if (strcmp(tokens[0], cmds[i]) == 0)
{
isInternal = i;
}
}
// internal commands
char wd[200];
if (isInternal != -1)
{
switch (isInternal)
{
case 0:
// wait
break;
case 1:
// pwd
if (getcwd(wd, sizeof(wd)) == NULL)
{
perror("getcwd() error!");
}
else
{
printf("%s\n", wd);
}
break;
case 2:
// cd
if (noargscheck)
{
chdir("/home");
}
else if (chdir((const char *)cmdargs) != 0)
{
perror("cd failed");
}
break;
case 3:
// exit
exit(1);
break;
}
}
else
{
// external commands
pid_t child_pid;
switch (child_pid = fork())
{
case -1:
perror("Fork failed");
return 1;
case 0:
// child
printf("\nHERE\n"); // for debugging
execvp(tokens[0], cmdargs);
break;
}
}
}
}
当我使用输入echo hello world
运行此代码时,程序在以case 0
开头的第二个switch语句中成功输入switch (child_pid=fork())
情况,但输出是意外的,如下:
OUTPUT :(包括在提示符下显示我输入的一行)
shell> echo hello world
(我的意见)
shell>
(这是我不理解的部分)
HERE
shell>
(程序现在等待下一个用户输入的提示)
我无法弄清楚为什么要打印额外的shell>
提示。任何人都可以看到问题吗?
编辑:修复了execvp的第一个参数。已从"echo"
(因为我很傻)改为tokens[0]
。
答案 0 :(得分:7)
当你分叉时,你现在有两个进程。您的孩子将打印HERE
消息,然后拨打execvp
。您不检查execvp
的返回值,因此可能会返回错误。 cmdargs
应该是一个向量 - 也就是说,一个以空指针终止的字符串指针数组。相反,您传递的是execvp
字符串。换句话说,它需要char* []
,这是cmdargs
的内容,但您之前已经错误地处理了cmdargs
。
例如,您说strcpy((char*)cmdargs, tokens[1]);
。这会在*cmdargs
处放置一个字符串。字符串是一个零或多个非零字符的数组,后跟一个8位宽的ascii NUL
:
char* cmdargs[] is a double pointer
you treat cmdargs as a single pointer and feed it to strcpy
cmdargs points to: 'H' 'e' 'l' 'l' 'o' '\0'
但是,这不是execvp想要的。 Execvp需要一个向量,看起来更像这样:
char* cmdargs[] is a double pointer
cmdargs[0] is a single pointer
cmdargs[0] points to: 'H' 'e' 'l' 'l' 'o' '\0'
cmdargs[1] points to: 'w' 'o' 'r' 'l' 'd' '\0'
cmdargs[2] is a null pointer, indicating that it is the end of the vector
因此,execvp无法找到向量的末尾并失败,返回-1
。您没有对此进行测试,因此子进程继续返回到循环的顶部,父进程也是如此,并且两个进程都打印shell>
。
编辑:顺便说一句,argv
向量中的FIRST字符串应该是正在执行的文件的名称 - 在这种情况下,echo
,然后第二个和第三个字符串应该是第一个和第二个“参数” - 此处hello
和world
。这是输入到您调用的程序的argv向量,按照惯例,该向量中的第一个元素是被调用程序的名称。如果忽略该约定,echo
将会非常混乱。
答案 1 :(得分:0)
cmdargs定义为包含100个字符串指针的数组,但您似乎将其用作单个字符串的一个100字节缓冲区。我也不明白为什么你特意处理令牌[1]。只有令牌[0]是特殊的,这是命令,所有其他都是参数。处理参数应该是
的循环while (cmdargs[i++] = strtok(NULL, " "))
然后在exedvp()的cmdargs中关闭NULL指针:
cmdargs[i] = NULL;
这是一个shell,你也忘了等待子进程。在最后一个子进程完成之前,您将提示用户输入。最终的开关案例应该看起来像这样:
pid_t child_pid;
switch (child_pid = fork())
{
case -1:
perror("Fork failed");
return 1;
case 0:
// child
printf("\nHERE\n"); // for debugging
execvp(tokens[0], cmdargs);
perror("Exec failed");
exit(1);
default:
// parent
int status;
wait(&status);
break;
}