I am writing a shell for an assignment in C. My only problem now is that when I call an external program with execvp() and then close is using CTRL + C, any further commands that I send are printed after the prompt not before it. I made an dummy program that is just an infinite loop to test this.
So after closing this program if I try and run a program that doesn't exist, it will print out
MINISHELL (cwd) $: and then a blank line
and then it will print my error message saying that I have entered a program that does not exist and then it will not print a prompt since it already has. So It ends with an empty new line and it appears that the program has not ended but it actually has.
It ONLY does this after I have closed a non terminating program with CTRL + C. Otherwise it works fine executing programs. Am I not using close() somewhere to close a file descriptor? I am not sure. Any help is appreciated. Thank you.
#include "minishell.h"
void intHandler(int sig)
{
my_char('\n');
}
int main(int argc, char **argv)
{
int n;
int pid = 0;
char* s = (char*)malloc(256*sizeof(char));
char cwd[1024];
char** input;
while (1)
{
signal(SIGINT, intHandler);
my_str("MINISHELL:");
if (getcwd(cwd, sizeof(cwd)) != NULL)
my_str(cwd);
else
{
my_str("Error in current directory");
return -1;
}
my_str(" $: ");
n = read(0, s, 256);
s[n] = '\0';
if (n > 1)
{
input = my_str2vect(s);
if (my_strcmp(input[0], "cd") == 0)
{
/*CHANGE DIRECTORY*/
if (input[1] != '\0')
{
if (chdir(input[1]) < 0)
my_str("MINISHELL: Error in path. Make sure the directory exists.\n");
}
else
my_str("MINISHELL: Error. No directory specified.\n");
}
else if (my_strcmp(input[0], "help") == 0)
{
/*HELP*/
my_str("\nMINISHELL COMMANDS:\n\ncd *directory\nChanges the current working directory to *directory\n\nexit\nExits the minishell\n\nhelp\nPrints a help message listing the built in commands\n\n");
}
else if (my_strcmp(input[0], "exit") == 0)
{
/*EXIT*/
my_str("Thank you for using MINISHELL\n");
exit(0);
}
else if (input[0] != NULL)
{
/*EXECUTE AN EXTERNAL PROGRAM*/
if ((pid = fork()) < 0)
my_str("MINISHELL: Error forking\n");
else if (pid > 0)
{
wait(NULL);
}
else
{
if (execvp(input[0], input) < 0)
{
my_str("MINISHELL: Error. Program does not exist in current directory.\n");
}
else
{
exit(0);
close(0);
}
}
}
else
{
my_str("MINISHELL: Error reading command. Type help to see available commands.");
}
}
}
return 0;
}
You cannot expect that the error message will always print before your prompt. The forked process is running in parallel with your main program. The order in which the output to stdout from the two programs occur is dependent upon how the OS scheduler runs the processes.
I think most real shells like bash actually do the check for the file existing in the main process before doing the fork(). Alternatively you could use pipe() and manually redirect the output. Then you can enforce flushing the entire contents before doing your prompt.
Related
I am creating a Linux type shell program for a school project. So far I have implemented the basic Linux external commands like "ls","ps", etc., using execvp and basic pipes. As part of the project, the user can either run the program in interactive mode or batch mode. In interactive mode the user just enters a command when prompted. For batch mode, the user specifies a file in the command line where there is a list of commands to execute.
The problem I am having is in batch mode. In batch mode, if an invalid command is listed (e.g. "kdfg", for which "kdfg: command not found" while be the output), everything afterward continues, but everything afterward is executed twice. so if I have a "kghd" on one line and the next line is "ls", then the "ls" command will be executed twice. I've literally been looking at my code for hours and have tried a bunch of stuff, but to no avail.
My code is displayed below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include<sys/wait.h>
#include<unistd.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
char* InputString();
char** GetArgs(char* com);
void Free(char** args);
char** GetCommands(char** line);
void PipedCommands(char* line);
int batch = 0; //Acts as bool for if there is a batch file given at command line
FILE* bFile; //This is just to make a quick tweek to the file if necssary to prevent any undefined behavior and keep track of where we are in the fil.
int b_fd;
int stdin_cpy;
int main(int argc, char** argv)
{
pid_t pid;
int status;
int fd;
int exitCom = 0; //acts as bool to check if an exit command was given.
char* line;
if(argc > 1) //check if batch file was given.
{
/*
if(freopen(argv[1], "r", stdin) == NULL) //I added this in case the file isn't found
{
printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
exit(1);
}
*/
//The following is to append a newline at the end of the file (if there isn't one).
//For some reaosn, there seems to be some undefined behavior if the input file isn't
//stricitly ended with a newline.
bFile = fopen(argv[1], "r+"); //open for reading and updating
if(bFile == NULL)
{
printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
exit(1);
}
fseek(bFile, -1, SEEK_END); //go to last character of file
if(fgetc(bFile) != '\n') //If last character is not a newline, append a newline to the file.
{
fprintf(bFile, "\n");
}
fclose(bFile); //close the file.
bFile = fopen(argv[1], "r"); //open file to keep track of when it ends
b_fd = open(argv[1], O_RDONLY); //open file again (with file descriptor this time) to duplicate it to stdin
stdin_cpy = dup(fileno(stdin)); //keep track of stdin file.
dup2(b_fd, 0); //duplicate to stdin so program takes input from bFile
close(b_fd);
batch = 1;
}
//int i=0; //this was used for debugging purposes
while(1)
{
printf("\n");
char** coms = GetCommands(&line);
for(int i=0; coms[i] != NULL; ++i) //loop goes through each command returned from GetCommands(...)
{
//fork and wait.
pid = fork();
wait(&status);
if(pid == 0)
{
int pipedCommand = 0;
//printf("\ncoms[%d]: %s\n", i, coms[i]);
for(int j=0; j<strlen(coms[i]); ++j)
{
if(coms[i][j] == '|')
{
pipedCommand = 1;
break;
}
}
if(pipedCommand == 1)
{
PipedCommands(coms[i]);
exit(1);
}
char** args = GetArgs(coms[i]);
//printf("\nargs[0]: %s\n", args[0]);
if(strcmp(args[0],"exit") == 0)
{
exit(5); //if exit command was given, exit status will be 5 (I just used 5 becuse I felt like it).
}
//printf("\nNo exit\n");
printf("\n");
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(1); //Normal exit exits with 1 or 0.
}
//Parent continues after child exits
else if(pid > 0)
{
//check exit status of child
if(WEXITSTATUS(status) == 5)
exitCom = 1; //set bool exitCom to 1 (true), indicating that the exit command was given
}
}
if(pid > 0)
{
free(line);
free(coms);
//Now that all commands in the line were executed, check exitCom and if it is 1 (exit command was given), the shell can now exit.
if(exitCom == 1)
{
printf("\n");
exit(0);
}
}
/*
if(i >= 5)
{
printf("\nFORCED EXIT\n"); //this was used for debugging purposes
exit(1);
}
++i;
*/
}
return 0;
}
char* InputString()
{
int len = 20;
char* str = (char*)malloc(sizeof(char)*len);
char* buff;
unsigned int i=0;
if(str != NULL)
{
int c = EOF;
//printf("%c", fgetc(bFile));
while( ((c = getchar()) != '\n') && (c != EOF) )
{
/*
//printf("%c", fgetc(bFile));
//fgetc(bFile);
if(feof(bFile))
{
printf("\n\nEnd of the line\n\n");
}
*/
str[i++] = (char)c;
if(i == len)
{
len = len*2;
str = (char*)realloc(str,sizeof(char)*len);
}
}
str[i] = '\0';
buff = (char*)malloc(i);
}
if(batch == 1)
{
if(fgets(buff, i, bFile) == NULL) //Once the end of file has been reached
{
dup2(stdin_cpy, 0); //revert input back to original stdin file so user can now enter commands interactively (this happens if exit command was not given)
close(stdin_cpy); //close stdin_copy
fclose(bFile); //close bFile as we have reached the end of it
batch = 0;
}
}
printf("\n");
return str;
}
//User enters a line of commands (1 or more). Commands are separated with a ';' being the delimeter.
char** GetCommands(char** line)
{
char** coms = (char**)malloc(sizeof(char*));
char delim[] = ";";
if(batch == 0)
printf("prompt> ");
fflush(stdout);
*line = InputString();
if(batch == 1)
printf("%s\n", *line);
strcat(*line, ";");
int i=0;
coms[i] = strtok(*line, delim);
while(coms[i] != NULL)
{
++i;
coms = (char**)realloc(coms, sizeof(char*) * (i+1));
coms[i] = strtok(NULL, delim);
//printf("\ni: %d\n", i);
}
return coms;
}
//A command obtained from GetCommands(...) is separated into various arguments with a space, ' ', being the delimiter.
char** GetArgs(char* com)
{
char** args = (char**)malloc(sizeof(char*));
char delim[] = " ";
//printf("\nline: %s\n", line);
int i=0;
args[i] = strtok(com, delim);
while(args[i] != NULL)
{
++i;
args = (char**)realloc(args, sizeof(char*) * (i+1));
args[i] = strtok(NULL, delim);
}
return args;
}
void PipedCommands(char* line)
{
char** coms = (char**)malloc(sizeof(char*));
int numComs;
char delim[] = "|";
int i=0;
coms[i] = strtok(line, delim);
while(coms[i] != NULL)
{
++i;
coms = (char**)realloc(coms, sizeof(char*) * (i+1));
coms[i] = strtok(NULL, delim);
}
numComs = i;
int fd[2];
pid_t pid;
int status;
int prev_p = 0;
// printf("\nnumComs: %d\n", numComs);
for(int i=0; i<numComs; ++i)
{
//printf("\ni: %d\n", i);
pipe(fd);
pid = fork();
wait(&status);
if(pid == 0)
{
//printf("\nChild\n");
if(i < numComs-1)
{
//printf("\ni < numComs-1\n");
//printf("%s", coms[i]);
//printf("coms[%d]: %s", i, coms[i]);
//printf("\nBefore dup2\n");
char** args = GetArgs(coms[i]);
//printf("\nexecvp in if\n");
if(prev_p != 0)
{
dup2(prev_p, 0);
close(prev_p);
}
dup2(fd[1], 1);
close(fd[1]);
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(3);
}
else
{
//printf("\nelse\n");
//printf("coms[%d]: %s", i, coms[i]);
//printf("\nBefore dup2 in else\n");
if(prev_p != 0)
{
dup2(prev_p, 0);
close(prev_p);
}
//close(fd[0]);
close(fd[1]);
char** args = GetArgs(coms[i]);
printf("\n");
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(3);
}
}
close(prev_p);
close(fd[1]);
prev_p = fd[0];
if(WEXITSTATUS(status) == 3)
{
close(fd[0]);
close(prev_p);
close(fd[1]);
return;
}
}
close(fd[0]);
close(prev_p);
close(fd[1]);
}
You can probably ignore the PipedCommands(...) function as I do not think the problem lies there.
Below is a simple batch file:
kldfg
whoami
Below is the output using the above batch file
kldfg
kldfg: command not found
whoami
jco0100
whoami
jco0100
The whoami command should only execute once, but it appears to execute twice. After that, the program reverts to interactive mode as it should and everything runs fine from there. Does anyone have any idea why this is happening. This only happens when an unknown command is entered. If all commands in the batch file are valid, nothing is outputted twice. It's only for batch files that have an unknown command that all commands after the unknown one are outputted twice.
Here's another example:
Batch file:
date
kldfg
whoami; ls | wc -l
date | wc -c
Output:
date
Tue Apr 13 19:43:19 CDT 2021
kldfg
kldfg: command not found
whoami; ls | wc -l
jco0100
34
date | wc -c
29
whoami; ls | wc -l
jco0100
34
date | wc -c
29
I got it working by disconnecting stdin on the child process before running the command:
...
freopen("/dev/null", "r", stdin); // disconnect
execcvp(args[0], args);
...
From this link: If I fork() and then do an execv(), who owns the console?
I had a crack at debugging it. To get you started on that road:
Compile your C program with debugging symbols:
$ gcc --debug your-program.c
Now debug your C program:
$ gdb a.out
This start a gdb interactive shell.
In gdb itself:
(gdb) list
(gdb) set follow-fork-mode parent
(gdb) breakpoint 69
list your code
tell the debugger to follow the parent when fork()
set a breakpoint at line 69
Run the program:
(gdb) run batch.txt
It will pause at line 69. Execute next line:
(gdb) next
Print a variable:
(gdb) print *coms
Continue running:
(gdb) continue
I leave the rest for you to explore.
I'm still not sure what's wrong with it. Something strange happens in InputString() after your fork fails with an unknown command. InputString() begins returning duplicates from getchar().
I didn't even know you could do that with stdin. Maybe just read from the file in a normal fashion, rather than clobbering stdin, and see if the problem goes away.
Don't write linux code much, I am trying to do more of that.
I took the fork and wait commands out so I could build it in mingw64 (because they arent supported in windows builds) and can't seem to reproduce the issue.
So I think the issue is in the multi-threading setup you have going there.
The "pid" variable is shared between every fork. which means when the fork command is called "pid" is set to whatever the last fork in the loop returned.
It looks like you are using an if statement checking the "pid" variable to see if this thread can execute the command. But wouldn't the main thread keep running right through that?
I don't know what fork() returns but "pid" is uninitialized, don't know if that matters.
Maybe this helps?
I'll post my code first, then explain the problem I'm having:
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define MAX_ARGS 20
#define BUFSIZE 1024
int get_args(char* cmdline, char* args[])
{
int i = 0;
/* if no args */
if((args[0] = strtok(cmdline, "\n\t ")) == NULL)
return 0;
while((args[++i] = strtok(NULL, "\n\t ")) != NULL) {
if(i >= MAX_ARGS) {
printf("Too many arguments!\n");
exit(1);
}
}
/* the last one is always NULL */
return i;
}
void execute(char* cmdline)
{
int pid, async, oneapp;
char* args[MAX_ARGS];
char* args2[] = {"-l", NULL};
int nargs = get_args(cmdline, args);
if(nargs <= 0) return;
if(!strcmp(args[0], "quit") || !strcmp(args[0], "exit")) {
exit(0);
}
printf("before the if\n");
printf("%s\n",args[nargs - 2]);
int i = 0;
// EDIT: THIS IS WHAT WAS SUPPOSED TO BE COMMENTED OUT
/*
while (args[i] != ">" && i < nargs - 1) {
printf("%s\n",args[i]);
i++;
}
*/
// Presence of ">" token in args
// causes errors in execvp() because ">" is not
// a built-in Unix command, so remove it from args
args[i - 1] = NULL;
printf("Escaped the while\n");
// File descriptor array for the pipe
int fd[2];
// PID for the forked process
pid_t fpid1;
// Open the pipe
pipe(fd);
// Here we fork
fpid1 = fork();
if (fpid1 < 0)
{
// The case where the fork fails
perror("Fork failed!\n");
exit(-1);
}
else if (fpid1 == 0)
{
//dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
//close(fd[0]);
// File pointer for the file that'll be written to
FILE * file;
// freopen() redirects stdin to args[nargs - 1],
// which contains the name of the file we're writing to
file = freopen(args[nargs - 1], "w+", stdin);
// If we include this line, the functionality works
//execvp(args[0],args);
// We're done writing to the file, so close it
fclose(file);
// We're done using the pipe, so close it (unnecessary?)
//close(fd[1]);
}
else
{
// Wait for the child process to terminate
wait(0);
printf("This is the parent\n");
// Connect write end of pipe (fd[1]) to standard output
dup2(fd[1], STDOUT_FILENO);
// We don't need the read end, so close it
close(fd[0]);
// args[0] contains the command "ls", which is
// what we want to execute
execvp(args[0], args);
// This is just a test line I was using before to check
// whether anything was being written to stdout at all
printf("Exec was here\n");
}
// This is here to make sure program execution
// doesn't continue into the original code, which
// currently causes errors due to incomplete functionality
exit(0);
/* check if async call */
printf("Async call part\n");
if(!strcmp(args[nargs-1], "&")) { async = 1; args[--nargs] = 0; }
else async = 0;
pid = fork();
if(pid == 0) { /* child process */
execvp(args[0], args);
/* return only when exec fails */
perror("exec failed");
exit(-1);
} else if(pid > 0) { /* parent process */
if(!async) waitpid(pid, NULL, 0);
else printf("this is an async call\n");
} else { /* error occurred */
perror("fork failed");
exit(1);
}
}
int main (int argc, char* argv [])
{
char cmdline[BUFSIZE];
for(;;) {
printf("COP4338$ ");
if(fgets(cmdline, BUFSIZE, stdin) == NULL) {
perror("fgets failed");
exit(1);
}
execute(cmdline) ;
}
return 0;
}
So, what's the problem? Simple: the code above creates a file with the expected name, i.e. the name provided in the command line, which gets placed at args[nargs - 1]. For instance, running the program and then typing
ls > test.txt
Creates a file called test.txt... but it doesn't actually write anything to it. I did manage to get the program to print garbage characters to the file more than a few times, but this only happened during bouts of desperate hail mary coding where I was basically just trying to get the program to write SOMETHING to the file.
I do think I've managed to narrow down the cause of the problems to this area of the code:
else if (fpid1 == 0)
{
printf("This is the child.\n");
//dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
//close(fd[0]);
// File pointer for the file that'll be written to
FILE * file;
// freopen() redirects stdin to args[nargs - 1],
// which contains the name of the file we're writing to
file = freopen(args[nargs - 1], "w+", stdout);
// If we include this line, the functionality works
//execvp(args[0],args);
// We're done writing to the file, so close it
fclose(file);
// We're done using the pipe, so close it (unnecessary?)
//close(fd[1]);
}
else
{
// Wait for the child process to terminate
wait(0);
printf("This is the parent\n");
// Connect write end of pipe (fd[1]) to standard output
dup2(fd[1], STDOUT_FILENO);
// We don't need the read end, so close it
close(fd[0]);
// args[0] contains the command "ls", which is
// what we want to execute
execvp(args[0], args);
// This is just a test line I was using before to check
// whether anything was being written to stdout at all
printf("Exec was here\n");
}
More specifically, I believe the problem is with the way I'm using (or trying to use) dup2() and the piping functionality. I basically found this out by process of elimination. I spent a few hours commenting things out, moving code around, adding and removing test code, and I've found the following things:
1.) Removing the calls to dup2() and using execvp(args[0], args) prints the result of the ls command to the console. The parent and child processes begin and end properly. So, the calls to execvp() are working properly.
2.) The line
file = freopen(args[nargs - 1], "w+", stdout)
Successfully creates a file with the correct name, so the call to freopen() isn't failing. While this doesn't immediately prove that this function is working properly as it's written now, consider fact #3:
3.) In the child process block, if we make freopen redirect to the output file from stdin (rather than stdout) and uncomment the call to execvp(args[0], args), like so:
// freopen() redirects stdin to args[nargs - 1],
// which contains the name of the file we're writing to
file = freopen(args[nargs - 1], "w+", stdin);
// If we include this line, the functionality works
execvp(args[0],args);
and run the program, then it works and result of the ls command is successfully written to the output file. Knowing this, it seems pretty safe to say that freopen() isn't the problem either.
In other words, the only thing I haven't been able to successfully do is pipe the output of the execvp() call that's done in the parent process to stdout, and then from stdout to the file using freopen().
Any help is appreciated. I've been at this since 10 AM yesterday and I'm completely out of ideas. I just don't know what I'm doing wrong. Why isn't this working?
I'm trying to write a code about a process that executes programs from $PATH using the execlp() command.(it doesn't need to be the execlp command but I've found it useful for this one) I've achieved my expected output, but I need to run more than one commands. More specifically I want the child process to run the exec command, then the parent process to print a text indicating that it's ready to accept another command. Then the child process will run the new exec command. My code is this:
int main ( int argc, char *argp[]) {
pid_t progpid = fork(); // the fork command for the creation of the child process
int status = 0;
char com[256];
if (progpid < 0) // the check in case of failure
{
printf("PROGRAM ABORTED!");
return 0;
}
do
{
if (progpid == 0) // the child process
{
scanf( "%s", com);
if (com == "exit")
{
exit(0);
}
else
{
execlp(com, com, NULL);
}
}
else //the parent process
{
wait(&status);
printf("$");
}
}while (com != "exit");
return 0;
}
The expected output is :
<program which I input from keyboard> ( for example : ls )
<output of the program>
$<next program>
<output of the next program>
.
.
.
$exit
In short I want to keep running programs till I enter exit where it ends without doing anything else. However the output I get is this:
<program>
<output of program>
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
It keeps printing $ until I shut it down. I'm new to processes so please don't be too harsh about my code so far.
Thank you in advance!
This
if (com == "exit")
should be
if (strcmp(com, "exit") == 0)
Similarly change the while condition as well.
In C, string comparisons are done using strcmp(). == in your case, simply compares the address of com and the address of the string literal "exit". (In expressions, an array gets converted into a pointer to its first element. Hence, "address" comparison. Also see: What is array decaying?).
Note that your execlp() call has an issue. NULL may be defined as 0, in which case execlp(), being a variadic function, may be able to recognize it as the last argument.
I'd suggest to change it to:
execlp(com, com, (char*)0);
You'd also want to check if wait() failed or not by checking its return code.
Here's a simple example based on yours with improved error checking.
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main ( int argc, char *argp[]) {
for(;;) {
char com[1024];
printf("$ ");
fgets(com, sizeof com, stdin);
com[strcspn(com, "\n")] = 0; /* Remove if there's a newline at the end */
if (strcmp(com, "exit") == 0) {
exit(0);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) { /* child process */
execlp(com, com, (char*)0);
}
int status;
int rc = wait(&status);
/* You can inspect 'status' for further info. */
if (rc == -1) {
perror("wait");
exit(1);
}
}
return 0;
}
Note that if you want your to execute commands with arguments then you need to do argument processing.
Here is the following code I am current having issues with:
#include <stdio.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#define MAX_LINE 80
int main(void)
{
char *args[MAX_LINE/2+1];
int background= 0;//integer that acts a boolean for if & appears
int should_run = 1;
int status;
while(should_run)//Just to keep the interface going until the user chooses to stop
{
printf("osh>");//prompt
fflush(stdout);
int i = 0;
while(getchar() != '\n')//Use scanf until a new line is found
{
scanf("%s", &args[i]);
if(strcmp(&args[i], "exit") == 0)//temporary exit protocal
{
printf("Exiting now...");
return 0;
}
if(strcmp(&args[i], "&") == 0)//If we find a & in our input then we changed background to 1 or true
background = 1;
printf("Args[%i] = %s\n", i, &args[i]);//Tester
i++;
}
printf("Background = %i\n",background);//Test
pid_t pid= fork();// Create new child process
printf("process %i created\n", pid);//Test
if(pid < 0)//fork() failed
{
printf("Fork Failed.\n");
return 1;
}
else if(pid == 0)//Child process id
{
printf("Child process started %s command\n", &args[0]);
if(execvp(args[0], args) < 0)//change the current child process to execute the input given by the user
//with args[0] being the command and args being the parameters(ls -l is an example).
{
printf("Command failed\n");
}
return 0;
}
else//Parent Process
{
if(background == 1)//If the user typed in a & then the parent will wait for a change in state from the child, if there is no &
//then we will just finish the parent process
{
printf("Parent process waiting on child\n");
wait(NULL);
}
}
}
return 0;
I have one major issue and one minor issue right now. The major issue is that I have a printf method before execvp starts that says "Child Process started" and I get this line to print, but then nothing else happens. No interrupts are thrown, the program just seems to be frozen on my execvp command.
My minor issue is that when my program starts a prompt "osh>" before asking for input. Now if, for example, I would type in "osh>ls -l" then I get args[0] = s, args1 = -l. Now if I put "osh> ls -l" in that exact format I get args[0] = ls, args1 = -l. Is that a part of scanf() that I am not using properly here to make sure I get ever character after "osh>" and between blank spaces as strings?
EDIT:
here is my output for user input "ls -l"
The problem you're having with the missing character is because getchar() is consuming the first character of your input before scanf gets to take a stab at it. You probably want to do something like:
while (scanf("%s", &buffer) > 0)
{
strcpy(args[i], buffer);
/* then do stuff with args[i] */
}
I'm trying to implement a minimal shell using C. I will run it on a linux machine.
Basically, execvp() does not seem to execute anything. Why is this? I have some code that tries to see if there is an error. Any command I enter into my mini-shell returns that error statement. Also, nothing happens, a sure sign something isn't working.
The problem from the book I'm working on says to invoke execvp() as execvp(args[0], args). I see this referenced on stackoverflow as the way to invoke it, but some also suggest execvp(args[0],args[1]) Which one is it?
I think by now I have little to no errors in the way I'm handling user input, but if there's anything just point it out.
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<time.h>
#define MAXLINE 80
int main(void)
{
char *args[MAXLINE/2 + 1]; //cl w/ 40 max args
//the above is an array of pointers-to-char
char *s;
int k;
int pid;
int lastarg;
int status;
int should_run = 1; //determines when to exit
printf("CS149 Shell from MYNAME HERE\n");
while(should_run)
{
/* PROMPT */
printf("AUTHOR-L3006323213> ");
fflush(stdout);
s = (char*) calloc ( 200, sizeof(char) );
fgets(s,199,stdin);
/* PARSE */
k=0;
args[k] = strtok(s," \n\t");
while(args[k])
{
printf("%d %s\n",k, args[k] );
++k;
args[k] = strtok(NULL," \n\t");
}
args[k]=NULL;
lastarg=k-1;
/* HANDLE EXIT */
if (strcmp(args[0],"exit\n") == 0)
{
should_run=0;
continue;
}
//fork child process using fork();
pid = fork();
if(pid<0) printf("ERROR!\n");
else if(pid==0)
{ //child
if(execvp(args[0], &args[1]) < 0)
printf("Command not found.\n");
exit(1);
}
else
{ //parent
//check last arg for == &
printf("Parent is ");
if(strcmp(args[lastarg],"&") == 0)
printf(" not waiting...\n");
else
{
printf("waiting...\n");
while (wait(&status) != pid);
}
}
/* Cleanup for next prompt */
free(s);
}
return 0;
}
EDIT: So I've managed to fix somethings. I'm invoking execvp() using execvp(args[0], &args[1]) now. If I give my terminal ps &, I see a list of processes, but another prompt does not show up. When I give my terminal ps, I do not see any list of processes at all. I think there's an issue with the parent code, and maybe with my control logic.