I'm building for a while my own linux shell as part of my academy project. I have reached to a problem when i'm trying to run "ps" command after the command line "/usr/bin/wc text_file.txt". When I'm trying so - the following error message is shown:
0: /usr/bin/wc shell.c
751 3273 24140 shell.c
1: ps
error: garbage option
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
The followig code is responsible on running the commands in my shell without path (aka "ps"):
void cmd_commands_without_path(char** parsed,unused struct tokens *tokens)
{
pid_t pid = fork(); // Forking a child
char* s = getenv("PATH"); // Holds the full environment var path.
int i = 0; // Counter.
int symbol_counting = 0; // Holds the number of ":" in the full environment path.
char *temp; // Helps with the initielizing of the new path.
char *symbol = "/"; // Holds "/" for correcting the new path.
/* Puts the user command into an array with NULL in the end. */
while((tokens_get_token(tokens,i)) != NULL) // While we did not reached to the end of user input.
{
parsed[i] = tokens_get_token(tokens,i); // Initielize parsed to hold the user input (command).
i++;
}
parsed[i+1] = NULL; // The end of the user input (command) will be NULL (for execv).
/* Count the number of ":" in the path. */
for(i = 0; i < strlen(s); i++)
{
if(s[i] == ':')
{
symbol_counting++; // increasing symbol counting.
}
}
symbol_counting++; // Because have to be an address after the last ":"
if (pid == -1) // If the creation of the child was unsuccessful.
{
printf("\nFailed forking child..");
return;
}
else if (pid == 0) // The child was returned.
{
setpgrp(); // Changing the child group id.
tcsetpgrp(0,getpgrp()); // Return the proccess id of the child.
/* Signals for "ctrl" options. */
signal(SIGINT,SIG_DFL);
signal(SIGQUIT,SIG_DFL);
signal(SIGKILL,SIG_DFL);
signal(SIGTERM,SIG_DFL);
signal(SIGTSTP,SIG_DFL);
signal(SIGCONT,SIG_DFL);
signal(SIGTTIN,SIG_DFL);
signal(SIGTTOU,SIG_DFL);
char* full_array[(symbol_counting+1)]; // Create new array at the size of the number of ":".
i = 0; // Restarting the counter.
/* Initielize array with the seperate environment variable paths. */
full_array[i] = strtok(s, ":");
for(i = 1; i < symbol_counting; i++)
{
full_array[i] = strtok(NULL, ":");
}
for(i = 0; i < symbol_counting; i++) // Runs as the number of phats.
{
temp = (char*)malloc(MAXLIST*sizeof(char)); // Allocate to temp size of 1024 chars.
temp[0] = '\0'; // Erasing temp.
if(temp[0] == '\0') // If the word was ended.
{
strcpy(temp,full_array[i]); // Adds to temp the path we currently gonna check.
temp = strcat(temp, symbol); // Adds "/" to the new path.
temp = strcat(temp, parsed[0]); // Adds to the new path the user command.
if(access(temp, X_OK) == 0) // If the command was found in the path.
{
execv(temp,parsed); // Executing the command.
}
temp[0] = '\0'; // Erasing temp again.
}
}
return;
}
else // pid > 0
{
// waiting for child to terminate
wait(NULL);
tcsetpgrp(0,getpgrp()); // Return the proccess id of the child.
return;
}
}
I should also say that this error occures only when im trying to run the "ps" command after the command "/usr/bin/wc text.txt".
Can anyone give me a clue what i'm doing wrong?
Related
I am working on a home made shell (very simple shell). I have decided to take the route of using execvp as my path is not a changeable element for my shell. I am running into an issue with coming up with the logic on how to fork and exec multiple processes at once.
My program should work with a command as such:
ls ; echo hello ; cat shell.c
Where each ";" indicates that we would like to run these processes at once simultaneously. So on our terminal output we should get a mix of these commands working at once.
To elaborate I'd like to explain how my program works:
A. Intake full command line into char array with a grab line function
B. Split the char array received from the last function by delimiters and place into an array of char arrays (pointer to pointer).
C. If one of the elements in our array of char arrays is ";" we can assume that multi commands are necessary. This is where I have trouble.
I have gotten as far as to know exactly how many processes I need to fork and such, but I cannot seem to wrap my head around how to pass all of these functions plus their arguments to the execvp function at once. Should I use a temp array? I know this shouldn't be this complicated but for some reason I cannot figure it out. I'm submitting my launch function below, which intakes an array of char arrays and executes accordingly based on my "multiCommand" variable which is set when multi commands are needed (by my split line function)
int launch(char **args){
pid_t pid;
int status;
int i = 0;
if(strcmp(args[0], "quit") == 0){
exit(EXIT_SUCCESS);
}
if(strcmp(args[0], ";") != 0){
printf("Essential Command Found : %s\n", args[0]);
numFork++;
}
if(multiCommand == 1){
//Handle Multicommands here
printf("Multi Commands Handling Here\n");
for(; i < elements - 1; i++){
if(strcmp(args[i], ";") == 0){
if((i + 1) < elements){
printf("Essential Command Found : %s\n", args[i + 1]);
numFork++;
}
}
}
//This is where I need to figure out what to do
printf("Fork: %d times\n", numFork);
}else if (multiCommand == 0){
pid = fork();
if(pid == 0){
execvp(args[0], args);
}else{
wait(&status);
}
}
multiCommand = 0;
elements = 0;
return 1;
}
The general idea would be to have a for loop over the different commands and fork each of them.
E.g.
for(int i = 0; i < commandCount; i++) {
int pid = fork();
if(pid == 0) { //this is the child (don't forget to check for errors and what-not)
execCommand(all, of, the, info, needed);
}
}
You can easily get the different commands using strtok().
Here's an example:
#include <string.h>
#include <stdio.h>
int main() {
char input[] = "abc;def;ghi";
char *token = strtok(input, ";");
while(token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ";");
}
return 0;
}
Output:
abc
def
ghi
The final function will look something like this:
char *token = strtok(input, ";");
while(token != NULL) {
int pid = fork();
if(pid == 0) {
//token is one command
//parse the different parts here
execCommand(args, to, exec);
}
token = strtok(NULL, ";");
}
I am working on an assignment to create an extremely simple Linux shell in C, and it works almost exactly how I want it to.
If the user enters a simple Linux command, the program will run it and loop to allow another command. If the user enters "quit", the program exits.
My problem is that the commands only work the first time. Afterward, they seem to somehow become formatted improperly. Is there a way I can reinitialize my args array so that it will receive the new input properly?
int main() {
char* args[50]; // Argument array.
char userInput[200]; // User input.
char* userQuit = "quit"; // String to be compared to user input to quit program.
int pid; // Process ID for fork().
int i = 0; // Counter.
while(1) {
// Promt and get input from user.
printf("minor5> ");
fgets(userInput, sizeof(userInput), stdin);
// Pass userInput into args array.
args[0] = strtok(userInput, " \n\0");
// Loop to separate args into individual arguments, delimited by either space, newline, or NULL.
while(args[i] != NULL) {
i++;
args[i] = strtok(NULL, " \n\0");
}
// If the first argument is "quit", exit the program.
if(strcmp(args[0], userQuit) == 0) {
printf("Exiting Minor5 Shell...\n");
exit(EXIT_SUCCESS);
}
// Create child process.
pid = fork();
// Parent process will wait for child to execute.
// Child process will execute the command given in userInput.
if(pid > 0) {
// Parent //
wait( (int *) 0 );
} else {
// Child //
int errChk;
errChk = execvp(args[0], args);
if(errChk == -1) {
printf("%s: Command not found\n", userInput);
}
}
}
return 0;
}
You need to ensure that args has a NULL last value. It probably had one on the first command, by chance, but no guarantee
Here's a reworked snippet of your parsing loop [please pardon the gratuitous style cleanup]:
// Pass userInput into args array.
char *uptr = userInput;
i = 0;
while (1) {
char *token = strtok(uptr, " \n");
uptr = NULL;
if (token == NULL)
break;
args[i++] = token;
}
// NOTE: this is the key missing ingredient from your code
args[i] = NULL;
I'm trying to create a shell using C that can take multiple commands separated by a semicolon(;). Currently I'm trying to use strtok to separate the commands but I don't think I'm using it correctly. I'll post all the info I can without posting the entire code. Is strtok being used correctly?
char *semi=";";
else
{
char *token=strtok(str,semi);
if(token != NULL)
{
token=strtok(NULL,semi);
if((childpid = fork()) == 0)
{
if ((execvp(args[0], args))<0)//prints error message when unknown command is used
{
printf("Error! Command not recognized.\n");
}
execvp(args[0],args);
free(args);//deallocate args
exit(0);
}
Edit: As per instructed I removed a large chunk of the code originally posted to focus solely on the use of strtok. When compiled the makeshift shell will accept one command at a time. I'm trying to use ";" to separate and run two commands simultaneously. Am I using strtok correctly? If not, is there an alternative?
You should always check, if strtok() returns NULL. I would change the structure as follows:
char* semi = ";"; // Your semikolon
char *token = NULL; // Your token string
// ...
// Split first occour of semicolon
token = strtok(str,semi);
if(token == NULL){
perror("No command given ...");
return NULL;
}
do {
// Execute your code here
// fork() etc.
// You should get each line (each semikolon seperated string)
// and it should be stored into token
} while((token = strtok(NULL, semi) != NULL);
I hope, I did understand your problem right ...
But as I can see, you need to split the token again by spaces to get them into a char-Array for the argv[] (second parameter) of execvp(). Here the problem is, that strtok() internally uses a static (?) variable to store the last position. So using another strtok() inside the loop would "destroy" your text.
You could do something like this:
char *str; // Your string ...
char semi[1] = ";"; // Your semikolon AND space; strtok() will split at both
char *token = NULL; // Your token string
int len = 0;
char *token2;
int argvpos = 0;
// ...
// Split first occour of semicolon
token = strtok(str,semi);
if(token == NULL){
perror("No command given ...");
return EXIT_FAILURE;
}
do {
// save length of token
len = strlen(token);
// Split for blanks to get the arguments
token2 = strtok(token," ");
// Build array of arguments
while(token2 != NULL){
args[argvpos++] = token2;
token2 = strtok(NULL," ");
}
// Do something with token (as command)
// and args (as arguments)
// ...
} while((token = strtok(token+len+1, semi) != NULL);
// In the while condition you add the length to the token; so you get the "old" last position
I think it is not a good solution, but it should work. And I hope, I did understand you problem ;-)
Kind regards.
In order to work correctly, strtok should be used along with a while loop. Also, you don't need to run execvp twice.
I created a small sample program using your code to demonstrate how you can correctly use your code:
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
char str[] = "ls -1; echo 'hello world'"; // Input commands separated by ';'
// Break the commands string into an array
char *commands[10]; // Array to hold a max of 10 commands
char *semi = ";";
char *token = strtok(str, semi);
int i = 0;
while (token != NULL)
{
commands[i] = token;
++i;
token = strtok(NULL, semi);
}
int numCommands = i; // numCommands is the max number of input commands
// Run each input command in a child process
i = 0;
while (i < numCommands)
{
printf("Command: %s\n", commands[i]);
// Tokenize the command so that it can be run using execvp
char *args[10] = {}; // Array to hold command args
args[0] = strtok(commands[i], " ");
int tokenCounter = 0;
while (args[tokenCounter] != NULL)
{
tokenCounter++;
args[tokenCounter] = strtok(NULL, " ");
}
// Create a child process
int childpid = fork();
// If this is child process, run the command
if (childpid == 0)
{
if ((execvp(args[0], args)) < 0)
{
printf("Error! Command not recognized.\n");
}
exit(0);
}
// If this is the parent, wait for the child to finish
else if (childpid > 0)
{
wait(&childpid);
}
// If the child process could not be created, print an error and exit
else
{
printf("Error: Could not create a child process.\n");
exit(1);
}
++i;
}
return 0;
}
I have constructed the following program to try to pipe in my own shell. A StringArray is simply a char** I have constructed. The code runs fine, but when I put in cat txt.txt | grep a, nothing prints back to the screen. When debugging, I saw that the code seems to stop around like 152 (where the print-out command is), where pid==0 and i==0.
For context, I'm calling this function in another function after a pipe has been detected.
void doPipe(StringArray sa)
{
printf("In 69\n");
int filedes[2]; // pos. 0 output, pos. 1 input of the pipe
int filedes2[2];
int num_cmds = 0;
char *command[256];
pid_t pid;
int err = -1;
int end = 0;
// Variables used for the different loops
int i = 0;
int j = 0;
int k = 0;
int l = 0;
// First we calculate the number of commands (they are separated
// by '|')
while (sa[l] != NULL){
if (strcmp(sa[l],"|") == 0){
num_cmds++;
}
l++;
}
num_cmds++;
// Main loop of this method. For each command between '|', the
// pipes will be configured and standard input and/or output will
// be replaced. Then it will be executed
while (sa[j] != NULL && end != 1){
k = 0;
// We use an auxiliary array of pointers to store the command
// that will be executed on each iteration
while (strcmp(sa[j],"|") != 0){
command[k] = sa[j];
j++;
if (sa[j] == NULL){
// 'end' variable used to keep the program from entering
// again in the loop when no more arguments are found
end = 1;
k++;
break;
}
k++;
}
// Last position of the command will be NULL to indicate that
// it is its end when we pass it to the exec function
command[k] = NULL;
j++;
printf("In 121\n");
// Depending on whether we are in an iteration or another, we
// will set different descriptors for the pipes inputs and
// output. This way, a pipe will be shared between each two
// iterations, enabling us to connect the inputs and outputs of
// the two different commands.
if (i % 2 != 0){
pipe(filedes); // for odd i
}else{
pipe(filedes2); // for even i
}
pid=fork();
if(pid==-1){
if (i != num_cmds - 1){
if (i % 2 != 0){
close(filedes[1]); // for odd i
}else{
close(filedes2[1]); // for even i
}
}
printf("Child process could not be created\n");
return;
}
if(pid==0){
printf("In 148\n");
// If we are in the first command
if (i == 0){
printf("In 152\n");
dup2(filedes2[1], STDOUT_FILENO);
}
// If we are in the last command, depending on whether it
// is placed in an odd or even position, we will replace
// the standard input for one pipe or another. The standard
// output will be untouched because we want to see the
// output in the terminal
else if (i == num_cmds - 1){
printf("In 162\n");
if (num_cmds % 2 != 0){ // for odd number of commands
dup2(filedes[0],STDIN_FILENO);
printf("In 166\n");
}else{ // for even number of commands
dup2(filedes2[0],STDIN_FILENO);
printf("In 166\n");
}
// If we are in a command that is in the middle, we will
// have to use two pipes, one for input and another for
// output. The position is also important in order to choose
// which file descriptor corresponds to each input/output
}else{ // for odd i
if (i % 2 != 0){
dup2(filedes2[0],STDIN_FILENO);
dup2(filedes[1],STDOUT_FILENO);
}else{ // for even i
dup2(filedes[0],STDIN_FILENO);
dup2(filedes2[1],STDOUT_FILENO);
}
}
if (execvp(command[0],command)==err){
kill(getpid(),SIGTERM);
}
}
// CLOSING DESCRIPTORS ON PARENT
if (i == 0){
close(filedes2[1]);
}
else if (i == num_cmds - 1){
if (num_cmds % 2 != 0){
close(filedes[0]);
}else{
close(filedes2[0]);
}
}else{
if (i % 2 != 0){
close(filedes2[0]);
close(filedes[1]);
}else{
close(filedes[0]);
close(filedes2[1]);
}
}
waitpid(pid,NULL,0);
i++;
}
}
One of your big problems may be doing waitpid on each iteration of the pipeline construction. The waiting should be done at the end (remembering the pids in a list).
I had some difficulty understanding your code, so I did some simplification and cleanup. In particular, doing if (i % 2 ...) everywhere made things harder.
I've cleaned up and fixed the code. I added a struct to make things easier to manage [please pardon the gratuitous style cleanup]:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef struct {
int pipe_fildes[2];
} pipectl_t;
#define CLOSEME(_fd) \
do { \
close(_fd); \
_fd = -1; \
} while (0)
void
doPipe(char **sa)
{
pipectl_t pipes[2];
pipectl_t *pipein;
pipectl_t *pipeout;
pipectl_t *pipetmp;
int num_cmds = 0;
char *command[256];
pid_t pidlist[256];
pid_t pid;
int err = -1;
int end = 0;
// Variables used for the different loops
int icmd = 0;
int j = 0;
int k = 0;
int l = 0;
// First we calculate the number of commands (they are separated
// by '|')
for (int l = 0; sa[l] != NULL; ++l) {
if (strcmp(sa[l], "|") == 0)
num_cmds++;
}
num_cmds++;
for (int ipipe = 0; ipipe <= 1; ++ipipe) {
pipes[ipipe].pipe_fildes[0] = -1;
pipes[ipipe].pipe_fildes[1] = -1;
}
pipein = &pipes[0];
pipeout = &pipes[1];
// Main loop of this method. For each command between '|', the
// pipes will be configured and standard input and/or output will
// be replaced. Then it will be executed
while (sa[j] != NULL && end != 1) {
// We use an auxiliary array of pointers to store the command
// that will be executed on each iteration
k = 0;
while (strcmp(sa[j], "|") != 0) {
command[k] = sa[j];
j++;
k++;
if (sa[j] == NULL) {
// 'end' variable used to keep the program from entering
// again in the loop when no more arguments are found
end = 1;
break;
}
}
// Last position of the command will be NULL to indicate that
// it is its end when we pass it to the exec function
command[k] = NULL;
j++;
// swap input and output, so previous child's output becomes the new
// child's input
// NOTE: by doing this here, in one place, we eliminate all the i % 2
// if statements
pipetmp = pipein;
pipein = pipeout;
pipeout = pipetmp;
// are we the last command?
int lastflg = (icmd == (num_cmds - 1));
// last command does _not_ have an output pipe, so don't create one
if (! lastflg)
pipe(pipeout->pipe_fildes);
pid = fork();
// NOTE: fork failure almost never happens and is fatal
if (pid == -1) {
printf("Child process could not be created\n");
return;
}
// process child
if (pid == 0) {
// NOTE: after we've dup'ed a file descriptor, we close it
// first command does _not_ have a pipe for input
if (icmd > 0)
dup2(pipein->pipe_fildes[0],STDIN_FILENO);
CLOSEME(pipein->pipe_fildes[0]);
// last command does _not_ have a pipe for output
if (! lastflg)
dup2(pipeout->pipe_fildes[1],STDOUT_FILENO);
CLOSEME(pipeout->pipe_fildes[1]);
// close the parent sides of the pipes (in this child)
// close previous child's output descriptor (the feed for our input)
CLOSEME(pipein->pipe_fildes[1]);
// close next child's input descriptor (our feed for its input)
CLOSEME(pipeout->pipe_fildes[0]);
if (execvp(command[0], command) == err) {
#if 0
kill(getpid(), SIGTERM);
#else
exit(1);
#endif
}
}
// close all input descriptors for _this_ child
CLOSEME(pipein->pipe_fildes[0]);
CLOSEME(pipein->pipe_fildes[1]);
// close output side of _this_ child's output pipe [which becomes next
// child's input pipe]
CLOSEME(pipeout->pipe_fildes[1]);
pidlist[icmd] = pid;
icmd++;
}
// wait for all pids _after_ the entire pipeline is constructed
for (int icmd = 0; icmd < num_cmds; ++icmd)
waitpid(pidlist[icmd], NULL, 0);
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
char *bp;
char buf[1000];
char **av;
char *avlist[256];
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
default:
break;
}
}
while (1) {
printf("> ");
fflush(stdout);
cp = fgets(buf,sizeof(buf),stdin);
if (cp == NULL)
break;
av = avlist;
bp = buf;
while (1) {
cp = strtok(bp," \t\r\n");
bp = NULL;
if (cp == NULL)
break;
*av++ = cp;
}
*av = NULL;
doPipe(avlist);
}
return 0;
}
UPDATE:
When I run this code, the same command cat txt.txt | grep a only appears to do the first command, and not the second after the pipe. (It cats out the txt file but does not grep)
I tested the entire program before I posted. I just retested using a cat/grep command. It worked, but that was my program unchanged.
Any ideas why this could be happening? I implemented your doPipe method in my code and passed in my StringArray sa which is just a char ** as well.
My suggestions are:
Verify that my unchanged version works for you.
Using gdb breakpoint on doPipe and look at the arguments. For both programs, they should be the same.
If StringArray is truly char **, replace it in your version to ensure it makes no difference. That is void doPipe(char **sa) and see if your code still compiles. In gdb at the breakpoint, you should be able to do ptype sa on both programs
The StringArray looks a bit "Java-esque" to me :-) I'd avoid it, particularly here since execvp wants a char **
Verify that sa is properly NULL terminated. If it isn't the last command in the pipeline may be bogus/garbage and the error checking for a failed execvp isn't that robust.
Verify that num_cmds is the same.
Try cat txt.txt | grep a | sed -e s/a/b/. If you get the cat and grep, but not the sed, this means num_cmds is not correct
Verify that caller's parsing of the buffer puts the "|" in a separate token. That is, this code works with cat txt.txt | grep a but it will not work with: cat txt.txt|grep a
UPDATE #2:
BTW, if your pipe code still isn't working (e.g. the last command is not executed), check to see if the last token has newline on it (i.e. the newline wasn't stripped correctly).
I've tried all of this but still can't get my redirection code to work with this. Essentially, I'm confused as to where in this code I should be checking for '<' or '>'
Doing the general parsing to support redirection (e.g. < or >), pipes (e.g. |), multiple commands per line (e.g. ;), embedded sub-shells (e.g. (echo the date is ; date), and detached jobs (e.g. &) can require a bit of care and you need a multilevel approach.
I suspect that after you get pipes and/or redirection working, you're tasked with implementing even more shell syntax. I've done this before, so, rather than you figuring it out piecemeal, here is what you'll need to do ...
You'll need to scan the input buffer char-by-char and save off tokens into a "token" struct that also has a type. You'll need a linked list of those structs. More on this below.
When you encounter a quoted string, you'll need to strip off the quotes: "abc" --> abc, being mindful of escaped quotes: "ab\"c --> ab"c.
Also, you have to be careful about quoted strings abutting [what perl calls] "bareword" strings: echo abc. If we have abc"d ef"ghi, this needs to be concatenated into a single string token: abcd efghi
Backslashes on redirectors must also be accounted for. echo abc > def is a redirection that will put abc into the file def. But, echo abc \> def should just output abc > def literally to stdout. Backslash on the other "punctuation" is similar.
You'll also have to handle the fact that punctuation doesn't have to have whitespace around it. That is, echo abc>def has to be handled just as if it were echo abc > def.
Also, punctuation inside a quoted string should be treated as if it were escaped above. That is, echo abc ">" def is not a redirection and [again] should be treated as a simple command.
Also, if the current line ends in a backslash (e.g. \<newline>), this means that the next line is a "continuation" line. You should strip the backslash and the newline. Then, read another line and continue to build up the token list.
Further, while & can be for detached jobs, as in: date &, it can also be part of a redirection, as in gcc -o myshell myshell.c 2>&1 >logfile
Okay, so to manage all this we need types for tokens and a token struct:
// token types
typedef enum {
TOKEN_NORMAL, // simple token/string
TOKEN_QUO1, // quoted string
TOKEN_QUO2, // quoted string
TOKEN_SEMI, // command separater (e.g. ;)
TOKEN_OREDIR, // output redirector (e.g. >)
TOKEN_IREDIR, // input redirector (e.g. <)
TOKEN_PIPE, // pipe separater (e.g. |)
TOKEN_AMP // an & (can be detach or redirect)
} toktype_t;
// token control
typedef struct token token_t;
struct token {
token_t *tok_next; // forward link
token_t *tok_prev; // backward link
toktype_t tok_type; // token type
char tok_str[256]; // token value
};
// token list
typedef struct tlist tlist_t;
struct token {
tlist_t *tlist_next; // forward link
tlist_t *tlist_prev; // backward link
token_t *tlist_head; // pointer to list head
token_t *tlist_tail; // pointer to list tail
};
Initially, after parsing an input line [being mindful of the continuations], we have a single tlist.
If the list has ; separaters in it, we split on them to create sublists. We then loop on the sublists and execute the commands in order.
When looking at a subcommand, if it ends in &, the command must be run detached. We note that and pop it off the back of the list.
Okay, now we have a list that might be of the form:
cat < /etc/passwd | grep root | sed -e s/root/admin/ > /tmp/out
Now, we do a further split on the | so we have a list that has three elements:
cat < /etc/passwd
grep root
sed -e s/root/admin/ > /tmp/out
Actually, each of those "lines" is a tlist and this is a two dimensional list of lists:
list_of_tlists:
|
|
tlist[0] --> cat --> < --> /etc/passwd
|
|
tlist[1] --> grep --> root
|
|
tlist[2] --> sed --> -e --> s/root/admin/ --> > /tmp/out
As we create the pipeline, we note the redirections and do file open instead of pipe as needed.
Okay, that's the abstract.
See my answer here: Implementing input/output redirection in a Linux shell using C for a full and complete implementation.
On that page, there is code for just doing redirections. It could probably be adapted to include pipes by merging that code with the code I've posted here.
That OP asked for help in doing redirections and pipes.
Side note: At that time, there was a spate of shell implementation questions. So, I ended up producing a full shell that does pretty much everything. But, that version was too large to post on SO. So, in that page, find the pastebin link I posted. It has full source code. It can be downloaded, built, and run.
You may not want to use that code directly, but it should give you some ideas. Also, the full version may do things a little bit differently than what I've described above.
[...] Preprocesser directives
void read_command()
{
int i; //index to the arrays stored in parameter[]
char *cp; //points to the command[]
const char *hash = " "; //figures out the strings seperated by spaces
memset(command, 0, 100); //Clear the memory for array
parameter[0] = "/bn/"; //Initialize the path
//Get the user input and check if an input did occur
if(fgets(command, sizeof(command), stdin) == NULL)
{
printf("Exit!\n");
exit(0);
}
//Split the command and look store each string in parameter[]
cp = strtok(command, " "); //Get the initial string (the command)
strcat(parameter[0], cp); //Append the command after the path
for(i = 1; i < MAX_ARG; i++)
{
cp = strtok(NULL, " "); //Check for each string in the array
parameter[i] = cp; //Store the result string in an indexed off array
if(parameter[i] == NULL)
{
break;
cp = NULL;
}
}
//Exit the shell when the input is "exit"
if(strcmp(parameter[0], "exit") == 0)
{
printf("Exit!\n");
exit(0);
}
}
int main()
{
[...]
read_command();
env = NULL; //There is no environment variable
proc = fork();
if(proc == -1) //Check if forked properly
{
perror("Error");
exit(1);
}
if (proc == 0) //Child process
{
execve(parameter[0], parameter, env); //Execute the process
}
else //Parent process
{
waitpid(-1, &status, 0); //Wait for the child to be done
}
[...]
}
The basic idea of the code is to read the input command by the user (done in the read_command() function) (ex: ls -l). Then I divide the input string in little strings and store them in an array. The point is to store the command in parameter[0] (ex: ls) and the parameters in parameter[1,2,3 etc.] (ex: -l). However, I think I executing the execve() function incorrectly.
There are all types of issues with your code including the following (some of them are correctly pointed out by Jonathan Leffler):
"/bin/" is misspelled as "/bn/"
Since parameter[0] points to a string literal ("/bn/") in strcat(parameter[0], cp); you are trying to append to this string literal which is incorrect. You should allocate a buffer to hold the concatenated string instead.
Your tokenizing code doesn't handle the trailing newline in command properly.
env should point to a NULL-terminated array of strings.
In general, I think you should focus on implementing and testing parts of your code properly before integrating them in a larger program. If you tested the read_command before trying to pass its results to execve, you would notice that it doesn't work.