I am trying to make a tiny shell. My problem is that when I call execvp() - I get errors.
For example, when I type in ls -l it returns ls: invalid option -- '
Can someone, please, help me understand why I am getting this error? For my code, the function command split gets the user input, and splits them up into separate commands. Separate commands are seperated by ; character.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_CHARACTERS 512
#define HISTORY_SIZE 10
int commandSplit(char *c, char *a[], int t[]) {
int count = 0;
int total = 0;
char *temp[MAX_CHARACTERS];
char *readCommands = strtok(c, ";");
while(readCommands != NULL) {
printf("Reading full command: %s\n", readCommands);
temp[count] = readCommands;
count++;
readCommands = strtok(NULL, ";");
}
printf("Done reading full commands\n");
for(int i = 0; i < count; i++) {
char *read = strtok(temp[i], " ");
int track = 0;
while(read != NULL) {
printf("Reading individual command: %s\n", read);
a[total] = read;
track++;
total++;
read = strtok(NULL, " ");
}
t[i] = track;
}
return count;
}
int main() {
int exitProgram = 0;
char *args[MAX_CHARACTERS];
while(!exitProgram) {
char *commands = (char *)(malloc(MAX_CHARACTERS*sizeof(char)));
int tracker[MAX_CHARACTERS];
int numOfCommands = 0;
printf("tinyshell> ");
fgets(commands, MAX_CHARACTERS, stdin);
if(strlen(commands) == 0) continue;
numOfCommands = commandSplit(commands, args, tracker);
printf("There are %i commands!\n", numOfCommands);
if(strcmp(args[0], "exit") == 0) {
printf("Exiting\n");
exitProgram = 1;
continue;
}
int l = 0;
for(int i = 0; i < numOfCommands; i++) {
int status;
char *holder[tracker[i]+1];
for(int j = 0; j < tracker[i]; j++) {
holder[j] = args[l];
printf("Assiging holder:%s\n", holder[j]);
l++;
}
holder[tracker[i]] = NULL;
printf("What is holder? \n");
for(int o = 0; o < tracker[i]; o++) printf("%s", holder[o]);
pid_t p = fork();
pid_t waiting;
if(p == 0) {
printf("I am in child process\n");
execvp(holder[0], holder);
fprintf(stderr, "Child process could not execvp!\n");
exit(1);
}
else {
if(p < 0) {
fprintf(stderr, "Fork FAILED!\n");
}
else {
waiting = wait(&status);
printf("Child %d, status %d\n", waiting, status);
}
}
for(int i = 0; i < numOfCommands; i++) {
args[i] = NULL;
}
}
}
return 0;
}
Your problem is that fgets() also reads the newline character. As a result, the last argument of execvp() arguments array contains a newline, causing ls complain about an unrecognized argument: what you acctually pass to ls is -l\n; what you need to pass is just -l without the newline.
Try adding this code after the fgets call to trim the input buffer:
int len;
len = strlen(commands);
if (len > 0 && commands[len-1] == '\n') {
commands[len-1] = '\0';
}
Related
The function gets an input from the user using read() and breaks it down using strtok and places it into an array. The program loops until it reaches an error (which i won't get into because that isn't the problem here) or if the program is terminated by the user. However, when it loops back around and reads the input from the user, it seems to be hanging onto the previous input from the user.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#define BUFFERSIZE 1024
int main(int argc, char* argv[])
{
argc++;
char buf[BUFFERSIZE];
int n;
printf("Please enter commands: \n");
while ((n = read(STDIN_FILENO, buf, BUFFERSIZE)) > 0)
{
printf("original string: %s:\n", buf);
buf[strlen(buf)-1] = '\0';
printf("after change: %s:\n", buf);
int i = 0;
char* array[100];
char* token1 = strtok(buf, " ");
while ((token1 != NULL))
{
array[i++] = token1;
token1 = strtok(NULL, " ");
}//while
for (int j = 0; j < i; j++)
{
printf("Array value %d: %s:\n", j, array[j]);
}//for
if (buf == "exit")
{
printf("found it\n");
}//if
for (int i = 1; i < argc; i++)
{
pid_t pid;
if (argc >= 1)
{
if ((pid = fork()) < 0)
{
perror("fork");
}//if
else if (pid == 0)
{ // child process
if (execvp(array[0], array) == -1)
{
perror("execvp");
return EXIT_FAILURE;
} // if
}//else if
else
{ // parent process
int status;
wait(&status);
printf("Please enter commands again: \n");
}//else
}//if
else
{
fprintf(stderr, "Please specify the name of the program to exec as a command line argument\n");
return EXIT_FAILURE;
}//if
}//for
}//while
if (n == -1) perror("read");
}//main
I've tried to clear the array and clear "buf" but no luck. i have a feeling it has to do with the read() and the fact that "buf" is hanging onto its old value.
Prefaced by my top comments ...
read does not add 0x00 the way fgets does, so we have to do it manually.
execvp needs the array to be terminated with a NULL entry.
child should use exit instead of return.
Here's the refactored code. It is annotated. Note that this code does not do the split/join of the buffer to guarantee the buffer ending in newline as suggested by my top comments:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#define BUFFERSIZE 1024
int
main(int argc, char *argv[])
{
argc++;
char buf[BUFFERSIZE];
int n;
printf("Please enter commands: \n");
// NOTE/BUG: read does _not_ add 0x00 the way fgets does
#if 0
while ((n = read(STDIN_FILENO, buf, BUFFERSIZE)) > 0) {
#else
while ((n = read(STDIN_FILENO, buf, BUFFERSIZE - 1)) > 0) {
buf[n] = 0;
#endif
printf("original string: %s:\n", buf);
buf[strlen(buf) - 1] = '\0';
printf("after change: %s:\n", buf);
int i = 0;
char *array[100];
char *token1 = strtok(buf, " ");
while ((token1 != NULL)) {
array[i++] = token1;
token1 = strtok(NULL, " ");
}
// NOTE/BUG: execvp needs a NULL terminator
#if 1
array[i] = NULL;
#endif
for (int j = 0; j < i; j++) {
printf("Array value %d: %s:\n", j, array[j]);
}
// NOTE/BUG: wrong way to compare strings
#if 0
if (buf == "exit")
#else
if (strcmp(buf, "exit") == 0) {
printf("found it\n");
break;
}
#endif
for (int i = 1; i < argc; i++) {
pid_t pid;
if (argc >= 1) {
if ((pid = fork()) < 0) {
perror("fork");
}
// child process
else if (pid == 0) {
if (execvp(array[0], array) == -1) {
perror("execvp");
#if 0
return EXIT_FAILURE;
#else
exit(EXIT_FAILURE);
#endif
}
}
// parent process
else {
int status;
wait(&status);
printf("Please enter commands again: \n");
}
}
else {
fprintf(stderr, "Please specify the name of the program to exec as a command line argument\n");
return EXIT_FAILURE;
}
}
}
if (n == -1)
perror("read");
}
In the above code, I've used cpp conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
Note: this can be cleaned up by running the file through unifdef -k
I'm trying to read from the pipe once and print out the results, but I get a double output.
I thought the read and write sizes were incorrect (Why is the output printed twice with write() and not with print() in IPC using pipe?), but I printed out the size in the child before the write and then tried inputted the same size to the read function and I still get a double output.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
typedef struct
{
int PID;
char *filename;
int wordCount;
int lineCount;
int byteCount;
} filestr;
int pipe(int pd[2]);
void mywc(FILE *fp, char *name, filestr *fileA);
int isParam(char* fileName);
int getParam(int argCount, char **args);
void error_exit(char *s);
int main(int argc, char *argv[])
{
int pd[2], status, pid, param,
wordT=0, lineT=0, byteT=0;
filestr fileAtr;
//param = getParam(argc, argv);
//printf("PARAM: %d\n", param);
if(pipe(pd) == -1)
error_exit("pipe() failed");
for(int i = 1; i < argc; ++i)
{
pid = fork();
if(pid == -1)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{
FILE *file = fopen(argv[i], "r");
if(file != 0){
mywc(file, argv[i], &fileAtr);
wait(NULL);
close(pd[0]);
printf("SIZE: %d\n", sizeof(fileAtr));
if(write(pd[1], &fileAtr, sizeof(fileAtr)) == -1)
error_exit("write() failed");
}
exit(0);
}
}
int sum;
for(int j = 0; j < argc; ++j)
{
close(pd[1]);
if(read(pd[0], &fileAtr, (32*sizeof(int))) == -1)
error_exit("read() failed");
printf("PID : %d\n", fileAtr.PID);
printf("File Name : %s\n", fileAtr.filename);
printf("Words : %d\n", fileAtr.wordCount);
printf("Lines : %d\n", fileAtr.lineCount);
printf("Bytes : %d\n\n", fileAtr.byteCount);
wordT += fileAtr.wordCount;
lineT += fileAtr.lineCount;
byteT += fileAtr.byteCount;
}
printf("Grand Total: word: %d line: %d byte: %d\n", wordT, lineT, byteT);
}
void mywc(FILE *fp, char *name, filestr *fileA)
{
int c, lineCount=0, wordCount=0, byteCount=0;
while( (c = getc(fp)) != EOF )
{
if( c == ' ' )
{
wordCount++;
byteCount++;
}
else if ( c == '\n')
{
wordCount++;
byteCount++;
lineCount++;
}
else
{
byteCount++;
}
}
fileA->PID = getpid();
fileA->filename = name;
fileA->wordCount = wordCount;
fileA->lineCount = lineCount;
fileA->byteCount = byteCount;
}
int getParam(int argCount, char **args)
{
int param;
for(int i = 0; i < argCount; i++)
{
if((strcmp(args[i], "-lwc") == 0) || (strcmp(args[i], "-lcw") == 0) || (strcmp(args[i], "-wlc") == 0)
|| (strcmp(args[i], "-wcl") == 0) || (strcmp(args[i], "-cwl") == 0) || (strcmp(args[i], "-clw") == 0))
param = 3;
else if((strcmp(args[i], "-w") == 0))
param = 0;
else if((strcmp(args[i], "-l") == 0))
param = 1;
else if((strcmp(args[i], "-c") == 0))
param = 2;
}
return param;
}
void error_exit(char *s)
{
fprintf(stderr, "\nError: %s\n", s);
exit(1);
}
My Ouput:
SIZE: 32
PID : 14896
File Name : test
Words : 4
Lines : 4
Bytes : 26
PID : 14896
File Name : test
Words : 4
Lines : 4
Bytes : 26
Grand Total: word: 8 line: 8 byte: 52
EDIT: Added full code
The loop that prints your output
for(int j = 0; j < argc; ++j)
{
...
}
Goes from j = 0 instead of j = 1. argc is 2. So it prints the output twice.
A combination of two problems: First, for(int i = 1; i < argc; ++i) will run argc - 1 times, but for(int j = 0; j < argc; ++j) will run argc times. Second, if(read(pd[0], &fileAtr, (32*sizeof(int))) == -1) only handles one thing that read can do. In addition to failing with -1, it can also return 0 when it gets to EOF, or return some bytes, but fewer than you asked for (called a partial read). In your case, the EOF is happening, but your code doesn't notice, so it just happily goes along with the data that happened to already be there (in this case, a duplicate of the final result).
I'm creating my own Shell and I successfully got processes to run in the background by using my is_background function to find a &. It was working fine until i tried to implement redirection of standard output. The chk_if_output function is a part of this as well as the if statement if(out[0] == 1) in the process function. Somehow implementing redirection screwed up the way I implemented background process. If I comment out the redirection code it works again. I get a segmentation fault every time I try to run a background process with the redirection code in the program and I can't for the life of me figure out why. I haven't changed any of the background process code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_LINE 80 /* The maximum length command */
int is_background(char *args[], int size){
int background = 0;
if (strcmp(args[size-1 ], "&") == 0){
background = 1;
args[size-1] = NULL;
}
return background;
}
int * chk_if_output(char *args[], int size){
int * out = malloc(2);
out[0] = 0; out[1] = 0;
for (int i = 0; i < size; i++){
if (strcmp(args[i],">") == 0){
out[0] = 1;
out[1] = i;
break;
}
}
return out;
}
void process(char *command, char *params[], int size){
pid_t pid;
int background = is_background(params, size);
int *out = chk_if_output(params, size);
int fd;
int fd2;
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork Failed\n");
}else if (pid == 0) {
if(out[0] == 1){
for (int i = out[1]; i < size; i++){
params[i] = params[i+1];
}
fd = open(params[out[1]-1],O_RDONLY,0);
dup2(fd,STDIN_FILENO);
close(fd);
fd2 = creat(params[out[1]],0644);
dup2(fd2,STDOUT_FILENO);
close(fd2);
out[0] = 0;
out[1] = 0;
}
execvp(command, params);
}else {
if(background == 1){
waitpid(pid, NULL, 0);
}
background = 0;
}
}
int main(void) {
char *args[MAX_LINE/2 + 1]; /* command line arguments */
int should_run = 1; /* flag to determine when to exit program */
while (should_run) {
char *line;
char *endline;
printf("Leyden_osh>");
fgets(line, MAX_LINE*sizeof line, stdin);
if((endline = strchr(line, '\n')) != NULL){
*endline = '\0';
}
if (strcmp((const char *)line,"exit") == 0){
should_run = 0;
}
int i = 0;
args[i] = strtok(line, " ");
do{
args[++i] = strtok(NULL, " ");
}while(args[i] != NULL);
process(args[0], args, i);
fflush(stdout);
return 0;
}
In the chk_if_output() function, the last element of the array in the loop was NULL.
Fixed it by looping to size -1.
I'm trying to implement a C shell that allows for unlimited unidirectional pipes using the character '>'
So it can handle ls -A > tail > grep '.zip'
I understand that pipes are supposed to talk between processes, but I thought I came up with an idea that could use one pipe and multiple children.
This is what I have so far
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
/*#include <wait.h>*/
char *args[1000][1000];//array of arguments
int args_count = 0;//count of the arguments in the array
int runCommand(char **arguments, int *fd, int pipeHasSomeData, int baseCase) {
pid_t pid;
int x = 0;
int status;
pid = fork();
if(pid != 0) {
waitpid(pid, &status, 0);
if(baseCase) {
if(WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0)
{
/*it worked*/
} else if(WEXITSTATUS(status) == 255) {
printf("The program %s does not exist \n", arguments[0]);
} else {
printf("ERROR: Error code: %d", WEXITSTATUS(status));
}
}
else
{
printf("There was a problem that is not normal");
}
printf("\n \n");
}
return 1;
} else {
if(pipeHasSomeData == 1) {// read from the pipe
dup2(fd[0], 0);//read from pipe
}
if(baseCase == 0) {// not the base case
dup2(fd[1], 1);//write to pipe
} else {
close(fd[1]);//close write
}
exit(execvp(arguments[0], arguments));
return 0;
}
}
int execute_commands(char *arguments[1000][1000], int pd[2] = NULL) {
int current_count = args_count;
int iterator = 0;
int fd[2];
int useAPipeInCommand = 0;
pipe(fd);
while(iterator <= args_count) {//go through and execute all the commands
if(current_count == 0) {//base case
return runCommand(arguments[iterator], fd, useAPipeInCommand, 1);
} else {
runCommand(arguments[iterator], fd, useAPipeInCommand, 0);
useAPipeInCommand = 1;
}
iterator++;
current_count--;
}//end while
return 1;
}
int main () {
int i = 0;
char text[1024]; /* the input line */
char *tok2;
while (1) { /* repeat until done .... */
fflush(stdin);
fflush(stdout);
printf("Shell -> "); /* display a prompt */
*text = 0;
fgets(text, sizeof text, stdin); /* read in the command line */
fflush(stdout);
printf("\n");
char * tok = strtok(text, " \n\t");
if (strcmp(tok, "exit") == 0) { /* is it an "exit"? */
return 0; /* exit if it is */
}
if (strcmp(tok, " ") == 0) { /* is it an "exit"? */
continue; /* exit if it is */
}
tok2 = tok;
memset(args, 0, sizeof(args[0][0]) * 1000 * 1000);//clear the arguments array
args_count = 0;
int count = 0;
while(tok2 != NULL) {
if(strcmp(tok2, ">") != 0) {
args[args_count][count] = tok2;
count++;
tok2 = strtok(NULL, " \n\t");
} else {//pipe was found, up the argument counter and set count to 0
args[args_count][count] = NULL;
args_count++;
count = 0;
tok2 = strtok(NULL, " \n\t");
}
}
args[args_count][count] = NULL;
execute_commands(args);
}//end while
return 0;
}
It is running the single base case no problem but the shell freezes when I do a pipe. Any ideas on the issue?
Correct answer from Comments by #beau-bouchard and #rici:
Pipes have a (small) finite buffer; you cannot write more than a little bit to the pipe without blocking unless the other end of the pipe is being read.
For a correct implementation, check out "multiple pipes in C" Coding multiple pipe in C
--UPDATE:
Here is my final working code for anyone that is having a similar issue:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>
int READ = 0;
int WRITE = 1;
char *args[1000][1000];//array of arguments
int args_count = 0;//count of the arguments in the array
int execute_commands(char *arguments[1000][1000]) {
int pd[2];
int iterator = 0;
int fd[2];
int f_in = 0;
while(iterator <= args_count) {//go through and execute all the commands
pid_t pid;
int status;
pipe(fd);
pid = fork();
if(pid != 0) {
waitpid(pid, &status, 0);//wait for child to exit
close(fd[WRITE]);//close the writing end
if(WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0)
{
/*it worked*/
} else if(WEXITSTATUS(status) == 255) {
printf("The program %s does not exist \n", arguments[iterator][0]);
} else {
printf("ERROR: Error code: %d", WEXITSTATUS(status));
}
}
else
{
printf("There was a problem that is not normal %d", status);
}
f_in = fd[READ];//set the pipe to the in
if(iterator == args_count) {
printf("\n \n");
}
//return 1;
} else {
dup2(f_in, 0);
if(iterator != args_count) {//its not the main value
dup2(fd[WRITE], 1);//write to pipe
}
close(fd[READ]);
exit(execvp(arguments[iterator][0], arguments[iterator]));
return 0;
}
iterator++;
}//end while
return 1;
}
int main () {
int i = 0;
char text[1024]; /* the input line */
char *tok2;
while (1) { /* repeat until done .... */
fflush(stdin);
fflush(stdout);
printf("Shell -> "); /* display a prompt */
*text = 0;
fgets(text, sizeof text, stdin); /* read in the command line */
fflush(stdout);
printf("\n");
char * tok = strtok(text, " \n\t");
if (strcmp(tok, "exit") == 0) { /* is it an "exit"? */
return 0; /* exit if it is */
}
if (strcmp(tok, " ") == 0) { /* is it an "exit"? */
continue; /* exit if it is */
}
tok2 = tok;
memset(args, 0, sizeof(args[0][0]) * 1000 * 1000);//clear the arguments array
args_count = 0;
int count = 0;
while(tok2 != NULL) {
if(strcmp(tok2, ">") != 0) {
args[args_count][count] = tok2;
count++;
tok2 = strtok(NULL, " \n\t");
} else {//pipe was found, up the argument counter and set count to 0
args[args_count][count] = NULL;
args_count++;
count = 0;
tok2 = strtok(NULL, " \n\t");
}
}
args[args_count][count] = NULL;
execute_commands(args);
}//end while
return 0;
}
I'm trying to write a very very simple unix shell in C, and I have the basics of what I need working, except support for a history command. I have a global 2D char array that holds the history of all entered commands. Commands are added before the fork() system call, and I was originally printing out the value of the history global array after strings were added, and they were printing out correctly, so I'm not sure why it doesn't print out when the command "history" is used at the shell.
Thank to anyone who takes a look.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "myhistory.h"
int BUFFER_SIZE = 1024;
char history[100][80];
int command_index = 0;
int main(int argc, char *argv[]){
int status = 0;
int num_args;
pid_t pid;
while(1){
char *buffer_input, *full_input;
char command[BUFFER_SIZE];
char *args[BUFFER_SIZE];
printf("myshell> ");
buffer_input = fgets(command, 1024, stdin);
full_input = malloc(strlen(buffer_input)+1);
strcpy(full_input, buffer_input);
if (command_index >= 100) {
command_index = 0;
}
strncpy(history[command_index], full_input, strlen(full_input) + 1);
command_index += 1;
parse_input(command, args, BUFFER_SIZE, &num_args);
//check exit and special command conditions
if (num_args==0)
continue;
if (!strcmp(command, "quit" )){
exit(0);
}
if(!strcmp(command, "history")){
int i;
fprintf(stderr,"%d\n",(int)pid);
for(i = 0; i < command_index; i++){
fprintf(stdout, "%d: %s\n",i+1,history[command_index]);
}
continue;
}
errno = 0;
pid = fork();
if(errno != 0){
perror("Error in fork()");
}
if (pid) {
pid = wait(&status);
} else {
if( execvp(args[0], args)) {
perror("executing command failed");
exit(1);
}
}
}
return 0;
}
void parse_input(char *input, char** args,
int args_size, int *nargs){
char *buffer[BUFFER_SIZE];
buffer[0] = input;
int i = 0;
while((buffer[i] = strtok(buffer[i], " \n\t")) != NULL){
i++;
}
for(i = 0; buffer[i] != NULL; i++){
args[i] = buffer[i];
}
*nargs = i;
args[i] = NULL;
}
Change:
fprintf(stdout, "%d: %s\n",i+1,history[command_index]);
to:
fprintf(stdout, "%d: %s\n",i+1,history[i]);