Help creating a simple C shell - c

I am new to C and I am trying to create a simple C shell that will allow the user to perform various functions like chdir, cd, exit, mkdir.
I've posted my code below. Can anyone look through it and see what I am doing wrong? I am not sure if I am using fork and execcv correctly. Thanks!
include stdio.h
include stdlib.h
include unistd.h
include <string.h>
include sys/types.h
main() {
//char *user;
//if ((user = getlogin()) == NULL)
// perror("__getlogin1() error");
//else printf("__getlogin1() returned %s\n", user);
int j, status;
int pid, c_pid;
int i = 0;
char *tmp, **ap;
char instring[80]; // store one line of input
char *argv[10]; // store parameters in the format for execv()
promptstart:
printf("Please enter a commcand:\n");
// read a char at a time and put it in instring[]
// put a '\0' at the end
instring[i] = getc(stdin); // stdin is the keyboard
while (instring[i] != '\n') {
i++;
instring[i] = getc(stdin);
}
instring[i] = '\0'; // replace '\n' with '\0'
tmp = instring;
i = 0;
argv[i] = strsep(&tmp, " \t"); // put first word int argv[0]
while ((i < 10) && (argv[i] != '\0')) {
i++;
argv[i] = strsep(&tmp, " \t");
}
// print out the command and options.
i = 0;
while (argv[i] != '\0') {
printf("your entered: %s\n", argv[i++]);
}
//PLACE ERROR HERE
if ((c_pid = fork()) == 0) {
for (j = 0; j < 10; j++)
printf("child (%d) prints %d\n", getpid(), j);
exit(0);
} else if (c_pid > 0) {
c_pid = wait(&status);
printf("child %d exited with status %d\n", c_pid, status);
} else {
execvp(argv[0], argv);
}
goto promptstart;
}

At least IMO, you're putting far too much into main. I'd start with something like:
int main() {
char input[128];
do {
fgets(stdin, input, sizeof(input));
dispatch(input);
} while (strcmp(input, "exit"));
return 0;
}
Then dispatch will look for internal commands, and only do an exec when/if it's given a command it doesn't recognize. To keep things simple to start with, you might consider using popen to execute external commands, and leave switching to a "raw" fork/exec for later, when the limitations of popen start to cause you problems.

For shell builtins (man bash), you probably don't want to fork/exec. I would save fork/exec for running programs that are in your PATH (an environment variable that your shell will have to manage). The shell itself should interface with the filesystem through commands like chdir (man 2 chdir).
Consider using a nice string tokenizer (or just fallback to strtok) for parsing the commandline, and as another comment suggests, abstract that into a function so that your main loop is lean.

Related

Execute fork processes simultaneously [duplicate]

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, ";");
}

Reading in user input with space using scanf in a while loop?

I'm working on some code in which I am trying to read in the following commands, which will lead to certain functions in my program being called:
PRINT
INSERT 0,Folders,Folders for storing related papers,25
PRINT
QUIT
I have been trying different ways to read in this input, which comes from ./inventory test02/inventory02-actual.txt < test02/input02.txt > test02/actual02.txt, in which these commands shown above are in the file input-02.txt.
I've primarily been working with scanf, but have tried fgets, but I have had the most success to what I desire with scanf. I originally tried scanf("%s", command) and in this case, scanf does not take in whitespace so the program terminates.
//Keep re-prompting user for commands until you reach EOF{
while ((scanf("%[0-9a-zA-Z, ]s", command) == 1)) {
printf("====================\nCommand? ");
printf("%s\n", command);
if (strcmp(command, "PRINT") == 0) {
print(list);
} else if (strcmp(substring(command, START, INSERT_END), "INSERT") == 0) {
//Get the substring - this is the input after "INSERT"
char string[MAX_LEN_COMMAND];
strcpy(string, substring(command, OFFSET, strlen(command)));
insert(string, list);
} else if (strcmp(command, "PRINTREVERSE") == 0) {
printReverse(list);
} else {
printf("Invalid command passed.\n");
exit(EXIT_BAD_INPUT);
}
}
Currently, when I run my code, only the first command, "PRINT" is read in. It seems like I cannot read the next line of input from input-02.txt. Is there a way I can read in these commands properly? In addition, after my program reads in "INSERT", it then reads in "0,Folders,Folders for storing related papers,25" as a command, which it should not. It should go directly to the next command, which is "PRINT". I have tried using a continue statement after calling the insert method, but that did not work. Does anyone have any suggestions?
EDIT: Updating code with fgets.
Instead of posting all my functions that I called to above, I think passing a printf to show us what the command is might be simple enough for a reproducible example!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OFFSET 7
#define START 0
#define INSERT_END 5
static const char *substring(char command[], int start, int end);
int main(int argc, char **argv)
{
//Get user input for commands
char command[500];
//Keep re-prompting user for commands until you reach EOF{
while (fgets(command, sizeof(command), stdin) != NULL) {
printf("====================\nCommand? ");
printf("%s\n", command);
if (strcmp(command, "PRINT") == 0) {
printf("%s", command);
} else if (strcmp(substring(command, START, INSERT_END), "INSERT") == 0) {
printf("%s", command);
} else if (strcmp(command, "PRINTREVERSE") == 0) {
printf("%s", command);
} else {
printf("Invalid command passed.\n");
exit(1);
}
}
}
static const char *substring(char command[], int start, int end)
{
int i = 0;
int j = 0;
char *sub;
sub = (char *)malloc(500 * sizeof(char));
for (i = start, j = 0; i <= end; i++, j++) {
sub[j] += command[i];
}
sub[j] = '\0';
return sub;
}
The output I get is:
====================
Command? PRINT
Invalid command passed.
Since you are reading a line, you will have better success with fgets.
char command[101];
while (fgets(command, 100, stdin))
{
// rest of the code can be the same
}

