Creating a Unix shell and dealing with pipes

duragezic

Lifer
Oct 11, 1999
11,234
4
81
For one of my classes, I have to create a shell. There is some small restrictions and modifications that make it a little different than a standard shell, but those aren't an issue for me.

For another class last semester, our first assignment was to create a simple shell. So some of that code was helpful for doing this assignment. Mainly, I used code from it to parse a line of input into a command and its arguments.

So entering:

ls -a -l (or ls -al both work ok)

would store "ls" as the first element in an array, followed by each argument. This is a 2D array (char *input[40]) with a hard limit of 40 arguments with the command. And this is a handy way to parse the line of input and store the command name and arguments since to execute the command, I simply call: execvp(input[0], input)

However, in that previous class, there was no mention of pipes, and I never even thought of handling them, so that aspect was never touched. However, for this new assignment, they have to be handled. I believe I have a decent idea how to do that with the pipe() call and such, but my problem now is parsing the line of input into separate commands and their arguments. So a line of input like:

ls -a -l | head -4 | tail -2

screws up my current parsing. Since I am using a 2D array for a single command and its arguments, the next step would be a 3D array for multiple commands and their arguments. But damn that just makes things a lot more complicated for me. Perhaps it is a simple extension of the 2D (i.e. now becomes char **input[40]), but it's hard to visualize and work with it.

So I'm wondering if using a 3D char array to grab the input, separated by command name and its arguments, when pipes are involved, is the way to go.

People have created tons of different shells... is this the way that they handle pipes? I'm trying to come up with a more simple way of parsing the input that contains multiple pipes. Any ideas?
 

KCfromNC

Senior member
Mar 17, 2007
208
0
76
You could parse up to the first | symbol, and then set up pipes and fork(). Pass the remaining string after the pipe symbol to the child, and exec() the stuff before the | in the parent. Do this iteratively (or recursively) so that each subsequent child gets the stuff after another pipe symbol. This should be easy if you have a pointer to the current character you're parsing - pass this as an argument to a function which parses the next command.

So for your example, you'd parse "ls -a -l", find a |, and therefore fork(). You'd set up your pipes, and the parent would exec the ls command. Your pointer to the next text to parse would be on the head command, so you'd parse that, see a | symbol, and fork() again. You'd again set up your pipes, the parent would exec the head command, and the child would loop back and parse starting at the tail command. You'd have to set things up to handle the end of string so that tail gets exec'd correctly, but that's easy.

And for reference, char *input[40] is a 1-D array - it's an array of 40 pointers to characters. I assume you're using strtok to fill up the list of pointers?
 

duragezic

Lifer
Oct 11, 1999
11,234
4
81
Thanks!

I wasn't actually using strtok(). But I re-wrote it to use it, and got a nice little pimp recursive method, and it seems to work great (handles all pipes and arguments). It's much simpler this way to parse (don't have to do some char *** crap)