在 C 中创建 shell。我将如何实现输入和输出重定向?
Creating a shell in C. How would I implement input and output redirection?
我正在用 C 语言创建一个 shell,我需要帮助来实现输入和输出重定向。
当我尝试使用“>”创建文件时,我收到一条错误消息,指出该文件不存在。当我尝试做类似 ls > test.txt; 的事情时它不会创建新文件。
我根据提供给我的建议更新了代码,但现在出现了不同的错误。但是,仍未为输出重定向创建新文件。
这是我的完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#define MAX_BUF 160
#define MAX_TOKS 100
int main(int argc, char **argv)
{
char *pos;
char *tok;
char *path;
char s[MAX_BUF];
char *toks[MAX_TOKS];
time_t rawtime;
struct tm *timeinfo;
static const char prompt[] = "msh> ";
FILE *infile;
int in;
int out;
int fd0;
int fd1;
in = 0;
out = 0;
/*
* process command line options
*/
if (argc > 2) {
fprintf(stderr, "msh: usage: msh [file]\n");
exit(EXIT_FAILURE);
}
if (argc == 2) {
/* read from script supplied on the command line */
infile = fopen(argv[1], "r");
if (infile == NULL)
{
fprintf(stderr, "msh: cannot open script '%s'.\n", argv[1]);
exit(EXIT_FAILURE);
}
} else {
infile = stdin;
}
while (1)
{
// prompt for input, if interactive input
if (infile == stdin) {
printf(prompt);
}
/*
* read a line of input and break it into tokens
*/
// read input
char *status = fgets(s, MAX_BUF-1, infile);
// exit if ^d or "exit" entered
if (status == NULL || strcmp(s, "exit\n") == 0) {
if (status == NULL && infile == stdin) {
printf("\n");
}
exit(EXIT_SUCCESS);
}
// remove any trailing newline
if ((pos = strchr(s, '\n')) != NULL) {
*pos = '[=11=]';
}
// break input line into tokens
char *rest = s;
int i = 0;
while((tok = strtok_r(rest, " ", &rest)) != NULL && i < MAX_TOKS)
{
toks[i] = tok;
if(strcmp(tok, "<") == 0)
{
in = i + 1;
i--;
}
else if(strcmp(tok, ">")==0)
{
out = i + 1;
i--;
}
i++;
}
if (i == MAX_TOKS) {
fprintf(stderr, "msh: too many tokens");
exit(EXIT_FAILURE);
}
toks[i] = NULL;
/*
* process a command
*/
// do nothing if no tokens found in input
if (i == 0) {
continue;
}
// if a shell built-in command, then run it
if (strcmp(toks[0], "help") == 0) {
// help
printf("enter a Linux command, or 'exit' to quit\n");
continue;
}
if (strcmp(toks[0], "today") == 0) {
// today
time(&rawtime);
timeinfo = localtime(&rawtime);
printf("Current local time: %s", asctime(timeinfo));
continue;
}
if (strcmp(toks[0], "cd") == 0)
{
// cd
if (i == 1) {
path = getenv("HOME");
} else {
path = toks[1];
}
int cd_status = chdir(path);
if (cd_status != 0)
{
switch(cd_status)
{
case ENOENT:
printf("msh: cd: '%s' does not exist\n", path);
break;
case ENOTDIR:
printf("msh: cd: '%s' not a directory\n", path);
break;
default:
printf("msh: cd: bad path\n");
}
}
continue;
}
// not a built-in, so fork a process that will run the command
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "msh: fork failed\n");
exit(1);
}
if (rc == 0)
{
if(in)
{
int fd0;
if((fd0 = open(toks[in], O_RDONLY, 0)) == -1)
{
perror(toks[in]);
exit(EXIT_FAILURE);
}
dup2(fd0, 0);
close(fd0);
}
if(out)
{
int fd1;
if((fd1 = open(toks[out], O_WRONLY | O_CREAT | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
{
perror (toks[out]);
exit( EXIT_FAILURE);
}
dup2(fd1, 1);
close(fd1);
}
// child process: run the command indicated by toks[0]
execvp(toks[0], toks);
/* if execvp returns than an error occurred */
printf("msh: %s: %s\n", toks[0], strerror(errno));
exit(1);
}
else
{
// parent process: wait for child to terminate
wait(NULL);
}
}
}
乍一看,除了您的 close
和 dup2
在您的 toks[in]
案例中出现问题外,没有任何显而易见的东西可以解释您为什么不创建重定向时的输出文件(例如 cat somefile > newfile
)。但是,有许多细节您没有检查。
例如,在调用 dup2
和 close
之前,您需要检查对 open
的调用是否成功。 (否则,您正试图重定向未打开的 file-descriptor)。简单的基本检查就可以了,例如
if (in) {
int fd0;
if ((fd0 = open(toks[in], O_RDONLY)) == -1) {
perror (toks[in]);
exit (EXIT_FAILURE);
}
dup2(fd0, 0);
close(fd0);
}
if (out)
{
int fd1;
if ((fd1 = open(toks[out],
O_WRONLY | O_CREAT | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
perror (toks[out]);
exit (EXIT_FAILURE);
}
dup2(fd1, 1);
close(fd1);
}
(注意: 我调整了将文件写入 0644
的权限(用户 'rw'
、组 'r'
和世界'r'
。你还应该检查 dup2
的 returns 和迂腐的 close
)
更大的问题在于如何在调用 execvp
之前重新排列 toks
。您使用 dup2
或管道的原因是 exec..
函数无法处理重定向(例如,它不知道如何处理 '>'
或 '<'
)。因此,您通过将文件重定向到输入案例上的 stdin
或将 stdout
(and/or stderr
) 重定向到文件来手动处理输入或输出到文件的重定向在输出情况下。在任何一种情况下,您都必须在调用 execvp
之前从 toks
中删除 < filename
或 > filename
标记,否则您将收到错误消息。
如果您确保将 toks
中的每个指针设置为 NULL
并且您读取的内容不超过 MAXTOKS - 1
(根据需要保留终止 NULL
),那么您可以迭代 toks
移动指针以确保您不会将 < >
和 filename
发送到 execvp
。在索引 i
处的 toks
中找到 <
或 >
并确保有一个 toks[i+1]
文件名后,类似于:
while (toks[i]) {
toks[idx] = toks[i+2];
i++;
}
然后将 toks
传递给 execvp
将不会产生错误(我怀疑是您遇到的错误)
还有一个 corner-case 您应该注意的问题。如果您的可执行文件有任何已注册的对 atexit
或其他析构函数的调用,则这些引用不是您对 execvp
的调用的一部分。因此,如果对 execvp
的调用失败,则无法调用 exit
(它可以在对任何 post-exit 函数的调用中调用未定义的行为),因此正确的调用是 _exit
它不会尝试任何此类调用。
工作重定向的最低限度类似于以下内容。不是下面没有解决解析和重定向的许多其他方面,但是对于您的基本文件创建问题,它提供了一个框架,例如
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
enum {ARGSIZE = 20, BUF_SIZE = 1024}; /* constants */
void execute (char **args);
int main (void) {
while (1) {
char line[BUF_SIZE] = "",
*args[ARGSIZE],
*delim = " \n",
*token;
int argIndex = 0;
for (int i = 0; i < ARGSIZE; i++) /* set all pointers NULL */
args[i] = NULL;
printf ("shell> "); /* prompt */
if (!fgets (line, BUF_SIZE, stdin)) {
fprintf (stderr, "Input canceled - EOF received.\n");
return 0;
}
if (*line == '\n') /* Enter alone - empty line */
continue;
for (token = strtok (line, delim); /* parse tokens */
token && argIndex + 1 < ARGSIZE;
token = strtok (NULL, delim)) {
args[argIndex++] = token;
}
if (!argIndex) continue; /* validate at least 1 arg */
if (strcmp (args[0], "quit") == 0 || strcmp (args[0], "exit") == 0)
break;
execute (args); /* call function to fork / execvp */
}
return 0;
}
void execute (char **args)
{
pid_t pid, status;
pid = fork ();
if (pid < 0) {
perror ("fork");
return;
}
else if (pid > 0) {
while (wait (&status) != pid)
continue;
}
else if (pid == 0) {
int idx = 0,
fd;
while (args[idx]) { /* parse args for '<' or '>' and filename */
if (*args[idx] == '>' && args[idx+1]) {
if ((fd = open (args[idx+1],
O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
perror (args[idx+1]);
exit (EXIT_FAILURE);
}
dup2 (fd, 1);
dup2 (fd, 2);
close (fd);
while (args[idx]) {
args[idx] = args[idx+2];
idx++;
}
break;
}
else if (*args[idx] == '<' && args[idx+1]) {
if ((fd = open (args[idx+1], O_RDONLY)) == -1) {
perror (args[idx+1]);
exit (EXIT_FAILURE);
}
dup2 (fd, 0);
close (fd);
while (args[idx]) {
args[idx] = args[idx+2];
idx++;
}
break;
}
idx++;
}
if (execvp (args[0], args) == -1) {
perror ("execvp");
}
_exit (EXIT_FAILURE); /* must _exit after execvp return, otherwise */
} /* any atext calls invoke undefine behavior */
}
示例Use/Output
最低限度地工作 > filename
和 < filename
,
$ ./bin/execvp_wredirect
shell> ls -al tmp.txt
ls: cannot access 'tmp.txt': No such file or directory
shell> cat dog.txt
my dog has fleas
shell> cat dog.txt > tmp.txt
shell> ls -al tmp.txt
-rw-r--r-- 1 david david 17 Feb 25 01:52 tmp.txt
shell> cat < tmp.txt
my dog has fleas
shell> quit
让我知道这是否解决了错误问题。唯一的其他创建问题是您在尝试创建文件时没有写权限。如果这不能解决问题,请 post MCVE 中的所有代码,这样我可以确保代码的其他区域不会产生问题。
在您的完整代码Post之后
您最大的问题是您使用了 strtok_r
而没有删除文件名(或在调用 execvp
之前将其设置为 NULL
),并且使用了 i + 1
分配给 in
和 out
的 i
,例如
tok = strtok_r(rest, delim, &rest);
while(tok != NULL && i < MAX_TOKS)
{
toks[i] = tok;
if(strcmp(tok, "<") == 0)
{
in = i;
i--;
}
else if(strcmp(tok, ">")==0)
{
out = i;
i--;
}
i++;
tok = strtok_r(NULL, delim, &rest);
}
当您使用 i + 1
时,您将 tok[in]
或 tok[out]
的索引设置为 文件名后的索引 提示 Bad Address
错误。这是其中一个 Doah!(或 "id10t")错误...(重写引用 all-caps)
此外,在调用 execvp
之前,您必须将 tok[in]
或 tok[out]
设置为 NULL
,因为您已经删除了 <
和 >
并且文件描述符已经被欺骗,例如
dup2(fd0, 0);
close(fd0);
toks[in] = NULL;
和
dup2(fd1, 1);
close(fd1);
toks[out] = NULL;
您还忘记了重置循环变量,例如
while (1)
{
in = out = 0; /* always reset loop variables */
for (int i = 0; i < MAX_TOKS; i++)
toks[i] = NULL; /* and NULL all pointers */
稍微清理一下,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h> /* missing headers */
#include <sys/wait.h>
#define MAX_BUF 160
#define MAX_TOKS 100
int main(int argc, char **argv)
{
char *delim = " \n"; /* delimiters for strtok_r (including \n) */
// char *pos; /* no longer used */
char *tok;
char *path;
char s[MAX_BUF];
char *toks[MAX_TOKS];
time_t rawtime;
struct tm *timeinfo;
static const char prompt[] = "msh> ";
FILE *infile;
int in;
int out;
// int fd0; /* unused and shadowed declarations below */
// int fd1; /* always compile with -Wshadow */
in = 0;
out = 0;
/*
* process command line options
*/
if (argc > 2) {
fprintf(stderr, "msh: usage: msh [file]\n");
exit(EXIT_FAILURE);
}
if (argc == 2) {
/* read from script supplied on the command line */
infile = fopen(argv[1], "r");
if (infile == NULL) {
fprintf(stderr, "msh: cannot open script '%s'.\n", argv[1]);
exit(EXIT_FAILURE);
}
} else {
infile = stdin;
}
while (1)
{
in = out = 0; /* always reset loop variables */
for (int i = 0; i < MAX_TOKS; i++)
toks[i] = NULL;
// prompt for input, if interactive input
if (infile == stdin) {
printf(prompt);
}
/*
* read a line of input and break it into tokens
*/
// read input
char *status = fgets(s, MAX_BUF-1, infile);
// exit if ^d or "exit" entered
if (status == NULL || strcmp(s, "exit\n") == 0) {
if (status == NULL && infile == stdin) {
printf("\n");
}
exit(EXIT_SUCCESS);
}
// break input line into tokens
char *rest = s;
int i = 0;
tok = strtok_r(rest, delim, &rest);
while(tok != NULL && i < MAX_TOKS)
{
toks[i] = tok;
if(strcmp(tok, "<") == 0)
{
in = i; /* only i, not i + 1, you follow with i-- */
i--;
}
else if(strcmp(tok, ">")==0)
{
out = i; /* only i, not i + 1, you follow with i-- */
i--;
}
i++;
tok = strtok_r(NULL, delim, &rest);
}
if (i == MAX_TOKS) {
fprintf(stderr, "msh: too many tokens");
exit(EXIT_FAILURE);
}
toks[i] = NULL;
/*
* process a command
*/
// do nothing if no tokens found in input
if (i == 0) {
continue;
}
// if a shell built-in command, then run it
if (strcmp(toks[0], "help") == 0) {
// help
printf("enter a Linux command, or 'exit' to quit\n");
continue;
}
if (strcmp(toks[0], "today") == 0) {
// today
time(&rawtime);
timeinfo = localtime(&rawtime);
printf("Current local time: %s", asctime(timeinfo));
continue;
}
if (strcmp(toks[0], "cd") == 0)
{
// cd
if (i == 1) {
path = getenv("HOME");
} else {
path = toks[1];
}
int cd_status = chdir(path);
if (cd_status != 0)
{
switch(cd_status)
{
case ENOENT:
printf("msh: cd: '%s' does not exist\n", path);
break;
case ENOTDIR:
printf("msh: cd: '%s' not a directory\n", path);
break;
default:
printf("msh: cd: bad path\n");
}
}
continue;
}
// not a built-in, so fork a process that will run the command
pid_t rc = fork(), rcstatus; /* use type pid_t, not int */
if (rc < 0)
{
fprintf(stderr, "msh: fork failed\n");
exit(1);
}
if (rc == 0)
{
if(in)
{
int fd0;
if((fd0 = open(toks[in], O_RDONLY, 0)) == -1)
{
perror(toks[in]);
exit(EXIT_FAILURE);
}
dup2(fd0, 0);
close(fd0);
toks[in] = NULL;
}
if(out)
{
int fd1;
if((fd1 = open(toks[out], O_WRONLY | O_CREAT | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
{
perror (toks[out]);
exit( EXIT_FAILURE);
}
dup2(fd1, 1);
close(fd1);
toks[out] = NULL;
}
// child process: run the command indicated by toks[0]
execvp(toks[0], toks);
/* if execvp returns than an error occurred */
printf("msh: %s: %s\n", toks[0], strerror(errno));
exit(1);
}
else
{
// parent process: wait for child to terminate
while (wait (&rcstatus) != rc)
continue;
}
}
}
您需要确认没有其他问题,但 cat file1 > file2
.
肯定没有问题
我正在用 C 语言创建一个 shell,我需要帮助来实现输入和输出重定向。
当我尝试使用“>”创建文件时,我收到一条错误消息,指出该文件不存在。当我尝试做类似 ls > test.txt; 的事情时它不会创建新文件。
我根据提供给我的建议更新了代码,但现在出现了不同的错误。但是,仍未为输出重定向创建新文件。
这是我的完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#define MAX_BUF 160
#define MAX_TOKS 100
int main(int argc, char **argv)
{
char *pos;
char *tok;
char *path;
char s[MAX_BUF];
char *toks[MAX_TOKS];
time_t rawtime;
struct tm *timeinfo;
static const char prompt[] = "msh> ";
FILE *infile;
int in;
int out;
int fd0;
int fd1;
in = 0;
out = 0;
/*
* process command line options
*/
if (argc > 2) {
fprintf(stderr, "msh: usage: msh [file]\n");
exit(EXIT_FAILURE);
}
if (argc == 2) {
/* read from script supplied on the command line */
infile = fopen(argv[1], "r");
if (infile == NULL)
{
fprintf(stderr, "msh: cannot open script '%s'.\n", argv[1]);
exit(EXIT_FAILURE);
}
} else {
infile = stdin;
}
while (1)
{
// prompt for input, if interactive input
if (infile == stdin) {
printf(prompt);
}
/*
* read a line of input and break it into tokens
*/
// read input
char *status = fgets(s, MAX_BUF-1, infile);
// exit if ^d or "exit" entered
if (status == NULL || strcmp(s, "exit\n") == 0) {
if (status == NULL && infile == stdin) {
printf("\n");
}
exit(EXIT_SUCCESS);
}
// remove any trailing newline
if ((pos = strchr(s, '\n')) != NULL) {
*pos = '[=11=]';
}
// break input line into tokens
char *rest = s;
int i = 0;
while((tok = strtok_r(rest, " ", &rest)) != NULL && i < MAX_TOKS)
{
toks[i] = tok;
if(strcmp(tok, "<") == 0)
{
in = i + 1;
i--;
}
else if(strcmp(tok, ">")==0)
{
out = i + 1;
i--;
}
i++;
}
if (i == MAX_TOKS) {
fprintf(stderr, "msh: too many tokens");
exit(EXIT_FAILURE);
}
toks[i] = NULL;
/*
* process a command
*/
// do nothing if no tokens found in input
if (i == 0) {
continue;
}
// if a shell built-in command, then run it
if (strcmp(toks[0], "help") == 0) {
// help
printf("enter a Linux command, or 'exit' to quit\n");
continue;
}
if (strcmp(toks[0], "today") == 0) {
// today
time(&rawtime);
timeinfo = localtime(&rawtime);
printf("Current local time: %s", asctime(timeinfo));
continue;
}
if (strcmp(toks[0], "cd") == 0)
{
// cd
if (i == 1) {
path = getenv("HOME");
} else {
path = toks[1];
}
int cd_status = chdir(path);
if (cd_status != 0)
{
switch(cd_status)
{
case ENOENT:
printf("msh: cd: '%s' does not exist\n", path);
break;
case ENOTDIR:
printf("msh: cd: '%s' not a directory\n", path);
break;
default:
printf("msh: cd: bad path\n");
}
}
continue;
}
// not a built-in, so fork a process that will run the command
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "msh: fork failed\n");
exit(1);
}
if (rc == 0)
{
if(in)
{
int fd0;
if((fd0 = open(toks[in], O_RDONLY, 0)) == -1)
{
perror(toks[in]);
exit(EXIT_FAILURE);
}
dup2(fd0, 0);
close(fd0);
}
if(out)
{
int fd1;
if((fd1 = open(toks[out], O_WRONLY | O_CREAT | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
{
perror (toks[out]);
exit( EXIT_FAILURE);
}
dup2(fd1, 1);
close(fd1);
}
// child process: run the command indicated by toks[0]
execvp(toks[0], toks);
/* if execvp returns than an error occurred */
printf("msh: %s: %s\n", toks[0], strerror(errno));
exit(1);
}
else
{
// parent process: wait for child to terminate
wait(NULL);
}
}
}
乍一看,除了您的 close
和 dup2
在您的 toks[in]
案例中出现问题外,没有任何显而易见的东西可以解释您为什么不创建重定向时的输出文件(例如 cat somefile > newfile
)。但是,有许多细节您没有检查。
例如,在调用 dup2
和 close
之前,您需要检查对 open
的调用是否成功。 (否则,您正试图重定向未打开的 file-descriptor)。简单的基本检查就可以了,例如
if (in) {
int fd0;
if ((fd0 = open(toks[in], O_RDONLY)) == -1) {
perror (toks[in]);
exit (EXIT_FAILURE);
}
dup2(fd0, 0);
close(fd0);
}
if (out)
{
int fd1;
if ((fd1 = open(toks[out],
O_WRONLY | O_CREAT | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
perror (toks[out]);
exit (EXIT_FAILURE);
}
dup2(fd1, 1);
close(fd1);
}
(注意: 我调整了将文件写入 0644
的权限(用户 'rw'
、组 'r'
和世界'r'
。你还应该检查 dup2
的 returns 和迂腐的 close
)
更大的问题在于如何在调用 execvp
之前重新排列 toks
。您使用 dup2
或管道的原因是 exec..
函数无法处理重定向(例如,它不知道如何处理 '>'
或 '<'
)。因此,您通过将文件重定向到输入案例上的 stdin
或将 stdout
(and/or stderr
) 重定向到文件来手动处理输入或输出到文件的重定向在输出情况下。在任何一种情况下,您都必须在调用 execvp
之前从 toks
中删除 < filename
或 > filename
标记,否则您将收到错误消息。
如果您确保将 toks
中的每个指针设置为 NULL
并且您读取的内容不超过 MAXTOKS - 1
(根据需要保留终止 NULL
),那么您可以迭代 toks
移动指针以确保您不会将 < >
和 filename
发送到 execvp
。在索引 i
处的 toks
中找到 <
或 >
并确保有一个 toks[i+1]
文件名后,类似于:
while (toks[i]) {
toks[idx] = toks[i+2];
i++;
}
然后将 toks
传递给 execvp
将不会产生错误(我怀疑是您遇到的错误)
还有一个 corner-case 您应该注意的问题。如果您的可执行文件有任何已注册的对 atexit
或其他析构函数的调用,则这些引用不是您对 execvp
的调用的一部分。因此,如果对 execvp
的调用失败,则无法调用 exit
(它可以在对任何 post-exit 函数的调用中调用未定义的行为),因此正确的调用是 _exit
它不会尝试任何此类调用。
工作重定向的最低限度类似于以下内容。不是下面没有解决解析和重定向的许多其他方面,但是对于您的基本文件创建问题,它提供了一个框架,例如
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
enum {ARGSIZE = 20, BUF_SIZE = 1024}; /* constants */
void execute (char **args);
int main (void) {
while (1) {
char line[BUF_SIZE] = "",
*args[ARGSIZE],
*delim = " \n",
*token;
int argIndex = 0;
for (int i = 0; i < ARGSIZE; i++) /* set all pointers NULL */
args[i] = NULL;
printf ("shell> "); /* prompt */
if (!fgets (line, BUF_SIZE, stdin)) {
fprintf (stderr, "Input canceled - EOF received.\n");
return 0;
}
if (*line == '\n') /* Enter alone - empty line */
continue;
for (token = strtok (line, delim); /* parse tokens */
token && argIndex + 1 < ARGSIZE;
token = strtok (NULL, delim)) {
args[argIndex++] = token;
}
if (!argIndex) continue; /* validate at least 1 arg */
if (strcmp (args[0], "quit") == 0 || strcmp (args[0], "exit") == 0)
break;
execute (args); /* call function to fork / execvp */
}
return 0;
}
void execute (char **args)
{
pid_t pid, status;
pid = fork ();
if (pid < 0) {
perror ("fork");
return;
}
else if (pid > 0) {
while (wait (&status) != pid)
continue;
}
else if (pid == 0) {
int idx = 0,
fd;
while (args[idx]) { /* parse args for '<' or '>' and filename */
if (*args[idx] == '>' && args[idx+1]) {
if ((fd = open (args[idx+1],
O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) {
perror (args[idx+1]);
exit (EXIT_FAILURE);
}
dup2 (fd, 1);
dup2 (fd, 2);
close (fd);
while (args[idx]) {
args[idx] = args[idx+2];
idx++;
}
break;
}
else if (*args[idx] == '<' && args[idx+1]) {
if ((fd = open (args[idx+1], O_RDONLY)) == -1) {
perror (args[idx+1]);
exit (EXIT_FAILURE);
}
dup2 (fd, 0);
close (fd);
while (args[idx]) {
args[idx] = args[idx+2];
idx++;
}
break;
}
idx++;
}
if (execvp (args[0], args) == -1) {
perror ("execvp");
}
_exit (EXIT_FAILURE); /* must _exit after execvp return, otherwise */
} /* any atext calls invoke undefine behavior */
}
示例Use/Output
最低限度地工作 > filename
和 < filename
,
$ ./bin/execvp_wredirect
shell> ls -al tmp.txt
ls: cannot access 'tmp.txt': No such file or directory
shell> cat dog.txt
my dog has fleas
shell> cat dog.txt > tmp.txt
shell> ls -al tmp.txt
-rw-r--r-- 1 david david 17 Feb 25 01:52 tmp.txt
shell> cat < tmp.txt
my dog has fleas
shell> quit
让我知道这是否解决了错误问题。唯一的其他创建问题是您在尝试创建文件时没有写权限。如果这不能解决问题,请 post MCVE 中的所有代码,这样我可以确保代码的其他区域不会产生问题。
在您的完整代码Post之后
您最大的问题是您使用了 strtok_r
而没有删除文件名(或在调用 execvp
之前将其设置为 NULL
),并且使用了 i + 1
分配给 in
和 out
的 i
,例如
tok = strtok_r(rest, delim, &rest);
while(tok != NULL && i < MAX_TOKS)
{
toks[i] = tok;
if(strcmp(tok, "<") == 0)
{
in = i;
i--;
}
else if(strcmp(tok, ">")==0)
{
out = i;
i--;
}
i++;
tok = strtok_r(NULL, delim, &rest);
}
当您使用 i + 1
时,您将 tok[in]
或 tok[out]
的索引设置为 文件名后的索引 提示 Bad Address
错误。这是其中一个 Doah!(或 "id10t")错误...(重写引用 all-caps)
此外,在调用 execvp
之前,您必须将 tok[in]
或 tok[out]
设置为 NULL
,因为您已经删除了 <
和 >
并且文件描述符已经被欺骗,例如
dup2(fd0, 0);
close(fd0);
toks[in] = NULL;
和
dup2(fd1, 1);
close(fd1);
toks[out] = NULL;
您还忘记了重置循环变量,例如
while (1)
{
in = out = 0; /* always reset loop variables */
for (int i = 0; i < MAX_TOKS; i++)
toks[i] = NULL; /* and NULL all pointers */
稍微清理一下,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h> /* missing headers */
#include <sys/wait.h>
#define MAX_BUF 160
#define MAX_TOKS 100
int main(int argc, char **argv)
{
char *delim = " \n"; /* delimiters for strtok_r (including \n) */
// char *pos; /* no longer used */
char *tok;
char *path;
char s[MAX_BUF];
char *toks[MAX_TOKS];
time_t rawtime;
struct tm *timeinfo;
static const char prompt[] = "msh> ";
FILE *infile;
int in;
int out;
// int fd0; /* unused and shadowed declarations below */
// int fd1; /* always compile with -Wshadow */
in = 0;
out = 0;
/*
* process command line options
*/
if (argc > 2) {
fprintf(stderr, "msh: usage: msh [file]\n");
exit(EXIT_FAILURE);
}
if (argc == 2) {
/* read from script supplied on the command line */
infile = fopen(argv[1], "r");
if (infile == NULL) {
fprintf(stderr, "msh: cannot open script '%s'.\n", argv[1]);
exit(EXIT_FAILURE);
}
} else {
infile = stdin;
}
while (1)
{
in = out = 0; /* always reset loop variables */
for (int i = 0; i < MAX_TOKS; i++)
toks[i] = NULL;
// prompt for input, if interactive input
if (infile == stdin) {
printf(prompt);
}
/*
* read a line of input and break it into tokens
*/
// read input
char *status = fgets(s, MAX_BUF-1, infile);
// exit if ^d or "exit" entered
if (status == NULL || strcmp(s, "exit\n") == 0) {
if (status == NULL && infile == stdin) {
printf("\n");
}
exit(EXIT_SUCCESS);
}
// break input line into tokens
char *rest = s;
int i = 0;
tok = strtok_r(rest, delim, &rest);
while(tok != NULL && i < MAX_TOKS)
{
toks[i] = tok;
if(strcmp(tok, "<") == 0)
{
in = i; /* only i, not i + 1, you follow with i-- */
i--;
}
else if(strcmp(tok, ">")==0)
{
out = i; /* only i, not i + 1, you follow with i-- */
i--;
}
i++;
tok = strtok_r(NULL, delim, &rest);
}
if (i == MAX_TOKS) {
fprintf(stderr, "msh: too many tokens");
exit(EXIT_FAILURE);
}
toks[i] = NULL;
/*
* process a command
*/
// do nothing if no tokens found in input
if (i == 0) {
continue;
}
// if a shell built-in command, then run it
if (strcmp(toks[0], "help") == 0) {
// help
printf("enter a Linux command, or 'exit' to quit\n");
continue;
}
if (strcmp(toks[0], "today") == 0) {
// today
time(&rawtime);
timeinfo = localtime(&rawtime);
printf("Current local time: %s", asctime(timeinfo));
continue;
}
if (strcmp(toks[0], "cd") == 0)
{
// cd
if (i == 1) {
path = getenv("HOME");
} else {
path = toks[1];
}
int cd_status = chdir(path);
if (cd_status != 0)
{
switch(cd_status)
{
case ENOENT:
printf("msh: cd: '%s' does not exist\n", path);
break;
case ENOTDIR:
printf("msh: cd: '%s' not a directory\n", path);
break;
default:
printf("msh: cd: bad path\n");
}
}
continue;
}
// not a built-in, so fork a process that will run the command
pid_t rc = fork(), rcstatus; /* use type pid_t, not int */
if (rc < 0)
{
fprintf(stderr, "msh: fork failed\n");
exit(1);
}
if (rc == 0)
{
if(in)
{
int fd0;
if((fd0 = open(toks[in], O_RDONLY, 0)) == -1)
{
perror(toks[in]);
exit(EXIT_FAILURE);
}
dup2(fd0, 0);
close(fd0);
toks[in] = NULL;
}
if(out)
{
int fd1;
if((fd1 = open(toks[out], O_WRONLY | O_CREAT | O_TRUNC | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
{
perror (toks[out]);
exit( EXIT_FAILURE);
}
dup2(fd1, 1);
close(fd1);
toks[out] = NULL;
}
// child process: run the command indicated by toks[0]
execvp(toks[0], toks);
/* if execvp returns than an error occurred */
printf("msh: %s: %s\n", toks[0], strerror(errno));
exit(1);
}
else
{
// parent process: wait for child to terminate
while (wait (&rcstatus) != rc)
continue;
}
}
}
您需要确认没有其他问题,但 cat file1 > file2
.