I am making a simple shell. It also needs to be able to read text files by lines. This is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
// Exit when called, with messages
void my_exit() {
printf("Bye!\n");
exit(0);
}
int main(void) {
setvbuf(stdout, NULL, _IONBF, 0);
// Char array to store the input
char buff[1024];
// For the fork
int fid;
// Get all the environment variables
char dir[50];
getcwd(dir,50);
char *user = getenv("USER");
char *host = getenv("HOST");
// Issue the prompt here.
printf("%s#%s:%s> ", user, host, dir);
// If not EOF, then do stuff!
while (fgets(buff, 1024, stdin) != NULL) {
// Get rid of the new line character at the end
// We will need more of these for special slash cases
int i = strlen(buff) - 1;
if (buff[i] == '\n') {
buff[i] = 0;
}
// If the text says 'exit', then exit
if (!strcmp(buff,"exit")) {
my_exit();
}
// Start forking!
fid = fork();
// If fid == 0, then we have the child!
if (fid == 0) {
// To keep track of the number of arguments in the buff
int nargs = 0;
// This is a messy function we'll have to change. For now,
// it just counts the number of spaces in the buff and adds
// one. So (ls -a -l) = 3. AKA 2 spaces + 1. Really in the
// end, we should be counting the number of chunks in between
// the spaces.
for (int i = 0; buff[i] != '\0'; i++) {
if (buff[i] == ' ') nargs ++;
}
// Allocate the space for an array of pointers to args the
// size of the number of args, plus one for the NULL pointer.
char **args = malloc((sizeof(char*)*(nargs + 2)));
// Set the last element to NULL
args[nargs+1] = NULL;
// Split string into tokens by space
char *temp = strtok (buff," ");
// Copy each token into the array of args
for (int i = 0; temp != NULL; i++) {
args[i] = malloc (strlen(temp) + 1);
strcpy(args[i], temp);
temp = strtok (NULL, " ");
}
// Run the arguments with execvp
if (execvp(args[0], args)) {
my_exit();
}
}
// If fid !=0 then we still have the parent... Need to
// add specific errors.
else {
wait(NULL);
}
// Issue the prompt again.
printf("%s#%s:%s> ", user, host, dir);
}
// If fgets == NULL, then exit!
my_exit();
return 0;
}
When I run it alone as a shell, it works great. When I run ./myshell < commands.txt, it does not work.
commands.txt is:
ls -l -a
pwd
ls
But the output is:
>Bye!
>Bye!
>Bye!
>Bye!
>Bye!
>Bye!>Bye!
>Bye!
>Bye!
>Bye!
Doesn't even run my commands. Any ideas? I thought my while loop was pretty simple.
I don't know if this is the problem, but you (correctly) mention in a comment that you have to allocate "plus one for the NULL pointer" in the *args array.
However, you don't actually set the last pointer in *args to NULL.
execvp() won't like that.
That doesn't explain why there might be a difference between redirected vs. non-redirected input, other than undefined behavior is a bastard.
Sorry everyone - turns out my text file was in some sort of demented format from Mac's TextEdit GUI. Everything is working great.
I really appreciate all of the helpful responses
Related
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;
}
This is the code I made so far. I apologize if my buffer sizes are an overkill.
The idea is to read the entire configuration file (in this example, it's file.conf), and for now we assume it exists. I'll add error checking later.
Once the file is read into stack space, then the getcfg() function searches the configuration data for the specified name, and if it's found, returns the corresponding value. My function works when the configuration file contains leading spaces before names or values; such spaces are ignored.
Say this is my configuration file:
something=data
apples=oranges
fruit=banana
animals= cats
fried =chicken
My code will work correctly with the first four entries of the config file. for example, if I use "something" as the name, then "data" will be returned.
The last item won't work as of yet because of the trailing spaces after "fried" and before the =. I want to be able to have my function automatically remove those spaces, too, especially in case an option format such as
somethingelse = items
begins to be used. (Note the spaces on both sides of the = sign.)
What can I do to make a less CPU-intensive version of my program that also detects and removes trailing spaces from the name and value when processing the name and values?
Here's my current code:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int getcfg(char* buf, char *name, char *val) {
int fl = 0, n = 0;
char cfg[1][10000], *p = buf;
memset(cfg, 0, sizeof(cfg));
while (*p) {
if (*p == '\n') {
if (strcmp(cfg[0], name) == 0) {
strcpy(val, cfg[1]);
return 1;
}
memset(cfg, 0, sizeof(cfg));
n = 0;
fl = 0;
} else {
if (*p == '=') {
n = 0;
fl = 1;
} else {
if (n != 0 || *p != ' ') {
cfg[fl][n] = *p;
n++;
}
}
}
p++;
}
return 0;
}
int main() {
char val[10000], buf[100000]; //val=value of config item, buf=buffer for entire config file ( > 100KB config file is nuts)
memset(buf, 0, sizeof(buf));
memset(val, 0, sizeof(val));
int h = open("file.conf", O_RDONLY);
if (read(h, buf, sizeof(buf)) < 1) {
printf("Can't read\n");
}
close(h);
printf("Value stat = %d ", getcfg(buf, "Item", val));
printf("Result = '%s'\n", val);
return 0;
}
Behold is a small (~15 lines) sscanf-based read_params() function which does the job. As a bonus, it understands the comments and complains about erroneous lines (if any):
$ cat config_file.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/errno.h>
#define ARRAY_SIZE(a) ((sizeof (a)) / (sizeof (a)[0]))
enum { MAX_LEN=128 };
struct param {
char name[MAX_LEN];
char value[MAX_LEN];
};
void strtrim(char *s)
{
char *p = s + strlen(s);
while (--p >= s && isspace(*p))
*p = '\0';
}
int read_params(FILE *in, struct param *p, int max_params)
{
int ln, n=0;
char s[MAX_LEN];
for (ln=1; max_params > 0 && fgets(s, MAX_LEN, in); ln++) {
if (sscanf(s, " %[#\n\r]", p->name)) /* emty line or comment */
continue;
if (sscanf(s, " %[a-z_A-Z0-9] = %[^#\n\r]",
p->name, p->value) < 2) {
fprintf(stderr, "error at line %d: %s\n", ln, s);
return -1;
}
strtrim(p->value);
printf("%d: name='%s' value='%s'\n", ln, p->name, p->value);
p++, max_params--, n++;
}
return n;
}
int main(int argc, char *argv[])
{
FILE *f;
struct param p[32];
f = argc == 1 ? stdin : fopen(argv[1], "r");
if (f == NULL) {
fprintf(stderr, "failed to open `%s': %s\n", argv[1],
strerror(errno));
return 1;
}
if (read_params(f, p, ARRAY_SIZE(p)) < 0)
return 1;
return 0;
}
Let's see how it works (quotes mark the beginning and the end of each line for clarity):
$ cat bb | sed -e "s/^/'/" -e "s/$/'/" | cat -n
1 'msg = Hello World! '
2 'p1=v1'
3 ' p2=v2 # comment'
4 ' '
5 'P_3 =v3'
6 'p4= v4#comment'
7 ' P5 = v5 '
8 ' # comment'
9 'p6 ='
$ ./config_file bb
1: name='msg' value='Hello World!'
2: name='p1' value='v1'
3: name='p2' value='v2'
5: name='P_3' value='v3'
6: name='p4' value='v4'
7: name='P5' value='v5'
error at line 9: p6 =
Note: as an additional bonus, the value can be anything, except #\n\r chars, including spaces, as can be seen above with the 'Hello World!' example. If it's not what needed, add space and tab into the exception list at the second sscanf() for the value (or specify accepted characters there instead) and drop strtrim() function.
I'll provide a straight-forward version, with everything being done in main and no key:value saving - the function only recognizes where they are and print them. I used the input file you gave and added one more line in the end as something = more_data.
This version of the parser does not recognize multiple data itens (itens separated by spaces in the data fields, you'll have to figure it out as an exercise).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd = open("file.conf", O_RDONLY, 0);
int i = 0;
char kv[100];
char c;
while (read(fd,&c,1) == 1) {
/* ignoring spaces and tabs */
if (c == '\t' || c == ' ') continue;
else if (c == '=') {
/* finished reading a key */
kv[i] = 0x0;
printf("key found [%s] ", kv);
i = 0;
continue;
} else if (c == '\n') {
/* finished reading a value */
kv[i] = 0x0;
printf(" with data [%s]\n", kv);
i = 0;
continue;
}
kv[i++] = c;
}
close(fd);
return 0;
}
And the output is:
key found [something] with data [data]
key found [apples] with data [oranges]
key found [fruit] with data [banana]
key found [animals] with data [cats]
key found [fried] with data [chicken]
key found [something] with data [more_data]
Explanation
while (read(fd,&c,1) == 1): reads one character at a time from the file.
if (c == '\t' || c == ' ') continue;: this is responsible for ignoring the white-spaces and tabs wherever they are.
else if (c == '='): If the program finds a = character, it concludes that what it just read was a key and treats it. What's inside that if should be easy to understand.
else if (c == '\n'): Then it uses a new-line character to recognize the end of a value. Again, what's inside the if is not hard to understand.
kv[i++] = c;: This is where we save the char value into the buffer kv.
So, with some minor changes, you can adapt this bit of code to become a parsing function that will suit your needs.
Edit and new code
As pointed out by John Bollinger in the comments, using read inside a while to read one character at a time is very costly. I'll post a second version of the program using the same input method OP was using (reading the whole file at once into a buffer) and then parsing it with another function.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void parse(char *s)
{
char c, kv[100];
int i;
while ((c = *s++)) {
/* ignoring spaces and tabs */
if (c == '\t' || c == ' ') continue;
else if (c == '=') {
/* finished reading a key */
kv[i] = 0x0;
printf("key found [%s] ", kv);
i = 0;
continue;
} else if (c == '\n') {
/* finished reading a value */
kv[i] = 0x0;
printf(" with data [%s]\n", kv);
i = 0;
continue;
}
kv[i++] = c;
}
}
int main(void)
{
int fd = open("file.conf", O_RDONLY, 0);
char buffer[1000];
/* use the reading method that suits you best */
read(fd, buffer, sizeof buffer);
/* only thing parse() expects is a null-terminated string */
parse(buffer);
close(fd);
return 0;
}
It is very unusual to read a whole config file into memory as a flat image, and especially to keep such an image as the internal representation. One would ordinarily parse the file contents into key/value pairs as you go, and store a representation of those pairs.
Also, your use of read() is incorrect, as you cannot safely assume that it will read all bytes of the file in one call. One normally must call read() in a loop, keeping track of the return value from each call to know both when the end of the file is reached and where in the buffer to put the next bytes read.
If the configuration is supposed to be completely generic, so that you don't know in advance what keywords to expect, then you might organize the configuration data in a hash table or a binary search tree, with the parameter names as the keys. If you do know what parameters to expect (or at least which to allow), then you might have a variable or a struct member for each one.
Naturally, the approach to parameter lookup must be paired correctly with the data structure in which you store the parameters. Any of the approaches I suggested will make looking up multiple configuration parameters far faster. They would also avoid wasting memory, and would adapt to extremely large configurations (or at least could do so).
How best to approach reading the file depends on details of your config file format, such as whether keys and/or values are permitted to contain internal spaces, whether more than one key/value pair may appear on the same line, and whether there is an upper bound on the allowed length of config file lines or of keys and values. Here's an approach that expects one key/value pair per line, supports keys and values that contain internal whitespace (but not newlines), but neither of which is longer than 1023 characters, and where keys are not permitted to contain the '=' character:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
int main() {
char key[1024];
char value[1024];
FILE *config;
int done;
config = fopen("file.conf", "r");
if (!config) {
perror("while opening file.conf");
return 1;
}
do {
char nl = '\0';
int nfields = fscanf(config, " %1023[^=\n]= %1023[^\n]%c", key, value, &nl);
int i;
done = 1;
if (nfields == EOF) {
if (ferror(config)) {
/* handle read error ... */
perror("while reading file.conf");
} else {
/* trailing empty line(s); ignore ... */
}
break;
} else if (nfields == 3) {
if (nl != '\n') {
/* handle excessive-length value ... */
} else {
done = 0;
}
} else if (nfields == 1) {
/* handle excessive-length key ... */
break;
} else {
assert(nfields == 2);
/* last key/value pair, not followed by a newline */
}
if (key[0] == '=') {
/* handle missing key ... */
break;
}
/* successfully read a key / value pair; truncate trailing whitespace */
for (i = strlen(key); key[--i] == ' '; ) {
/* nothing */
}
key[i + 1] ='\0';
for (i = strlen(value); value[--i] == ' '; ) {
/* nothing */
}
value[i + 1] ='\0';
/* record the key / value pair somewhere (but here we just print it) ... */
printf("key: [%s] value: [%s]\n", key, value);
} while (!done);
fclose(config);
return 0;
}
Important points to note about that include:
No mechanism for storing the key / value pairs is provided. I gave you a few options, and there are others, but you must decide what's best for your own purposes. Rather, the program above addresses the problem of parsing your config data once for all, so that you can avoid parsing it de novo every time you perform a lookup.
The code relies on fscanf() to consume any leading whitespace before the key and value, but in order to accommodate internal whitespace in the key and value, it cannot do the same for trailing whitespace.
Instead, it manually trims trailing whitespace from key and value.
The fscanf() format uses explicit field widths to avoid buffer overruns. It uses the %[ and %c field descriptors to scan data that may be or include whitespace.
Although it may look longish, do note how much of that code is dedicated to error handling.
Divide and conquer.
Getting the data and parsing it are best handled with 2 separate routines.
1) Use fgets() or other code with read() to read a line
int foo(FILE *inf) {
char buffer[1000];
while (fgets(buffer, sizeof buffer, inf)) {
if (Parse_KeyValue(buffer, &key_offset, &value_offset)) {
fprintf(stderr, "Bad Line '%s'\n", buffer);
return 1;
}
printf("'%s'='%s'\n", &buffer[key_offset], &buffer[value_offset]);
}
}
2) Parse the line. (Sample unchecked code)
// 0: Success
// 1: failure
int Parse_KeyValue(char *line, size_t *key_offset, size_t *value_offset) {
char *p = line;
while (isspace((unsigned char) *p)) p++;
*key_offset = p - line;
const char *end = p;
while (*p != '=') {
if (*p == '\0') return 1; // fail, no `=` found
if (!isspace((unsigned char) *p)) {
end = p+1;
}
p++;
}
*end = '\0';
p++; // consume `=`
while (isspace((unsigned char) *p)) p++;
*value_offset = p - line;
end = p;
while (*p) {
if (!isspace((unsigned char) *p)) {
end = p+1;
}
p++;
}
*end = '\0';
return 0;
}
This does allow for valid "" key and value. Adjust as needed.
For this Shell program i'm using the functions strtok (see fragmenta.h code) to parsing a string which is introduced by user.
I need to remove the blanks with strotk function and introduce those on a struct of an array of pointers. This are made in fragmenta.h
In the main program (shell.c), is necessary to introduce the string, this one is passed to fragmenta and stored on char **arg. After that, i use the execvp function to execute the command.
The problem is that the program store the whole command, but only execute the first individual command. For example, if we introduce "ls -al", only execute the ls command so i understand that is a problem on the pointer.
Main program shell.c
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include "fragmenta.h"
//
char cadena[50];
int pid;
int i, status;
char **arg;
pid_t pid;
//
main()
{
printf("minishell -> ");
printf("Introduce the command \n");
scanf("%[^\n]", cadena);
if (strcmp(cadena, "exit") == 0)
{
exit(0);
}
else
{
pid = fork();
if (pid == -1)
{
printf("Error in fork()\n");
exit(1);
}
else if (pid == 0) //child proccess
{
arg = fragmenta(cadena);
if (execvp(*arg, arg) < 0) /* execute the command */
{
printf("*** ERROR: exec failed\n");
exit(1);
}
}
else /* for the parent: */
{
while (wait(&status) != pid);
}
}
}
int len;
char *dest;
char *ptr;
char *aux;
char **fragmenta(const char *cadena)
{
//
char *token;
int i = 0;
//
len = strlen(cadena);
char *cadstr[len + 1];
dest = (char *)malloc((len + 1) * sizeof(char));
strcpy(dest, cadena);
//printf("Has introducido:%s\n",dest);
token = strtok(dest, " ");
while ( token != NULL)
{
cadstr[i] = malloc(strlen(token) + 1);
strcpy(cadstr[i], token);
token = strtok(NULL, " ");
i++;
}
*cadstr[i] = '\0';
ptr = *cadstr;
i = 0;
while (cadstr[i] != NULL)
{
//printf("almacenado: %s\n",cadstr[i]);
i++;
}
return &ptr;
}
You've got at least two problems.
The first one is this:
ptr=*cadstr;
You've gone through all that trouble to create an array of arguments, and then you just copy the first argument and return a pointer to that copy.
You could just get rid of ptr and return cadstr, except that it's a local variable, so as soon as the function returns, it can be overwritten or deallocated.
Since you're storing everything else in the universe as globals for some reason, the obvious fix to that is to make cadstr global too. (Of course you can't use a C99 runtime-length array that way, but since you've written your code to guarantee a buffer overrun if the input is more than 50 characters, you can safely just allocate it to 50 strings.)
A better solution would be to initialize a new array on the heap and copy all of cadstr into it. Or just initialize cadstr on the heap in the first place.
Second, you never append a NULL to the end of cadstr. Instead, you do this:
*cadstr[i] = '\0';
That leaves the last element in cadstr pointing to whatever uninitialized pointer it was pointing to, but modifies the first byte of whatever that is to be a 0. That could corrupt important memory, or cause a segfault, or be totally harmless, but the one thing it can't do is set cadstr[i] to point to NULL.
When you check this:
i = 0;
while (cadstr[i] != NULL)
i++;
… you only get out of that loop because of luck; you read right past the end of the allocated array and keep going until some other structure or some uninitialized memory happens to be sizeof(void*) 0s in a row.
And when you pass the same thing to execvp, who knows what it's going to do.
You're also declaring main without a prototype, which is deprecated, so you'll probably get a warning for it from any compiler that accepted the rest of your code. To fix that, just do int main().
Making a simple -type shell, using fork and execvp functions to run the commands from the stdin line.
However, things like ls work, but not ls -all -S.
It will execute ls, but nothing will be printed for ls -all
The only idea I can come up with is that there is a "\n" somewhere in the command, but I don't know how to get it out or even where it is....
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
//Libs ^^^^ Defs vvvvvvvv
#define comlen 4096 //Max command length
#define comarg 32 //Max argument length
int main(int argc, char *argv[])
{
char buff; //command buffer
char* comand[comlen];
int i;
do
{
i = 0;
printf("simsh: ");
char* whtspc = strtok (fgets(&buff, comlen, stdin)," "); //get input and tokenize
printf("[%lu] :: %s------------\nEND OF BUFF TEST\n", strlen(&buff), &buff);
while (whtspc != NULL)
{
comand[i]=(char*)malloc((sizeof(char)*strlen(whtspc))); //alloctie mem for commands
strncpy(comand[i], whtspc, strlen(whtspc)-1); //coppy comand token to array index i
whtspc = strtok (NULL, " "); //grab next token
i++; //incriment
/*trying to change new line character to NULL so that commands can be passed properly*/
// if (comand[strlen(comand[i]) - 1] == "\n")
// {
// comand[strlen(comand[i]) - 1] = '\0';
// }
//breka out incase index oversteps
if (i == 4096)
break;
}
//last entry in command should be null
comand[i] = NULL;
//fork to run in background
pid_t pid = fork();
if (pid == 0)
{
//testing and pass comands to execvp
printf("START OF COMAND TEST\n!!!!!!!!!%s!!!!!!!!!!!!!!!!\n %lu\nEND OF COMAND TEST\n\n",comand[1], strlen(comand[0]));
execvp(comand[0], &comand);
}
else
{
//parent wait on child.
waitpid(pid, &i, WUNTRACED | WCONTINUED);
}
}
while(1);
return 0;
}
Any help would be welcomed.
If it helps at all , here is the terminal output of the code::
simsh: ls
[3] :: ls
------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!(null)!!!!!!!!!!!!!!!!
2
END OF COMAND TEST
chop_line.c chop_line.h list.c list.h Makefile Makefile~ One simsh1 simsh1.c simsh1.c~
simsh: ls -all
[2] :: ls------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!-all!!!!!!!!!!!!!!!!
1
END OF COMAND TEST
simsh: echo
[5] :: echo
------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!(null)!!!!!!!!!!!!!!!!
4
END OF COMAND TEST
simsh: echo all
[4] :: echo------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!all!!!!!!!!!!!!!!!!
3
END OF COMAND TEST
simsh: echo echo
[4] :: echo------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!echo!!!!!!!!!!!!!!!!
3
END OF COMAND TEST
The first argument to fgets should be a pointer to a buffer where the string is copied into. You are passing a pointer to a single char.
Second, execvp expects two arguments: a filename and a null-terminated list of command-line arguments which, by convention, starts with the filename itself.
I took the liberty to make some modifications to your code, both fixing the issues I pointed above and making it a little more readable.
Note that there's a memory leak in the code below (fix it :). There might be other issues that I didn't notice.
I implemented a shell a while ago; if you want to take a look, my GitHub URL is in my profile (BEWARE: ugly college homework code).
Hope it helps!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#define COMLEN 4096
#define COMARG_N 32
#define TRUE 1
int main(int argc, char *argv[])
{
char *token;
char *args[COMARG_N];
char *buff;
int i;
pid_t pid;
while(TRUE) {
printf("simsh: ");
buff = (char*) malloc(sizeof(char) * COMLEN);
fgets(buff, COMLEN, stdin);
if (buff[strlen(buff) - 1] == '\n')
buff[strlen(buff) - 1] = '\0';
i = 0;
token = strtok (buff, " ");
while (token != NULL && i < COMARG_N - 1) {
args[i] = token;
token = strtok (NULL, " ");
i++;
}
args[i] = NULL;
pid = fork();
if (pid == 0)
execvp(args[0], &args[0]);
else
waitpid(pid, &i, WUNTRACED | WCONTINUED);
free(buff);
}
return 0;
}
First of all, your fgets is reading to a single character buff. You should read into a buffer of characters. Second, fgets keeps the newline at the end of the read string, so you may want to remove it first, e.g.:
char buff[4096];
if (!fgets(buff, sizeof(buff), stdin)) {
// error or EOF
return 1;
}
int len = strlen(buff);
if (len > 0 && buff[len-1] == '\n') {
buff[--len] = '\0';
}
char *whtspc = strtok(buff, " ");
You must also replace all references to &buff with buff.
In addition to this, your malloc is also wrong, and allocates one character less than is required (strlen is without the terminating NUL):
if (!(comand[i] = malloc(strlen(whtspc)+1))) {
return 1; // out of memory
}
(void) strcpy(comand[i], whtspc);
Correspondingly your strncpy was copying one character less than required. This is what made your original code accidentally work for a single-word input because it had the effect of removing the trailing '\n' for you in that case, but in every other case it removed the last character of the word itself.
And the second argument to execvp should be just the comand (sic) array:
execvp(comand[0], comand);
I wrote this simple shell so far. But I got some trouble with my shell.
For example, when I try to open a pdf file via the command "evince pdffile.pdf", the actual pdfile does not get opened. The pdf viewer runs but the actual file with the whole content never appears.
Or another example is the command "ls -l". I don't get the files and folders listed as it should be, but "ls" is working.
Another example is gedit, and so on.
Also, I should mention. I am not using "system()", because system() would do everything and I would not have something to do. Instead, I am using "execvp()".
Here is the code. I hope, you may find the problem, because I have no clue what the problem is causing.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define MAX_LENGTH 1024
#define DELIMS " \t\r\n"
void exec_cmd (char *buf);
int main() {
char line[MAX_LENGTH];
char * cmd;
char curDir[100];
while (1) {
getcwd(curDir, 100);
printf("%s#%s$ ", getlogin(), curDir);
if (!fgets(line, MAX_LENGTH, stdin))
break;
if ((cmd = strtok(line, DELIMS))) {
errno = 0;
if (strcmp(cmd, "cd") == 0) {
char *arg = strtok(0, DELIMS);
if (!arg)
fprintf(stderr, "cd: argument is missing.\n");
else chdir(arg);
} else if (strcmp(cmd, "exit") == 0) {
exit(0);
} else exec_cmd(line);
if (errno) perror("Error. Command failure");
}
}
return 0;
}
void exec_cmd (char *buf) {
int status = 0;
char *argv[MAX_LENGTH];
int j=0;
pid_t pid;
argv[j++] = strtok (buf, DELIMS);
while (j<MAX_LENGTH && (argv[j++]=strtok(NULL,DELIMS))!=NULL); // EDIT: " " replaced by DELIMS
pid = fork();
if(pid < 0) {
printf("Error occured");
exit(-1);
} else if(pid == 0) {
execvp(argv[0],argv);
} else if( pid > 0) {
wait(&status);
}
}
The bug is in main().
You are using strtok to find the first word of the line. But strtok modifies the line, making it actually contain just that word (a NUL terminator is written to the string right after it).
You need to make a copy of the line, or use strtok_s, or do something else to avoid modifying the line.
Check the arguments (eg, print them out, each enclosed in []) before you call fork/exec, there's a good chance they're not what you think.
While your first call to strtok uses your full delimiter set, subsequent calls do not. They instead just use a space. That means that the final argument will probably have the newline left on the string by fgets. I'd be using the same delimiter set in the subsequent calls. In other words:
while (j<MAX_LENGTH && (argv[j++]=strtok(NULL,DELIMS))!=NULL);
Having entered your code and done that debugging, I find that the string passed to the function only ever has one word in it. It turns out that's because of the strtok that happened in main to check for cd/exit. That left the nul character at the end of the first word, an effect inherent in the way strtok works.
Probably the quickest fix is to make a copy of the string before the initial strtok in main, then pass that to the function. In other words, use strdup (and, later, free). Now glibc has a strdup but, if you're in an environment that doesn't (it's POSIX rather than ISO), see here.