我的 execvp 使用有什么问题?
What is wrong with my execvp usage?
我正在写一个小的shell来学习C。现在我想执行自定义命令但是它不起作用。
$ ./a.out
OS>ls
10357: executing ls
failed to execute ls
: (2: No such file or directory)
我不能使用系统调用来执行自定义命令,我应该使用 execvp 和 fork。但为什么它现在起作用了?整个代码为
#include<sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
int mystrcmp(char const *, char const *);
struct command
{
char * const *argv;
};
static _Noreturn void err_syserr(char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
/* Helper function that spawns processes */
static int spawn_proc(int in, int out, struct command *cmd)
{
pid_t pid;
if ((pid = fork()) == 0)
{
if (in != 0)
{
if (dup2(in, 0) < 0)
err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
;
close(in);
}
if (out != 1)
{
if (dup2(out, 1) < 0)
err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
close(out);
}
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
execvp(cmd->argv[0], cmd->argv);
err_syserr("failed to execute %s: ", cmd->argv[0]);
}
else if (pid < 0) {
err_syserr("fork failed: ");
}
return pid;
}
/* Helper function that forks pipes */
static void fork_pipes(int n, struct command *cmd)
{
int i;
int in = 0;
int fd[2];
for (i = 0; i < n - 1; ++i)
{
pipe(fd);
spawn_proc(in, fd[1], cmd + i);
close(fd[1]);
in = fd[0];
}
if (dup2(in, 0) < 0) {
err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
}
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
execvp(cmd[i].argv[0], cmd[i].argv);
err_syserr("failed to execute %s: ", cmd[i].argv[0]);
}
#define BUFFERSIZE 200
int main() {
char *args[80];
char buffer[BUFFERSIZE];
char *prompt = "OS";
char *a = ">";
char *tok;
tok = strtok (buffer," ");
while(buffer != NULL) {
bzero(buffer, BUFFERSIZE);
printf("%s%s",prompt,a);
fgets(buffer, BUFFERSIZE, stdin);
if(mystrcmp(buffer,"cd") == 0) {
tok = strchr(buffer,' ')+1; //use something more powerful
*strchr(tok, '\n')='[=11=]';
cd(tok);
}
else if(mystrcmp(buffer,"exit") == 0) {
return 0;
}
else {
//system("ls"); //for testing the CWD/PWD
char *commandbuffer[] = { buffer, 0 };
//char *less[] = { "less", 0 };
struct command cmd[] = { {commandbuffer} };
fork_pipes(1, cmd);
printf("Spawned foreground process: %d\n", getpid());
}
}
return 0;
}
int mystrcmp(char const *p, char const *q)
{
int i = 0;
for(i = 0; q[i]; i++)
{
if(p[i] != q[i])
return -1;
}
return 0;
}
int cd(char *pth) {
char path[BUFFERSIZE];
strcpy(path,pth);
char *token;
char cwd[BUFFERSIZE];
if(pth[0] != '/')
{ // true for the dir in cwd
getcwd(cwd,sizeof(cwd));
strcat(cwd,"/");
strcat(cwd,path);
chdir(cwd);
} else { //true for dir w.r.t. /
chdir(pth);
}
printf("Spawned foreground process: %d\n", getpid());
return 0;
}
错误不在于您使用 execvp
,而在于您使用 fgets
。 fgets
将换行符留在缓冲区中行的末尾,因此最终您将 "ls\n"
提供给 execvp
,它正确地抱怨找不到该命令。
因为我猜你最终会替换这段代码,所以暂时
fgets(buffer, BUFFERSIZE, stdin);
strtok(buffer, "\n"); /* quick & dirty: remove newline if there. */
解决这个问题,直到您开始正确地进行输入解析。 不过,我不能推荐任何使用 strtok
作为长期解决方案的方法。 从长远来看,您可能对 GNU 特定的 getline
功能感兴趣,或者确实在 libreadline 中(如果将您的代码置于 GPL 下对您来说不是问题)。
之后
fgets(buffer, BUFFERSIZE, stdin);
buffer
总是以 '\n' 结尾,因为您的输入以 return 结束。
因此,如果您只是将 ls
作为命令传递,您的程序将获得 ls\n
,显然 PATH
中没有这样的命令或二进制文件。
要解决此问题,您只需执行以下操作:
fgets(buffer, BUFFERSIZE, stdin);
if (buffer[strlen(buffer)-1] == '\n')
buffer[strlen(buffer)-1] = '[=11=]';
....
像往常一样,用strace就可以解决这个问题。
不幸的是,代码错误太多,我无法写出详尽的评论。
meh.c:99:13: warning: implicit declaration of function 'cd' is invalid
in C99 [-Wimplicit-function-declaration]
cd(tok);
^
meh.c:80:11: warning: unused variable 'args' [-Wunused-variable]
char *args[80];
^
meh.c:132:11: warning: unused variable 'token' [-Wunused-variable]
char *token;
^
3 warnings generated.
这是怎么回事?
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
int mystrcmp(char const *, char const *);
struct command
{
char * const *argv;
};
static _Noreturn void err_syserr(char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
改为考虑非标准错误函数。
/* Helper function that spawns processes */
static int spawn_proc(int in, int out, struct command *cmd)
{
pid_t pid;
if ((pid = fork()) == 0)
{
if (in != 0)
{
if (dup2(in, 0) < 0)
err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
;
close(in);
}
if (out != 1)
{
if (dup2(out, 1) < 0)
err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
close(out);
}
如果您必须像这样签入和签出 fds,很可能您已经做错了什么。考虑如果 'out' 为 0 会发生什么。在这个级别,只需确保您的 shell 始终打开 0,1,2 即可解决问题。
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
execvp(cmd->argv[0], cmd->argv);
err_syserr("failed to execute %s: ", cmd->argv[0]);
}
else if (pid < 0) {
err_syserr("fork failed: ");
}
改组允许尽早放置父代码并避免长子案例的缩进。
return pid;
}
/* Helper function that forks pipes */
static void fork_pipes(int n, struct command *cmd)
{
int i;
int in = 0;
int fd[2];
for (i = 0; i < n - 1; ++i)
{
pipe(fd);
spawn_proc(in, fd[1], cmd + i);
close(fd[1]);
in = fd[0];
}
if (dup2(in, 0) < 0) {
err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
}
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
如果带有换行符的 printfs 不够用,strace 会揭示问题:
execve("/usr/bin/ls\n", ["ls\n"], [/* 58 vars */]) = -1 ENOENT (No such file or directory)
execvp(cmd[i].argv[0], cmd[i].argv);
你知道这会覆盖你的 shell 吗?
err_syserr("failed to execute %s: ", cmd[i].argv[0]);
}
#define BUFFERSIZE 200
int main() {
char *args[80];
char buffer[BUFFERSIZE];
char *prompt = "OS";
char *a = ">";
char *tok;
tok = strtok (buffer," ");
while(buffer != NULL) {
bzero(buffer, BUFFERSIZE);
printf("%s%s",prompt,a);
fgets(buffer, BUFFERSIZE, stdin);
if(mystrcmp(buffer,"cd") == 0) {
tok = strchr(buffer,' ')+1; //use something more powerful
*strchr(tok, '\n')='[=16=]';
cd(tok);
}
else if(mystrcmp(buffer,"exit") == 0) {
return 0;
}
else {
//system("ls"); //for testing the CWD/PWD
char *commandbuffer[] = { buffer, 0 };
//char *less[] = { "less", 0 };
struct command cmd[] = { {commandbuffer} };
fork_pipes(1, cmd);
printf("Spawned foreground process: %d\n", getpid());
}
}
return 0;
}
int mystrcmp(char const *p, char const *q)
{
int i = 0;
这个初始化是怎么回事?
for(i = 0; q[i]; i++)
不正确。你假设 q 不长于 p.
{
if(p[i] != q[i])
return -1;
}
有比逐字符比较更好的方法。
return 0;
}
这是干什么用的?
int cd(char *pth) {
char path[BUFFERSIZE];
strcpy(path,pth);
路径和pth?掌管。考虑 'orig_path' 之类的。一个 /word/ 的变体看起来像是打字错误,实际上您很容易不小心打错它。 fscking 避免。
char *token;
char cwd[BUFFERSIZE];
if(pth[0] != '/')
{ // true for the dir in cwd
getcwd(cwd,sizeof(cwd));
strcat(cwd,"/");
strcat(cwd,path);
chdir(cwd);
即使忽略了常见的缓冲区溢出问题和缺少错误检查,这也是不正确的。如果与此过程相关的目录树在您 getcwd 之后被修改,则您输入了错误的目录(假设 chdir 成功)。此外,包含“..”的路径对符号链接敏感。1
} else { //true for dir w.r.t. /
chdir(pth);
}
printf("Spawned foreground process: %d\n", getpid());
好像是复制粘贴?
return 0;
}
我正在写一个小的shell来学习C。现在我想执行自定义命令但是它不起作用。
$ ./a.out
OS>ls
10357: executing ls
failed to execute ls
: (2: No such file or directory)
我不能使用系统调用来执行自定义命令,我应该使用 execvp 和 fork。但为什么它现在起作用了?整个代码为
#include<sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
int mystrcmp(char const *, char const *);
struct command
{
char * const *argv;
};
static _Noreturn void err_syserr(char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
/* Helper function that spawns processes */
static int spawn_proc(int in, int out, struct command *cmd)
{
pid_t pid;
if ((pid = fork()) == 0)
{
if (in != 0)
{
if (dup2(in, 0) < 0)
err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
;
close(in);
}
if (out != 1)
{
if (dup2(out, 1) < 0)
err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
close(out);
}
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
execvp(cmd->argv[0], cmd->argv);
err_syserr("failed to execute %s: ", cmd->argv[0]);
}
else if (pid < 0) {
err_syserr("fork failed: ");
}
return pid;
}
/* Helper function that forks pipes */
static void fork_pipes(int n, struct command *cmd)
{
int i;
int in = 0;
int fd[2];
for (i = 0; i < n - 1; ++i)
{
pipe(fd);
spawn_proc(in, fd[1], cmd + i);
close(fd[1]);
in = fd[0];
}
if (dup2(in, 0) < 0) {
err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
}
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
execvp(cmd[i].argv[0], cmd[i].argv);
err_syserr("failed to execute %s: ", cmd[i].argv[0]);
}
#define BUFFERSIZE 200
int main() {
char *args[80];
char buffer[BUFFERSIZE];
char *prompt = "OS";
char *a = ">";
char *tok;
tok = strtok (buffer," ");
while(buffer != NULL) {
bzero(buffer, BUFFERSIZE);
printf("%s%s",prompt,a);
fgets(buffer, BUFFERSIZE, stdin);
if(mystrcmp(buffer,"cd") == 0) {
tok = strchr(buffer,' ')+1; //use something more powerful
*strchr(tok, '\n')='[=11=]';
cd(tok);
}
else if(mystrcmp(buffer,"exit") == 0) {
return 0;
}
else {
//system("ls"); //for testing the CWD/PWD
char *commandbuffer[] = { buffer, 0 };
//char *less[] = { "less", 0 };
struct command cmd[] = { {commandbuffer} };
fork_pipes(1, cmd);
printf("Spawned foreground process: %d\n", getpid());
}
}
return 0;
}
int mystrcmp(char const *p, char const *q)
{
int i = 0;
for(i = 0; q[i]; i++)
{
if(p[i] != q[i])
return -1;
}
return 0;
}
int cd(char *pth) {
char path[BUFFERSIZE];
strcpy(path,pth);
char *token;
char cwd[BUFFERSIZE];
if(pth[0] != '/')
{ // true for the dir in cwd
getcwd(cwd,sizeof(cwd));
strcat(cwd,"/");
strcat(cwd,path);
chdir(cwd);
} else { //true for dir w.r.t. /
chdir(pth);
}
printf("Spawned foreground process: %d\n", getpid());
return 0;
}
错误不在于您使用 execvp
,而在于您使用 fgets
。 fgets
将换行符留在缓冲区中行的末尾,因此最终您将 "ls\n"
提供给 execvp
,它正确地抱怨找不到该命令。
因为我猜你最终会替换这段代码,所以暂时
fgets(buffer, BUFFERSIZE, stdin);
strtok(buffer, "\n"); /* quick & dirty: remove newline if there. */
解决这个问题,直到您开始正确地进行输入解析。 不过,我不能推荐任何使用 strtok
作为长期解决方案的方法。 从长远来看,您可能对 GNU 特定的 getline
功能感兴趣,或者确实在 libreadline 中(如果将您的代码置于 GPL 下对您来说不是问题)。
之后
fgets(buffer, BUFFERSIZE, stdin);
buffer
总是以 '\n' 结尾,因为您的输入以 return 结束。
因此,如果您只是将 ls
作为命令传递,您的程序将获得 ls\n
,显然 PATH
中没有这样的命令或二进制文件。
要解决此问题,您只需执行以下操作:
fgets(buffer, BUFFERSIZE, stdin);
if (buffer[strlen(buffer)-1] == '\n')
buffer[strlen(buffer)-1] = '[=11=]';
....
像往常一样,用strace就可以解决这个问题。
不幸的是,代码错误太多,我无法写出详尽的评论。
meh.c:99:13: warning: implicit declaration of function 'cd' is invalid in C99 [-Wimplicit-function-declaration] cd(tok); ^ meh.c:80:11: warning: unused variable 'args' [-Wunused-variable] char *args[80]; ^ meh.c:132:11: warning: unused variable 'token' [-Wunused-variable] char *token; ^ 3 warnings generated.
这是怎么回事?
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
int mystrcmp(char const *, char const *);
struct command
{
char * const *argv;
};
static _Noreturn void err_syserr(char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
改为考虑非标准错误函数。
/* Helper function that spawns processes */
static int spawn_proc(int in, int out, struct command *cmd)
{
pid_t pid;
if ((pid = fork()) == 0)
{
if (in != 0)
{
if (dup2(in, 0) < 0)
err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
;
close(in);
}
if (out != 1)
{
if (dup2(out, 1) < 0)
err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
close(out);
}
如果您必须像这样签入和签出 fds,很可能您已经做错了什么。考虑如果 'out' 为 0 会发生什么。在这个级别,只需确保您的 shell 始终打开 0,1,2 即可解决问题。
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
execvp(cmd->argv[0], cmd->argv);
err_syserr("failed to execute %s: ", cmd->argv[0]);
}
else if (pid < 0) {
err_syserr("fork failed: ");
}
改组允许尽早放置父代码并避免长子案例的缩进。
return pid;
}
/* Helper function that forks pipes */
static void fork_pipes(int n, struct command *cmd)
{
int i;
int in = 0;
int fd[2];
for (i = 0; i < n - 1; ++i)
{
pipe(fd);
spawn_proc(in, fd[1], cmd + i);
close(fd[1]);
in = fd[0];
}
if (dup2(in, 0) < 0) {
err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
}
fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
如果带有换行符的 printfs 不够用,strace 会揭示问题:
execve("/usr/bin/ls\n", ["ls\n"], [/* 58 vars */]) = -1 ENOENT (No such file or directory)
execvp(cmd[i].argv[0], cmd[i].argv);
你知道这会覆盖你的 shell 吗?
err_syserr("failed to execute %s: ", cmd[i].argv[0]);
}
#define BUFFERSIZE 200
int main() {
char *args[80];
char buffer[BUFFERSIZE];
char *prompt = "OS";
char *a = ">";
char *tok;
tok = strtok (buffer," ");
while(buffer != NULL) {
bzero(buffer, BUFFERSIZE);
printf("%s%s",prompt,a);
fgets(buffer, BUFFERSIZE, stdin);
if(mystrcmp(buffer,"cd") == 0) {
tok = strchr(buffer,' ')+1; //use something more powerful
*strchr(tok, '\n')='[=16=]';
cd(tok);
}
else if(mystrcmp(buffer,"exit") == 0) {
return 0;
}
else {
//system("ls"); //for testing the CWD/PWD
char *commandbuffer[] = { buffer, 0 };
//char *less[] = { "less", 0 };
struct command cmd[] = { {commandbuffer} };
fork_pipes(1, cmd);
printf("Spawned foreground process: %d\n", getpid());
}
}
return 0;
}
int mystrcmp(char const *p, char const *q)
{
int i = 0;
这个初始化是怎么回事?
for(i = 0; q[i]; i++)
不正确。你假设 q 不长于 p.
{
if(p[i] != q[i])
return -1;
}
有比逐字符比较更好的方法。
return 0;
}
这是干什么用的?
int cd(char *pth) {
char path[BUFFERSIZE];
strcpy(path,pth);
路径和pth?掌管。考虑 'orig_path' 之类的。一个 /word/ 的变体看起来像是打字错误,实际上您很容易不小心打错它。 fscking 避免。
char *token;
char cwd[BUFFERSIZE];
if(pth[0] != '/')
{ // true for the dir in cwd
getcwd(cwd,sizeof(cwd));
strcat(cwd,"/");
strcat(cwd,path);
chdir(cwd);
即使忽略了常见的缓冲区溢出问题和缺少错误检查,这也是不正确的。如果与此过程相关的目录树在您 getcwd 之后被修改,则您输入了错误的目录(假设 chdir 成功)。此外,包含“..”的路径对符号链接敏感。1
} else { //true for dir w.r.t. /
chdir(pth);
}
printf("Spawned foreground process: %d\n", getpid());
好像是复制粘贴?
return 0;
}