好吧,我正在尝试使用C语言为linux编写一个shell。使用函数fork()和execl(),我可以执行每个命令,但现在我一直试图读取参数:
char * command;
char ** c_args = NULL;
bytes_read = getline (&command, &nbytes, stdin);
command = strtok(command, "\n ");
int arg = 0;
c_arg = strtok(NULL, "\n ");
while( c_arg != NULL ) {
if( c_args == NULL ) {
c_args = (char**) malloc(sizeof(char*));
}
else {
c_args = (char**) realloc( c_args, sizeof(char*) * (arg + 1) );
}
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
c_arg = strtok(NULL, "\n ");
arg++;
}
...
pid_t pid = fork()
...
...
execl( <path>, command, c_args, NULL)
...
...
这样我在尝试传递参数时会从命令中获得错误,例如:
ls -l
给我:
ls: cannot access p��: No such file or directory
我知道问题是c_args分配。怎么了?
干杯。
答案 0 :(得分:2)
您不能将execl()
用于变量参数列表;您需要使用execv()
或其中一个变体(execve()
,execvp()
等)。当您知道编译时将出现的所有参数时,您只能使用execl()
。在大多数情况下,一般shell都不会知道。例外情况是,当您执行以下操作时:
execl("/bin/sh", "/bin/sh", "-c", command_line, (char *)0);
在这里,您调用shell来运行单个字符串作为命令行(没有其他参数)。但是,当您处理人们在完整shell中键入键盘的内容时,您将无法知道他们在编译时键入了多少个参数。
最简单的,你应该使用:
execvp(c_args[0], c_args);
第0个参数,即命令名称,应该是您传递给execvp()
的参数。如果这是一个简单的文件名(无/
),那么它将在$PATH
环境变量的目录中查找命令。如果命令名称包含斜杠,则它将查找指定的(相对或绝对)文件名,如果存在则执行该文件名,如果不存在则执行。其他参数都应该在以空值终止的列表c_args
中。
现在,可能还有其他内存分配问题;我没有仔细检查代码。但是,您可以通过参数列表的诊断打印来检查它们:
char **pargs = c_args;
while (*pargs != 0)
puts(*pargs++);
将每个参数打印在一个单独的行上。请注意,它在遇到空指针之前不会停止;至关重要的是,null null终止指向参数字符串的指针列表。
你的代码:
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
在通常情况下,看起来有点过分,而在极端情况下,内存分配不足。当你复制字符串时,分配足够的长度。我看到你正在使用strtok()
来破坏字符串 - 它会为shell的早期版本做,但是当你开始处理像ls -l>$tmp
这样的命令行时,你会发现{{}在你阅读它之前践踏你的分隔符的倾向成为一个主要的责任。但是,当您使用它时,您可能不必复制那样的参数;你可以设置strtok()
。当你需要复制时,你应该使用c_args[arg++] = result_from_strtok;
;例如,它不会忘记为尾随strdup()
分配足够的空间,既不会过度分配也不会分配不足。
答案 1 :(得分:1)
乔纳森有一个很好的答案,我只是想补充一些东西。
你可以使用popen
或system
直接由shell执行。它们通常不受欢迎,因为它们可以很容易地注入,但如果你正在写一个开壳,我看不出使用它们的危害。
如果您要使用有限的shell(接受类似sh语法),请查看wordexp。它做了很多,但根据我的经验,它确实做得太多了,特别是如果你正在尝试编写一个中等安全的解释器(它确实像波形扩展和变量替换那样愚蠢的事情)。