I am trying to create a basic shell for Linux using C. I have gotten it to work until I try to do output redirection and it just destroys everything. When I run this code, i
I added a separate argument pass to capture and remember the I/O redirections and remove them from the arg list passed to the child.
Here's the corrected code [please pardon the gratuitous style cleanup]:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
char *prompt = "% ";
int
main()
{
int pid;
//int child_pid;
char line[81];
char *token;
char *separator = " \t\n";
char **args;
char **args2;
char *cp;
char *ifile;
char *ofile;
int i;
int j;
int err;
//int check;
//char history[90];
//typedef void (*sighandler_t) (int);
args = malloc(80 * sizeof(char *));
args2 = malloc(80 * sizeof(char *));
//signal(SIGINT, SIG_IGN);
while (1) {
fprintf(stderr, "%s", prompt);
fflush(stderr);
if (fgets(line, 80, stdin) == NULL)
break;
// split up the line
i = 0;
while (1) {
token = strtok((i == 0) ? line : NULL, separator);
if (token == NULL)
break;
args[i++] = token; /* build command array */
}
args[i] = NULL;
if (i == 0)
continue;
// assume no redirections
ofile = NULL;
ifile = NULL;
// split off the redirections
j = 0;
i = 0;
err = 0;
while (1) {
cp = args[i++];
if (cp == NULL)
break;
switch (*cp) {
case '<':
if (cp[1] == 0)
cp = args[i++];
else
++cp;
ifile = cp;
if (cp == NULL)
err = 1;
else
if (cp[0] == 0)
err = 1;
break;
case '>':
if (cp[1] == 0)
cp = args[i++];
else
++cp;
ofile = cp;
if (cp == NULL)
err = 1;
else
if (cp[0] == 0)
err = 1;
break;
default:
args2[j++] = cp;
break;
}
}
args2[j] = NULL;
// we got something like "cat <"
if (err)
continue;
// no child arguments
if (j == 0)
continue;
switch (pid = fork()) {
case 0:
// open stdin
if (ifile != NULL) {
int fd = open(ifile, O_RDONLY);
if (dup2(fd, STDIN_FILENO) == -1) {
fprintf(stderr, "dup2 failed");
}
close(fd);
}
// trying to get this to work
// NOTE: now it works :-)
// open stdout
if (ofile != NULL) {
// args[1] = NULL;
int fd2;
//printf("PLEASE WORK");
if ((fd2 = open(ofile, O_WRONLY | O_CREAT, 0644)) < 0) {
perror("couldn't open output file.");
exit(0);
}
// args+=2;
printf("okay");
dup2(fd2, STDOUT_FILENO);
close(fd2);
}
execvp(args2[0], args2); /* child */
signal(SIGINT, SIG_DFL);
fprintf(stderr, "ERROR %s no such program\n", line);
exit(1);
break;
case -1:
/* unlikely but possible if hit a limit */
fprintf(stderr, "ERROR can't create child process!\n");
break;
default:
//printf("am I here");
wait(NULL);
//waitpid(pid, 0, 0);
}
}
exit(0);
}
UPDATE:
If you're still around do you think you could help me with creating a pipe?
Sure. It's too big to post here. See: http://pastebin.com/Ny1w6pUh
Wow did you create all 3300 lines?
Yes.
I borrowed xstr from another SO answer of mine [with bugfix and enhancement]. The dlk was new, but I do many of those, so was easy. Most of it was new code.
But ... It was composed of fragments/concepts I've done before: tgb, FWD, BTV, sysmagic. Notice all struct members for struct foo are prefixed with foo_ [standard for me]. The macro "trickery" with DLHDEF and DLKDEF to simulate inheritance/templates is also something I do [when necessary].
Many function vars reuse my style: idx for index var [I would never use i/j, but rather xidx/yidx], cp for char pointer, cnt for count, len for byte length, etc. Thus, I don't have to "think" about small stuff [tactics] and can focus on strategy.
The above idx et. al. is a "signature style" for me. It's not necessarily better [or worse] than others. It comes from the fact that I started using C when the linker/loader could only handle 8 character symbols, so brevity was key. But, I got used to using the shorter names. Consider which is clearer/better:
for (fooidx = 0; fooidx <= 10; ++fooidx)
Or:
for (indexForFooArray = 0; indexForFooArray <= 10; ++indexForFooArray)
I use the do { ... } while (0) to avoid if/else ladders a lot. This is called a "once through" loop. This is considered "controversial", but, in my experience it keeps the code cleaner. Personally, I've never found a [more standard] use of a do/while loop that can't be done more easily/better with a while or for loop--YMMV. In fact, a number of languages don't even have do/while at all.
Also, I use lower case unless it's a #define [or enum] which is always upper. That is, I use "snake case" (e.g. fooidx) and not "camel hump case" (e.g. indexForFooArray).
The .proto file containing function prototypes is auto-generated. This is a huge time saver. Side note: Be sure you have at least v2 from the external link as there was a bug in the Makefile. A make clean would erase the .proto. v2 won't do that
I've developed my own style over the years. Turns out that the linux kernel style was "borrowed from mine". Not actually :-) Mine came first. But ... They, in parallel, came up with something that is a 99% match to mine: /usr/src/kernels/whatever_version/Documentation/CodingStyle.
Consistency to a given style [one's own] is key. For a given function, you don't have to worry about what you'll name the variables, what indent or blank lines you'll use.
This helps a reader/new developer. They can read a few functions, see the style in play, and then go faster because all functions have similar style.
All this allows you to "go faster" and still get high quality code on the first try. I'm also quite experienced.
Also, my code comments focus on "intent". That is, what do you want the code to do in real world terms. They should answer the "what/why" and the code is the "how".