Creating Simple Shell in Linux

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;

command line interpreter with multiple commands per line

I want to write a comamnd line intepreter with multiple commands per line.
I wrote a program in C which works for 1 comamnd per line but if i enter more commands dont work,comamnds are enter like: ls -l ; pwd ; cat file ; ls.
First i parse args,i put them into array and the i have this function:
pid_t pid;
pid = fork();
switch(pid) {
case -1:
printf("DEBUG:Fork Failure\n");
exit(-1);
case 0:
execvp(cmd[j], cmd);
if(execvp(cmd[j], cmd) == -1) {
printf("Command Not Found\n");
exit(0);
}
default:
wait(NULL);
printf("DEBUG:Child Finished\n");
}
My parser is :
printf("shell> ");
fgets (input, MAX_SIZE, stdin);
if ((strlen(input)>0) && (input[strlen (input) - 1] == '\n')) {
input[strlen (input) - 1] = '\0';
}
printf("INPUT: %s\n", input);
cnd = strtok(input, " ;");
int i = 0;
while(cnd != NULL) {
cmd[i] = cnd;
i++;
cnd = strtok(NULL, ";");
I think that i must use pipes to solve my problem,but how ?
Any ideas?
sorry for bad English
The way you explain it, it seems to be that you want to execute the commands one after the other, but not have them communicate with each other (besides, piping the output of ls to pwd just makes no sense).
For that the solution is simple: Split the input on the semicolon, and handle each command as it was a single command (since that's what it is).
With some pseudo-code it could look something like this
input = read_next_line();
while ((next_command = get_next_command(input)) != NULL)
{
execute_command(next_command);
}
You could implement this with e.g. strtok or similar functions.

C: Segmentation fault when signal received

I'm trying to write a program of a basic shell that exits at EOF and handles SIGINT without quitting. It was working within one functionless c file but upon creating functions I ran into an error where EOF and SIGNINT would cause seg faults. I'm unsure what's causing this and hoping that someone will spot something I did not. Below is the function I think causing the issue, its the first thing done after the Shell reads a command (or where I'm applying signals).
int check_redirect(char *line, int flag)
{
int n = 0;
if (line == NULL) return (flag);
else
{
do
{
if (line[n] == '>') flag = 1;
n++;
}while (line[n] != '\n');
}
return (flag);
}
This is where the function is called:
char buffer[15];
time_t now = time(NULL);
strftime(buffer, 15, "[%d/%m %H:%M]", localtime(&now) );
fprintf(stdout, "%s # ", buffer);
signal(SIGINT, SIG_IGN);
read = getline (&line, &len, stdin);
signal(SIGINT, SIG_DFL);
flag = check_redirect(line, flag);
Hopefully this is clear enough. This is at the beginning of a loop, which is looking for input to execute as a command, having printed out the prompt (date and time #). The commands work mostly but act as if they are lost occasionally, which I think may be related to this signal processing error.
Thanks in advance.
Seg fault can occur from an infinite loop
You have the potential for an infinite loop here:
... }while (line[n] != '\n');
Suggest checking for more than just the \n character.
You could use strstr(line, "\n"); to verify newline exists before entering loop, for example.
Change your code to
int check_redirect(char *line, int flag, int len)
{
int n = 0;
if (line != NULL)
{
do
{
if (line[n] == '>')
{
flag = 1;
}
n++;
}
while ((line[n] != '\n') && (n<len))
}
return (flag);
}
call it with:
flag = check_redirect(line, flag, read);

Resources