I'm trying to build a version of a linux shell wherein, I take in the commands from the user and execute the commands using execv() system call.
I'm using strtok_r to tokenize the commands and pass the arguments to execv(). For example, when the user types in the command "ls -l", I first remove the leading/trailing white spaces and then tokenize the command into two tokens 1) ls, 2) -l. I concatenate the first token with a char path[10] in which I've already copied "/bin/" path. So, after concatenation, path becomes "/bin/ls". However, the execv() call fails. But, when I directly pass "/bin/ls" (instead of passing it as a variable) as the argument in execv(), it works. Code is shown below:
Please let me know where I've committed the mistake.
`
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
void removeTrailingLeadingSpaces(char* stringInput);
int main ()
{
printf("dash> ");
char* input;
size_t characters;
size_t bufsize = 32;
input = (char *)malloc(bufsize*sizeof(char));
characters = getline(&input,&bufsize,stdin);
removeTrailingLeadingSpaces(input);
char *args[2];
char* last;
char* token;
token = strtok_r(input," ",&last);
printf("%s\n", token);
removeTrailingLeadingSpaces(token);
char path[10];
strcpy(path,"/bin/");
strcat(path,token);
args[0] = path;
printf("%s\n",args[0]);
args[1] = strtok_r(NULL," ",&last);
args[2] = NULL;
int i = access(args[0],X_OK);
printf("result is %d",i);
int j= execv(args[0],args);
printf("result2 is %d",j);
return 0;
}
void removeTrailingLeadingSpaces(char* input)
{
while(isspace((unsigned char)*input))
input++;
int i=strlen(input)-1;
while(i>0)
{
if(input[i] == ' '|| input[i] == '\n'|| input[i] =='\t')
i--;
else
break;
}
input[i+1] = '\0';
}
`
Related
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int args, char* argv[])
{
const int CBUFF = 1024;
char input[CBUFF];
char wkdir[CBUFF];
char* command;
printf("Welcome to MyShell...\n");
while(1)
{
getcwd(wkdir, CBUFF);
printf("%s ? ", wkdir);
fgets(input, CBUFF, stdin);
command = strtok(input, " ");
if(strcmp(command, "cd") == 0)
{
char* path;
path = strtok(NULL, " ");
if(chdir(path) != 0)
{
printf("ERROR: COULD NOT CHANGE DIRECTORY TO SPECIFIED PATH");
}
}
if(strcmp(command, "exit") == 0) break;
}
return 0;
}
I am running into an issue creating a very simple command shell in C. The input is only being read the way I want it too when I add a space after my directive. I know that it has something to do with my improper use of the strtok() function but am not able to figure out what I am doing wrong. I have read the documentation of <string.h> and am turning up blank.
Behavior I want:
Directive "exit" to exit from program.
Current behavior:
Must add space after directive to get it to parse correctly ie. "exit " or "cd " is entered.
You left the trailing newline in the buffer. Get rid of it.
char *got = fgets(input, CBUFF, stdin);
if (!got) return ; /* EOF -- treat like exit */
size_t gotlen = strlen(got);
if (got[gotlen] == '\n') got[gotlen] = 0;
I want to split the first command line argument into two different numbers. I got a segmentation fault error when running the program this way:
gcc -ansi main.c -o main
./main 6000V7000
Here is the source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *token;
char arr[200];
strcpy(arr, argv[1]);
token = strtok(arr, "v,V");
int firstNumber = atoi(token);
token = strtok(NULL, "v,V");
int secondNumber = atoi(token);
return 0;
}
How can I fix this problem?
You do not test if there is at least one command line argument, nor that this argument is less than 200 characters long, nor do you test the return values of strtok: you would have undefined behavior if the command is given no argument or if the argument does not contain any of the characters v, V or ,.
If you effectively compile the program with gcc -ansi main.c -o main and run it with the posted argument as ./main 6000V7000 you should not get a segmentation fault... There is something you are not telling us ;)
It is always better to avoid wild assumptions: test for the unexpected to give your program defined behavior in all cases.
Here is a simpler approach for your problem using sscanf():
#include <stdio.h>
int main(int argc, char *argv[]) {
int a, b;
if (argc > 1 && sscanf(argv[1], "%d%*1[vV,]%d", &a, &b) == 2)
printf("a=%d, b=%d\n", a, b);
return 0;
}
The code and command line arguments given do not seg-fault. However if the command line argument either omits the delimiter, includes a space before the second number, or omits any argument at all, then it will fail.
The following will prevent erroneous input causing a runtime error:
if( argc > 1 )
{
strcpy( arr, argv[1]);
int firstNumber = 0 ;
int secondNumber = 0 ;
token = strtok(arr, "v,V");
if( token != NULL )
{
firstNumber = atoi(token) ;
token = strtok(NULL, "v,V") ;
if( token != NULL )
{
secondNumber= atoi(token);
}
}
}
You must define var at the begin of the function :
The follow code could work :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
char *token;
char arr[200];
int firstNumber;
int secondNumber;
strcpy( arr, argv[1]);
token = strtok(arr, "v,V");
firstNumber = atoi(token);
token = strtok(NULL, "v,V");
secondNumber= atoi(token);
return 0;
}
There's only one "V" in your input. The second call to strtok() finds no V and returns NULL.
Here is the general problem:
The program must fork() and wait() for the child to finish.
The child will exec() another program whose name is INPUT by the user.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
int status;
char input[BUFSIZ];
printf(">");
scanf("%s",input);
char *args[] = {"./lab1"};
pid_t pid = fork();
if(pid==0){
execvp(args[0],args);
}else if(pid<0){
perror("Fork fail");
}else{
wait(&status);
printf("My Child Information is: %d\n", pid);
}
return 0;
}
My problem is getting the user to input a program name to run (at the ">" prompt) and getting that input into execvp (or another exec() function if anyone has any ideas)
I'm going to hold off lambasting you for using scanf("%s") for now, though you should be aware it's really not robust code.
Your basic task here is going to be taking a character array entered by the user and somehow turning that into an array of character pointers suitable for passing to execvp.
You can use strtok to tokenise the input string into tokens separated by spaces, and malloc/realloc to ensure you have enough elements in an array to store the strings.
Alternatively, since you already have a potential buffer overflow issue, it may be good enough to just use a fixed size array.
For example, the following program shows one way of doing this, it uses a fixed string echo my hovercraft is full of eels and tokenises it to be suitable for execution:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static char *myStrDup (char *str) {
char *other = malloc (strlen (str) + 1);
if (other != NULL)
strcpy (other, str);
return other;
}
int main (void) {
char inBuf[] = "echo my hovercraft is full of eels";
char *argv[100];
int argc = 0;
char *str = strtok (inBuf, " ");
while (str != NULL) {
argv[argc++] = myStrDup (str);
str = strtok (NULL, " ");
}
argv[argc] = NULL;
for (int i = 0; i < argc; i++)
printf ("Arg #%d = '%s'\n", i, argv[i]);
putchar ('\n');
execvp (argv[0], argv);
return 0;
}
Then it outputs the tokenised arguments and executes it:
Arg #0 = 'echo'
Arg #1 = 'my'
Arg #2 = 'hovercraft'
Arg #3 = 'is'
Arg #4 = 'full'
Arg #5 = 'of'
Arg #6 = 'eels'
my hovercraft is full of eels
I am trying to make this shell parse. How do I make the program implement parsing in a way so that commands that are in quotes will be parsed based on the starting and ending quotes and will consider it as one token? During the second while loop where I am printing out the tokens I think I need to put some sort of if statement, but I am not too sure. Any feedback/suggestions are greatly appreciated.
#include <stdio.h> //printf
#include <unistd.h> //isatty
#include <string.h> //strlen,sizeof,strtok
int main(int argc, char **argv[]){
int MaxLength = 1024; //size of buffer
int inloop = 1; //loop runs forever while 1
char buffer[MaxLength]; //buffer
bzero(buffer,sizeof(buffer)); //zeros out the buffer
char *command; //character pointer of strings
char *token; //tokens
const char s[] = "-,+,|, ";
/* part 1 isatty */
if (isatty(0))
{
while(inloop ==1) // check if the standard input is from terminal
{
printf("$");
command = fgets(buffer,sizeof(buffer),stdin); //fgets(string of char pointer,size of,input from where
token = strtok(command,s);
while (token !=NULL){
printf( " %s\n",token);
token = strtok(NULL, s); //checks for elements
}
if(strcmp(command,"exit\n")==0)
inloop =0;
}
}
else
printf("the standard input is NOT from a terminal\n");
return 0;
}
For an arbitrary command-line syntax, strtok is not the best function. It works for simple cases, where the words are delimited by special characters or white space, but there will come a time where you want to split something like this ls>out into three tokens. strtok can't handle this, because it needs to place its terminating zeros somewhere.
Here's a quick and dirty custom command-line parser:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int error(const char *msg)
{
printf("Error: %s\n", msg);
return -1;
}
int token(const char *begin, const char *end)
{
printf("'%.*s'\n", end - begin, begin);
return 1;
}
int parse(const char *cmd)
{
const char *p = cmd;
int count = 0;
for (;;) {
while (isspace(*p)) p++;
if (*p == '\0') break;
if (*p == '"' || *p == '\'') {
int quote = *p++;
const char *begin = p;
while (*p && *p != quote) p++;
if (*p == '\0') return error("Unmachted quote");
count += token(begin, p);
p++;
continue;
}
if (strchr("<>()|", *p)) {
count += token(p, p + 1);
p++;
continue;
}
if (isalnum(*p)) {
const char *begin = p;
while (isalnum(*p)) p++;
count += token(begin, p);
continue;
}
return error("Illegal character");
}
return count;
}
This code understands words separated by white-space, words separated by single or double quotation marks and single-character operators. It doesn't understand escaped quotation marks inside quotes and non-alphanumeric characters such as the dot in words.
The code is not hard to understand and you can extend it easily to understand double-char operators such as >> or comments.
If you want to escape quotation marks, you'll have to recognise the escape character in parse and unescape it and possible other escape sequences in token.
First, you've declared argv to be an array of pointers to... pointers. In fact, it is an array of pointers to chars. So:
int main(int argc, char **argv){
The trend is you want to reach for [], which got you into incorrect code here, but the idiom in C/C++ is more commonly to use pointer syntax, e.g.:
const char* s = "-+| ";
FWIW.
Also, note that fgets() will return NULL when it hits end of file (e.g., the user types CTRL-D on *nix or CTRL-Z on DOS/Windows). You probably don't want a segment violation when that happens.
Also, bzero() is a nonportable function (you probably don't care in this context) and the C compiler will happily initialize an array to zeroes for you if you ask it to (possibly worth caring about; syntax demonstrated below).
Next, as soon as you allow quoted strings, the next language question that immediately arises is: "how do I quote a quote?". Then, you are immediately out of the territory that can be handled cleanly with strtok(). I'm not 100% sure how you want to break your string into tokens. Using strtok() in the way you do, I think the string "a|b" would produce two tokens, "a" and "b", making you overlook the "|". You're treating "|" and "-" and "+" like whitespace, to be ignored, which is not generally what a shell does. For example, given this command-line:
echo 'This isn''t so hard' | cp -n foo.h .. >foo.out
I would probably want to get the following list of tokens:
echo
'This isn''t so hard'
|
cp
-n
foo.h
..
>
foo.out
Usually, characters like '+' and '-' are not special for most shells' tokenizing process (unlike '|' and '&' and '<', etc. which are instructions to the shell that the spawned command never sees). They get passed onto the application that is then free to decide "'-' indicates this word is an option and not a filename" or whatever.
What follows is a version of your code that produces the output I described (which may or may not be exactly what you want) and allows either double or single-quoted arguments (trivial to extend to handle back-ticks too) that can contain quote marks of the same kind, etc.
#include <stdio.h> //printf
#include <unistd.h> //isatty
#include <string.h> //strlen,sizeof,strtok
#define MAXLENGTH 1024
int main(int argc, char **argv[]){
int inloop = 1; //loop runs forever while 1
char buffer[MAXLENGTH] = {'\0'}; //compiler inits entire array to NUL bytes
// bzero(buffer,sizeof(buffer)); //zeros out the buffer
char *command; //character pointer of strings
char *token; //tokens
char* rover;
const char* StopChars = "|&<> ";
size_t toklen;
/* part 1 isatty */
if (isatty(0))
{
while(inloop ==1) // check if the standard input is from terminal
{
printf("$");
token = command = fgets(buffer,sizeof(buffer),stdin); //fgets(string of char pointer,size of,input from where
if(command)
while(*token)
{
// skip leading whitespace
while(*token == ' ')
++token;
rover = token;
// if possible quoted string
if(*rover == '\'' || *rover == '\"')
{
char Quote = *rover++;
while(*rover)
if(*rover != Quote)
++rover;
else if(rover[1] == Quote)
rover += 2;
else
{
++rover;
break;
}
}
// else if special-meaning character token
else if(strchr(StopChars, *rover))
++rover;
// else generic token
else
while(*rover)
if(strchr(StopChars, *rover))
break;
else
++rover;
toklen = (size_t)(rover-token);
if(toklen)
printf(" %*.*s\n", toklen, toklen, token);
token = rover;
}
if(strcmp(command,"exit\n")==0)
inloop =0;
}
}
else
printf("the standard input is NOT from a terminal\n");
return 0;
}
Regarding your specific request: commands that are in quotes will be parsed based on the starting and ending quotes.
You can use strtok() by tokenizing on the " character. Here's how:
char a[]={"\"this is a set\" this is not"};
char *buf;
buf = strtok(a, "\"");
In that code snippet, buf will contain "this is a set"
Note the use of \ allowing the " character to used as a token delimiter.
Also, Not your main issue, but you need to:
Change this:
const char s[] = "-,+,|, "; //strtok will parse on -,+| and a " " (space)
To:
const char s[] = "-+| "; //strtok will parse on only -+| and a " " (space)
strtok() will parse out whatever you have in the delimiter string, including ","
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);