在C中创建外壳.如何实现输入和输出重定向?
我正在用C创建外壳程序,我需要实现输入和输出重定向的帮助.
I'm creating a shell in C, and I need help implementing input and output redirection.
当我尝试使用>"创建文件时,我收到一条错误消息,指出该文件不存在.当我尝试做ls> test.txt之类的东西时;它不会创建一个新文件.
When I try to create a file using ">" I get an error message saying the file does not exist. When I try to do something like ls > test.txt; it won't create a new file.
我使用提供给我的建议更新了代码,但是现在我遇到了不同的错误.但是,仍然没有为输出重定向创建新文件.
I updated the code with the suggestions provided to me, but now I got different errors. However, a new file is still not created for the output redirection.
这是我的完整代码:
#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 = '\0';
}
// 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
).但是,您没有检查很多细微之处.
On first glance, other than your close
and dup2
being out of order in your toks[in]
case, there isn't anything readily apparent that explains why you do not create an output file when redirecting (e.g. cat somefile > newfile
). However, there are a number of subtleties that you are not checking.
例如,您需要在调用dup2
和close
之前先检查对open
的调用是否成功. (否则,您尝试重定向未打开的文件描述符).简单的基本检查即可,例如
For example, you need to check whether your call to open
succeeds in each case before calling dup2
and close
. (otherwise, you are attempting to redirect a file-descriptor that is not open). Simple basic checking will do, e.g.
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'
和world 'r'
.您还应该检查以下内容的返回值: dup2
,而在学究情况下是close
)
(note: I've tweaked the permission to write the file as 0644
(user 'rw'
, group 'r'
and world 'r'
. You should also check the returns of dup2
and in the pedantic case close
)
更大的问题出在调用execvp
之前如何重新排列toks
.使用dup2
或管道的原因是exec..
函数无法处理重定向(例如,它不知道如何使用'>'
或'<'
).因此,您可以通过将输入案例中的文件重定向到stdin
或将输出案例中的stdout
(和/或stderr
)重定向到文件来手动处理输入或输出到文件的重定向.无论哪种情况,都必须在调用execvp
之前从toks
中删除< filename
或> filename
令牌,否则会出现错误.
The bigger issues come in how you rearrange toks
before your call to execvp
. The reason you use dup2
or a pipe is that the exec..
function cannot handle redirection (e.g. it doesn't know what to do with '>'
or '<'
). So you are manually handling the redirection of input or output to a file by redirecting either the file to stdin
on the input case or stdout
(and/or stderr
) to the file on the output case. In either case, you must remove the < filename
or > filename
tokens from toks
before calling execvp
or you will get an error.
如果确保将toks
中的每个指针设置为NULL
,并且读取的内容不超过MAXTOKS - 1
(根据需要保留终止的NULL
),则可以遍历toks
,将指针移至确保您不将< >
和filename
发送到execvp
.在索引i
的toks
中找到<
或>
并确保有toks[i+1]
文件名后,类似以下内容:
If you insure that set each pointer in toks
to NULL
and you read no more than MAXTOKS - 1
(preserving a terminating NULL
as required), then you can iterate over toks
shifting the pointers to insure you do not send the < >
and filename
to execvp
. After you find <
or >
in toks
at index i
and insure there is a toks[i+1]
filename, something like:
while (toks[i]) {
toks[idx] = toks[i+2];
i++;
}
然后将toks
传递给execvp
不会生成错误(我怀疑是您遇到的错误)
Then passing toks
to execvp
will not generate the error (that I suspect is what you are experiencing)
您还应该注意另一个极端情况.如果您的可执行文件已注册了对atexit
或其他描述符的任何调用,则这些引用不是您对execvp
的调用的一部分.因此,如果对execvp
的调用失败,则无法调用exit
(它可以在对任何退出后函数的调用中调用未定义的行为),因此正确的调用是对_exit
的调用,而不会尝试任何此类调用.
There is also another corner-case nit you should be aware of. If your executable has any registered calls to atexit
or other desctructors, the references are not part of your call to execvp
. So if the call to execvp
fails, you cannot call exit
(which can invoke undefined behavior in a call to any post-exit function), so the proper call is to _exit
which will not attempt any such calls.
最低限度的工作重定向将类似于以下内容.解析和重定向没有很多其他方面在下面没有解决,但是对于您的基本文件创建问题,它提供了一个框架,例如
A bare minimum of the working redirection would be something like the following. Not there are many other aspects of parsing and redirection not addressed below, but for your basic file creation problem, it provides a framework, e.g.
#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 */
}
使用/输出示例
最小化> 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
让我知道这是否可以解决错误问题.唯一的其他创建问题是您在尝试创建文件的地方没有写权限.如果这样做不能解决问题,请在MCVE中发布所有代码,以便确保在代码的其他区域不会出现问题.
Let me know if this solves the error issue. The only other creation issue would be you don't have write permission where you are attempting to create the file. If this doesn't solve the issue, please post all your code in a MCVE so I can insure that problems are not created in other areas of the code.
发布完整代码后
最大的问题是使用strtok_r
而不删除文件名(或在调用execvp
之前将其设置为NULL
),以及在分配in
时使用i + 1
而不是i
>和out
,例如
Your biggest issue was in your use of strtok_r
and not removing the filename (or setting it NULL
before calling execvp
), and in using i + 1
instead of i
in your assignment to in
and out
, e.g.
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")错误之一(重写全大写的引号)
When you used i + 1
, you set the index for tok[in]
or tok[out]
to one past the filename prompting the Bad Address
error. It's one of those Doah! (or "id10t") errors... (rewrite the quote all-caps)
此外,在调用execvp
之前,必须将tok[in]
或tok[out]
设置为NULL
,因为您已经删除了<
和>
并且文件描述符已经被复制,例如>
Further, before your call to execvp
you must set tok[in]
or tok[out]
to NULL
as you have removed the <
and >
and the file descriptor has already been duped, e.g.
dup2(fd0, 0);
close(fd0);
toks[in] = NULL;
和
dup2(fd1, 1);
close(fd1);
toks[out] = NULL;
您还忘记了重置循环变量,例如
You had also forgotten to reset your loop variables, e.g.
while (1)
{
in = out = 0; /* always reset loop variables */
for (int i = 0; i < MAX_TOKS; i++)
toks[i] = NULL; /* and NULL all pointers */
清理一下自己的工作后,可以执行以下操作:
Cleaning what you had up a bit, you could do something like the following:
#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
当然没有问题.
You will need to verify there are no additional issues, but it certainly has no problems with cat file1 > file2
.