I'm trying to write a shell program in c.
The program needs to have multiple processes created by fork function and be able to print multiple output line in one command.
For example, like linux terminal, if input is "ls ; ps ; pwd ;", the output should be like this.
$./shell
shell> ls ; ps ; pwd ;
(ls output)
(ps output)
(pwd output)
And it should be able to open a file and display the command list and the output that file contains.(batch mode I guess?)
Let's say these command lists are in the batch file.
batch
1 ls
2 ps
3 ls ; pwd ; ps
And the output is
$./shell batch
shell> ls
shell> (ls output)
shell> ps
shell> (ps output)
shell> ls ; pwd ; ps
shell> (ls output)
(ps output)
(pwd output)
Here's the code I wrote
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
void command(char* myargs[][10], char* buffer);
int tokenizing(char* myargs[][10], char* buffer);
int main(int argc, char* argv[]) {
int fd;
char buffer[200];
char* myargs[10][10];
char* token;
if(argc >= 2) {
if((fd=open(argv[1], O_RDONLY)) == -1)
printf("cannot open file\n");
else {
read(fd, buffer, 200);
printf("%s\n", buffer);
token = strtok(buffer, "\n");
while(token != NULL) {
printf("%s\n", token);
command(myargs, token);
token = strtok(NULL, "\n");
}
return 0;
}
}
while(1) {
printf("prompt> ");
if(fgets(buffer, 200, stdin) == NULL||
strcmp(buffer, "quit\n") == 0)
break;
command(myargs, buffer);
}
return 0;
}
void command(char* myargs[][10], char* buffer) {
int rc = fork();
if(rc < 0) {
fprintf(stderr, "fork failed\n");
} else if(rc == 0) {
int n = tokenizing(myargs, buffer);
for(int i = 0 ; i < n; i++) {
int rc2 = fork();
if(rc2 < 0) {
fprintf(stderr, "for failed\n");
} else if(rc2 == 0) {
execvp(myargs[i][0], myargs[i]);
printf("%s: command not found\n", myargs[i][0]);
exit(0);
} else {
wait(NULL);
}
}
exit(0);
}
else {
wait(NULL);
}
}
int tokenizing(char* myargs[][10], char* buffer) {
int i = 0;
int j = 0;
int k = 0;
char* token;
char* subCommand[10];
token = strtok(buffer, ";\n");
while(token != NULL) {
subCommand[k] = token;
k++;
token = strtok(NULL, ";\n");
}
for(int i = 0; i < k; i++) {
token = strtok(subCommand[i], " \n");
while(token != NULL) {
myargs[i][j] = token;
j++;
token = strtok(NULL, " \n");
}
myargs[i][j] = NULL;
j=0;
}
}
This code works fine but has some issues. When this code runs with a batch file, I struggle with some errors.
When the program is executed, I think the output should be like the above image file - as far as I know.
But frequently the program comes out with some weird command line that I didn't even type. These results just happen alternatively.
In addition, if you see 'ps' lists, you can see two shell programs are running.
Can you guys please help me solve these problems?
'strings' in C need a NUL terminator. Calling strng functions like printf("%s....") and strtok() on char arrays that are not securely NUL-terminated results in undefined behaviour.
read() returns a value. You can use it to load a terminator into 'buffer'. To be super-safe, you should attempt to read only [buffer size -1] chars to ensure that there will always be enough space for the terminator, eg:
buffer[read(fd, buffer, 199)]='\0';
Related
I am writing code for simple shell in C, and for some unknown reason I am getting error when trying to execute man command, while commands like ls, cat or other works fine. I want to have function for searching path of command.
This is error:
man: can't execute cat: No such file or directory
man: command exited with status 255: (cd /usr/share/man && /usr/lib/man-db/zsoelim) | (cd /usr/share/man && /usr/lib/man-db/manconv -f UTF-8:ISO-8859-1 -t ISO-8859-1//IGNORE) | (cd /usr/share/man && tbl) | (cd /usr/share/man && nroff -mandoc -rLL=88n -rLT=88n -Tutf8)
#include<string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdio.h>
char *searchPath(char *cmd) {
int poljelen = 2;
char **stringovi = malloc(poljelen * sizeof(char*));
char *path = getenv("PATH");
int i = 0;
while((stringovi[i] = strsep(&path, ":")) != NULL) {
i++;
if (i >= poljelen) {
poljelen = poljelen + i;
stringovi = realloc(stringovi, poljelen * sizeof(char*));
if (stringovi == NULL) {
fprintf(stderr, "%s", "Alloc error\n");
exit(0);
}
}
}
stringovi[i] = '\0';
i = 0;
while(*stringovi[i] != '\0') {
int pathlen = strlen(stringovi[i]);
int cmdlen = strlen(cmd);
char path[pathlen + cmdlen + 2];
strcpy(path, stringovi[i]);
strcat(path, "/");
strcat(path, cmd);
if (access(path, F_OK) == 0) {
char *pathname = malloc(sizeof(char) * (pathlen + cmdlen + 2));
if (!pathname) {
return NULL;
}
strcpy(pathname, path);
pathname[strlen(pathname)] = '\0';
return pathname;
}
else {
i++;
continue;
}
}
return NULL;
}
int main() {
char *tokens[3];
tokens[0] = "man"; //if you put here "ls"
tokens[1] = "ls"; // and here "-al" its working
tokens[2] = NULL;
char *pathh = searchPath(tokens[0]);
if (pathh == NULL) {
fprintf(stderr, "Error: command not found\n");
return 1;
}
char stringg[strlen(pathh) + 1];
strcpy(stringg, pathh);
pid_t childP;
int status = 0;
childP = fork();
if (childP == 0) {
if (execv(stringg, tokens) == -1) {
fprintf(stderr, "Error: execv failed: %s\n", strerror(errno));
exit(1);
}
}
else if (childP == -1) {
fprintf(stderr, "Error: fork failed: %s\n", strerror(errno));
return 1;
}
else {
waitpid(childP, &status, WUNTRACED);
}
}
Your use of strsep() corrupts your PATH environment variable.
Per the Linux strsep() man page:
... this function finds the first token in
the string *stringp, that is delimited by one of the bytes in the
string delim. This token is terminated by overwriting the
delimiter with a null byte ('\0') ...
By calling strsep() with the value returned from getenv("PATH") you corrupt your PATH environment variable, leaving it as only the first component of the original value.
You should make a copy of the value returned from getenv("PATH") and split that into tokens.
I've been writing a shell program in C. The program is working as expected in Linux (Ubuntu 16.04) but I'm getting unexpected output in MacOS (10.14.2 Mojave).
/* A shell program.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void input(char* argv[]);
void print_arr(char *argv[]); // For debugging
int
main(void)
{
while (1)
{
pid_t pid;
char *argv[100];
// Display shell prompt
write(1, "(ash) $ ", 8);
// Take user input
input(argv);
// print_arr(argv); // DEBUG STATEMENT
if (argv[0] != NULL)
{
// Exit if exit command is entered
if (strcmp(argv[0], "exit") == 0)
{
exit(0);
}
// Create child process
if ((pid = fork()) > 0)
{
wait(NULL);
}
else if (pid == 0)
{
// print_arr(argv); // DEBUG STATEMENT
execvp(argv[0], argv);
printf("%s: Command not found\n", argv[0]);
exit(0);
}
else
{
printf("Fork Error!\n");
}
}
}
}
/* Takes input from user and splits it in
tokens into argv. The last element in
argv will always be NULL. */
void
input(char* argv[])
{
const int BUF_SIZE = 1024;
char buf[BUF_SIZE];
int i;
buf[0] = '\0';
fgets((void*) buf, BUF_SIZE, stdin);
i = 0;
argv[i] = strtok(buf, " \n\0");
while (argv[i] != NULL)
{
argv[++i] = strtok(NULL, " \n\0");
}
}
/* Print argv for debugging */
void
print_arr(char *argv[])
{
int i = 0;
while (argv[i] != NULL)
{
printf("%d: %s\n", i, argv[i]);
++i;
}
}
In Linux:
(ash) $ ls
// files and folders are listed
In MacOS (with debug statements):
(ash) $ ls
0: p?M??
0: ??M??
: Command not found
(ash) $ ls
0: ls
0: ??M??
: Command not found
(ash) $ ls
0: ls
0: ??M??
I don't understand that why are the contents of char* argv[] getting modified across fork()?
I've also tried it in the default clang compiler and brew's gcc-4.9, the results are same.
When a program behaves different for no good reason, that's a VERY good sign of undefined behavior. And it is also the reason here.
The array buf is local to the function input and ceases to exist when the function exits.
One way of solving this is to declare buf in main and pass it to input. You will also need the size of the buffer for fgets.
void
input(char * argv[], char * buf, size_t size)
{
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
argv[0] = strtok(buf, " \n\0");
for(int i=0; argv[i] != NULL; i++) argv[i+1] = strtok(NULL, " \n\0");
}
Another solution (although I suspect many will frown upon it) is to declare buf as static, but then you would need to change BUF_SIZE to a #define or a hard coded value, since you cannot have a static VLA.
#define BUF_SIZE 1024
void
input(char * argv[])
{
static char buf[BUF_SIZE];
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
argv[0] = strtok(buf, " \n\0");
for(int i=0; argv[i] != NULL; i++) argv[i+1] = strtok(NULL, " \n\0");
}
I removed the cast to void* since it's completely unnecessary. I also changed the while loop to a for loop to make the loop variable local to the loop.
I am trying to work with command line arguments and parsing a text file in C. Basically I want to be able to put in two numbers, like, 1 and 4 and have it read a column of a text file then print it to stdout. I want to be able to do something like take this:
PID TTY TIME CMD
449 ttys000 0:00.35 -bash
1129 ttys001 0:00.35 -bash
25605 ttys001 0:00.15 vi prog.c
6132 ttys002 0:00.11 -bash
6208 ttys002 0:00.03 vi test
And do:
./your_prog 1 4 < data.txt
PID CMD
449 bash
1129 -bash
25605 vi
6132 -bash
6208 vi
So I need to enter the the columns i want to print out, redirect the file in "data.txt" and have it process the file and print like so.
So far I have this for my code:
#include <stdio.h>
int main(int argc, char **argv){
//int row = argc[0];
//int col = argc[1];
//if number entered is less than one, re-enter
int i;
for (i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
if(argc < 1){
fprintf(stderr, "Enter a valid input");
//quit
return 1;
}
else{
char ch[256];
//ch[255] = '\0';
while(fgets(ch, 256, stdin) != NULL){
printf("%s", ch);
}
}
return 1;
}
but am not sure if I am on the correct track and am confused as to what to do next. I am new to C, so I apologize if this is an easy question.
To get you started, this improves the way you gather the command line information. Note the first argument argv[0] is the name of the executable itself. Bear in mind comments above, I haven't improved any further. Syntax is progname filename col1 col2
#include <stdio.h>
#include <stdlib.h>
void fatal(char *msg) {
printf("%s\n", msg);
exit (1);
}
int main(int argc, char*argv[])
{
int col1, col2;
FILE *fp;
char ch[256];
if(argc < 4)
fatal("Need three arguments");
if (sscanf(argv[2], "%d", &col1) != 1)
fatal("Argument 1 error");
if (sscanf(argv[3], "%d", &col2) != 1)
fatal("Argument 2 error");
printf("Read columns %d and %d from file %s\n", col1, col2, argv[1]);
if ((fp = fopen(argv[1], "r")) == NULL)
fatal("Unable to open file");
// process the file
while (fgets(ch, 256, fp) != NULL) {
printf("%s", ch);
}
fclose(fp);
return 0;
}
Here is what I have so far. I can get it to parse apart then I can put it back how it was, but for the life of me I cant figure out how to tell it to print only the first column. If I could have some direction, then I could figure out how to do the rest, but I can find anything. Here is what code I have so far:
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
int col1, col2;
int size = 512;
char ch[size];
char *temp[size];
char *token;
if(argc == 1){
fprintf(stderr, "I need more!\n");
return 1;
}
else{
//test to see what is stored
int i;
for (i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
if(sscanf(argv[1], "%d", &col1) != 1) return 1;
if(sscanf(argv[2], "%d", &col2) != 1) return 1;
while(fgets(ch, size, stdin) != NULL){
//get 1st token
token = strtok(ch, " ");
while(token != NULL){
printf(" %s", token);
temp[i++] = token;
token = strtok(NULL, " ");
}
}
return 0;
}
}
I have an assignment for school where I have to create a shell which can do the following:
read incoming command, parse each part of command
fork child process and execute each command without (< > >> |)
successfully execute each command with <, >, >>
successfully execute each command with |
I am seriously lost... I am new to shell and I have no clue on what to do from here.
My code gives me an error stating segmentation fault (core dumped). Any and all help will be greatly appreciated.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define MAX_ARG 10
int main()
{
char line [256];
char prompt[] = "sh2 % ";
char command[256], *args[MAX_ARG];
int pid;
char status;
int i;
/* spit out the prompt */
printf("%s", prompt );
while( fgets(line, sizeof line, stdin) != NULL)
{
/* fgets leaves '\n' in input buffer. ditch it */
line [strlen(line)-1] = '\0';
while (line != NULL)
{
//parse command and arg
args[0] = strtok(line, " "); //grab command
for (i=1; i<MAX_ARG; i++) //grab arguments, to assume max = 10?
{
//if a single command with arguments then set command & argument
//for (i>0)
{
// check to see if the command is 'exit'
if(!strcmp("exit", args[i]))
{
exit(0);
}
{
int p[2];
pipe(p)
if (fork() == 0) //child
{
close (0);
dup(p[0]);
exec("cmd2");
}
else
{
close(1);
close(p[0]);
close(p[1]);
dup(p[1]);
exec("cmd1");
}
close(0);
open("stdout.txt", "r");
if (fork()== 0)
{
exec("cmd3");
}
}
else if (!strcmp(">", args[i]))
open("stderr.txt". "w")
if (fork() == 0)
{
exec("cmd1");
}
}
else if (!strcmp(">>", args[i]))
{
close(1);
open("stdout_stderr.txt", "w");
if (fork() == 0)
{
close(2);
dup(1);
exec("cmd2");
}
}
else
{
pid = fork();
if (pid == 0)
{
status = execvp(command,args);
exit(0);
}
else
{
waitpid(-1);
}
}
}
}
}
}
return 0;
}
You have quite a few things wrong in your program, so it's hard to point to one line and say change this. I think you are trying to do too much at once and not building on a solid base.
In programming you want to start small and build on your progress, your professor did you a favor by lining up the steps:
read incoming command, parse each part of command
fork child process
and execute each command without (< > >> |)
successfully execute
each command with <, >, >>
successfully execute each command with |
Try to get #1 working befor moving on to #2 and as mentioned before using functions is going to help a lot. I would suggest looking at this post http://bytes.com/topic/c/answers/215994-writing-shell-c which will give you a simple shell you could model from. Here is a baseline parser to get you started on #1 (based on the post mentioned)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char line[4096] = {0};
char safeline[4096] = {0};
int done = 0;
int i = 0;
char *s = line;
char **t = NULL;
char *prompt = ">";
char *args = NULL;
char *nl = NULL;
int main(void)
{
while(!done)
{
printf("%s", prompt);
fflush(stdout);
if(NULL == fgets(line, sizeof line, stdin))
{
t = NULL;
done = 1;
}
else
{
nl = strchr(line, '\n');
if(nl != NULL)
{
*nl = '\0';
strcpy(safeline, line);
}
else
{
int ch;
printf("Line too long! Ignored.\n");
while((ch = getchar()) != '\n' && ch != EOF)
{
continue;
}
if(ch == EOF)
{
done = 1;
}
}
args = strchr(line, ' ');
if(args != NULL)
{
*args++ = '\0';
}
if(!done)
{
printf("command - %s : args - %s\n",s, args);
}
}
}
}
I have the following problem: In my code, here in line 83, I have this: check = wait(NULL);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include <sys/stat.h>
#include <sys/types.h>
//---------------------------------
//Function: parse_cmdline(const char* cmdline)
//This function takes the input from stdin
//and returns it as array of strings.
//If stdin is /bin/ls -l /usr/include
//the function will return ["/bin/ls","-l","/usr/include"]
//---------------------------------
char** parse_cmdline(const char* cmdline) {
int count, word_count = 0;
char** line_parsed, line_return;
char *pch, *cmdline_copy = (char*)malloc(sizeof(char)*(strlen(cmdline)+1));
strcpy(cmdline_copy, cmdline);
pch = strtok(cmdline_copy," \n\t\r");
while (pch != NULL) {
++word_count;
pch = strtok(NULL, " \n\t\r");
}
line_parsed = (char**)malloc((word_count+1)*sizeof(char*));
count = 0;
strcpy(cmdline_copy, cmdline);
pch = strtok(cmdline_copy," \n\t\r");
while (pch != NULL) {
line_parsed[count] = (char*)malloc((strlen(pch) + 1)*sizeof(char));
strcpy(line_parsed[count], pch);
++count;
pch = strtok(NULL," \n\t\r");
}
line_parsed[count] = NULL;
free(cmdline_copy);
return line_parsed;
}
int main() {
int count = 0, check;
size_t size;
char* line;
char** cmdline;
while(1) {
check = 0;
printf("$Monkey Eats:< ");
getline(&line, &size, stdin);
cmdline = parse_cmdline(line);
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
} else if(pid == 0) {
struct stat _stat;
stat(cmdline[0],&_stat);
if(_stat.st_mode & S_IXUSR){
execvp(cmdline[0], cmdline);
}else fprintf(stderr,"%s: Permission denied!\n",cmdline[0]);
perror("");
exit(1);
}else {
check = wait(NULL);
}
count = 0;
while(cmdline[count] != NULL) {
free(cmdline[count]);
++count;
}
free(cmdline);
}
return 0;
}
It makes me a problem. When I run it and when I type a command I have the following message:
$Monkey Eats:< ls
ls: Permission denied!
No such file or directory
If I have only wait(NULL); the program runs normally without a problem. Can somebody tell me what is the problem? Thank you :)
The problem is trying to run ls. execvp() doesn't know where ls is. Try running /bin/ls as your command.
The problem is: stat(cmdline[0],&_stat); - the return code is not checked. What if file not found ? The program continues, and finds that _stat.st_mode & S_IXUSR is 0 (randomly).
However you may test the program as is with "/bin/ls" as input..