[英]Creating a shell in C. How would I implement input and output redirection?
我正在用C创建外壳,我需要帮助来实现输入和输出重定向。
当我尝试使用“>”创建文件时,我收到一条错误消息,指出该文件不存在。 当我尝试做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 = '\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
)。 但是,您没有检查很多细微之处。
例如,您需要在调用dup2
和close
之前检查每种情况下的open
调用是否成功。 (否则,您尝试重定向未打开的文件描述符)。 简单的基本检查就可以了,例如
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
的返回值,并且在学究的情况下请close
)
更大的问题在于如何在调用execvp
之前重新排列toks
。 使用dup2
或管道的原因是exec..
函数无法处理重定向(例如,它不知道如何处理'>'
或'<'
)。 因此,通过将文件重定向到输入案例上的stdin
或将stdout
(和/或stderr
)重定向到输出案例上的文件,您正在手动处理输入或输出到文件的重定向。 无论哪种情况,都必须在调用execvp
之前从toks
删除< filename
或> filename
令牌, toks
会出现错误。
如果确保将toks
每个指针都toks
为NULL
并且读取的内容不超过MAXTOKS - 1
(根据需要保留终止NULL
),则可以遍历toks
移动指针,以确保不将< >
和filename
发送给execvp
。 在索引i
toks
中找到<
或>
,并确保有toks[i+1]
文件名后,类似:
while (toks[i]) {
toks[idx] = toks[i+2];
i++;
}
然后将toks
传递给execvp
不会生成错误(我怀疑这是您遇到的问题)
您还应该注意另一个极端情况。 如果您的可执行文件具有对atexit
或其他析构函数的任何已注册调用,则这些引用不是您对execvp
的调用的一部分。 因此,如果对execvp
的调用失败,则无法调用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 */
}
使用/输出示例
最少使用> 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中发布所有代码,以便确保在代码的其他区域不会出现问题。
发布完整代码后
最大的问题是使用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);
}
使用i + 1
,将tok[in]
或tok[out]
的索引设置为文件名后一个,提示出现Bad Address
错误。 这就是这些多哈之一! (或“ id10t”)错误...(改写全大写的引号)
此外,在调用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
当然没有问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.