[英]Creating a shell in C. How would I implement input and output redirection?
I'm creating a shell in C, and I need help implementing input and output redirection. 我正在用C创建外壳,我需要帮助来实现输入和输出重定向。
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;
当我尝试做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.
但是,仍然没有为输出重定向创建新文件。
This is my full code: 这是我的完整代码:
#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);
}
}
}
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 (eg cat somefile > newfile
). 乍一看,除了
close
和dup2
在toks[in]
出现问题外,没有任何明显的东西可以解释为什么在重定向时不创建输出文件(例如cat somefile > newfile
)。 However, there are a number of subtleties that you are not checking. 但是,您没有检查很多细微之处。
For example, you need to check whether your call to open
succeeds in each case before calling dup2
and close
. 例如,您需要在调用
dup2
和close
之前检查每种情况下的open
调用是否成功。 (otherwise, you are attempting to redirect a file-descriptor that is not open). (否则,您尝试重定向未打开的文件描述符)。 Simple basic checking will do, eg
简单的基本检查就可以了,例如
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);
}
( 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
) ( 注意:我已经调整了将文件写为
0644
的权限(用户'rw'
,组'r'
和'r'
。您还应该检查dup2
的返回值,并且在学究的情况下请close
)
The bigger issues come in how you rearrange toks
before your call to execvp
. 更大的问题在于如何在调用
execvp
之前重新排列toks
。 The reason you use dup2
or a pipe is that the exec..
function cannot handle redirection (eg it doesn't know what to do with '>'
or '<'
). 使用
dup2
或管道的原因是exec..
函数无法处理重定向(例如,它不知道如何处理'>'
或'<'
)。 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. 因此,通过将文件重定向到输入案例上的
stdin
或将stdout
(和/或stderr
)重定向到输出案例上的文件,您正在手动处理输入或输出到文件的重定向。 In either case, you must remove the < filename
or > filename
tokens from toks
before calling execvp
or you will get an error. 无论哪种情况,都必须在调用
execvp
之前从toks
删除< filename
或> filename
令牌, toks
会出现错误。
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
. 如果确保将
toks
每个指针都toks
为NULL
并且读取的内容不超过MAXTOKS - 1
(根据需要保留终止NULL
),则可以遍历toks
移动指针,以确保不将< >
和filename
发送给execvp
。 After you find <
or >
in toks
at index i
and insure there is a toks[i+1]
filename, something like: 在索引
i
toks
中找到<
或>
,并确保有toks[i+1]
文件名后,类似:
while (toks[i]) {
toks[idx] = toks[i+2];
i++;
}
Then passing toks
to execvp
will not generate the error (that I suspect is what you are experiencing) 然后将
toks
传递给execvp
不会生成错误(我怀疑这是您遇到的问题)
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
. 如果您的可执行文件具有对
atexit
或其他析构函数的任何已注册调用,则这些引用不是您对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. 因此,如果对
execvp
的调用失败,则无法调用exit
(它可以在对任何后退出函数的调用中调用未定义的行为),因此正确的调用是_exit
,它将不尝试任何此类调用。
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, eg
解析和重定向没有很多其他方面在下面没有解决,但是对于您的基本文件创建问题,它提供了一个框架,例如
#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 */
}
Example Use/Output 使用/输出示例
Minimally working both > filename
and < filename
, 最少使用
> 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
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.
如果这样做不能解决问题,请在MCVE中发布所有代码,以便确保在代码的其他区域不会出现问题。
After Post of Your Complete Code 发布完整代码后
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
, eg 最大的问题是使用
strtok_r
而不删除文件名(或在调用execvp
之前将其设置为NULL
),以及在分配in
和out
使用i + 1
而不是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);
}
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. 使用
i + 1
,将tok[in]
或tok[out]
的索引设置为文件名后一个,提示出现Bad Address
错误。 It's one of those Doah! 这就是这些多哈之一! (or "id10t") errors... (rewrite the quote all-caps)
(或“ id10t”)错误...(改写全大写的引号)
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, eg 此外,在调用
execvp
之前,必须将tok[in]
或tok[out]
为NULL
因为您已删除<
和>
并且文件描述符已被复制,例如
dup2(fd0, 0);
close(fd0);
toks[in] = NULL;
and 和
dup2(fd1, 1);
close(fd1);
toks[out] = NULL;
You had also forgotten to reset your loop variables, eg 您还忘记了重置循环变量,例如
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;
}
}
}
You will need to verify there are no additional issues, but it certainly has no problems with cat file1 > file2
. 您将需要验证没有其他问题,但是
cat file1 > file2
当然没有问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.