I'm trying to write a VERY basic shell program in C. The problem I am facing is trying to fill my argv array of character pointers with the words taken from input. When I attempt to print out the contents of the argv array after attempting to fill it using the parse() function below I get a segmentation fault. I know this means that I am probably trying to access part of the argv array that is out of bounds. However, even when supplying only one argument to fill the array, I still get the segfault. The printf call used to print argc returns the correct value for argc based on input, but the second printf call with *argv[0] is the one causing the segfault. I am wondering if my error is in the way I am attempting to print the contents of argv, or if the error is because I am attempting to fill argv incorrectly.
EDIT: I should add that the getword() function takes in a line of text and returns the first word delimited by spaces, and a number of other delimiters. I can post all the delimiters it breaks the words up by if necessary, but I do not think the problem is because of getword().
EDIT 2: Added in the header file and included the #include statement in main.
EDIT 3: Added the getword function under main(), and getword.h below p2.h
Here is p2.h, the header file included in main:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "getword.h"
#include <signal.h>
#define MAXITEM 100
getword.h:
#include <stdio.h>
#include <string.h>
#include <strings.h>
#define STORAGE 255
int getword(char *w);
int parse(char *, char *[]);
Here is the main function :
#include "p2.h"
int main() {
pid_t pid, child_pid;
int argc, inputRedirect;
char *devNull;
devNull = (char *) malloc(10);
strcpy(devNull, "/dev/null");
char *argv[MAXITEM];
char commandLine[STORAGE];
for (;;) {
printf("p2: ");
scanf("%s", commandLine);
argc = parse(commandLine, argv);
printf("argc = %d\n", argc);
if(argc == 0)
continue;
printf("*argv = %s\n", *argv[0]);
child_pid = fork();
if (child_pid < 0) {
printf("Cannot fork! Terminating...");
exit(1);
} else if (child_pid == 0) {
inputRedirect = open(devNull, O_RDONLY);
dup2(inputRedirect, STDIN_FILENO);
close(inputRedirect);
execvp(*argv, argv);
}
else {
for(;;) {
pid = wait(NULL);
if(pid == child_pid)
break;
}
printf("Child's pid is %d\n", child_pid);
}
}
killpg(getpid(), SIGTERM);
printf("p2 Terminated.\n");
exit(0);
}
int parse(char *commandLine, char *argv[]) {
int i, argc = 0;
char *commandPointer = commandLine;
while (*commandPointer != '\0') {
*argv = commandPointer;
argc++;
getword(commandPointer);
}
*commandPointer = '\0';
*argv = '\0';
return argc;
}
getword.c:
#include "getword.h"
#include <stdlib.h>
/*Function Prototypes*/
int tilde(char *p, int i);
int BSFollowedByMetaCharacter(int c, char *w);
int getword(char *w) {
int c;
int index = 0;
/*This while loop removes all leading blanks and whitespace characters
* The if statement then tests if the first character is a new line or
* semicolon metacharacter*/
while ((c = getchar()) == ' ' || c == '\t' || c == '\n' || c == ';') {
if (c == '\n' || c == ';') {
w[index] = '\0';
return 0;
}
}
/*This if statement calls ungetc() to push whatever character was taken
* from the input stream in the previous while loop back to the input
* stream. If EOF was taken from the input stream, ungetc() will return EOF,
* which will then cause getword() to return -1, signalling that it reached
* the End Of File. */
if (ungetc(c, stdin) == EOF)
return -1;
/*This if statement deals with some of the "non-special" metacharacters.
* If one of these metacharacters is pulled from the input stream by getchar(),
* it is stored in w and null-terminated. getword() then returns the length of
* the current string stored in w. If getchar() pulls anything besides one of the
* specified metacharacters from the input stream, it is then returned using ungetc() after
* the if statement.*/
if ((c = getchar()) == '<' || c == '>' || c == '|' || c == '&') {
w[index++] = c;
int d = getchar();
if (c == '>' && d == '>')
w[index++] = d;
else {
ungetc(d, stdin);
}
w[index] = '\0';
return index;
}
ungetc(c, stdin);
/*This while statement handles plain text from the input stream, as well as a few 'special'
* metacharacters. It also ensures that the word scanned is shorter than STORAGE-1 bytes.*/
while ((c = getchar()) != ' ' && c != '<' && c != '>' && c != '|'
&& c != ';' && c != '&' && c != '\t' && c != '\n' && c != '\0'
&& index <= STORAGE - 1) {
if (c == '~') {
int *ip = &index;
index = tilde(&w[index], *ip);
continue;
}/*END IF*/
else if (c == '\\') {
int d = c;
c = getchar();
if (BSFollowedByMetaCharacter(c, w)) {
w[index++] = c;
continue;
} else {
w[index++] = d;
}
}/*END ELSE IF*/
w[index] = c;
index++;
}/*END WHILE*/
ungetc(c, stdin);/*This final ungetc() call is used to push any meta characters*/
w[index] = '\0'; /*used as delimiters back to the input stream, to be retrieved*/
return index; /*at the next call of getword(). */
}/*END getword()*/
int tilde(char *cp, int i) {
int *ip;
ip = &i;
char *p = cp;
char *o;
o = (strcpy(p, getenv("HOME")));
int offset = strlen(o);
*ip = *ip + offset;
return i;
}
int BSFollowedByMetaCharacter(int c, char *w) {
if (c == '~' || c == '<' || c == '>' || c == '|' || c == ';' || c == '&'
|| c == ' ' || c == '\t' || c == '\\') {
return 1;
} else {
return 0;
}
}
The functions in getword.c seems correct. Your problem is in function parse.
To use execvp, contents of argv should be following (input:"hello world"):
argv[0] -> "hello"
argv[1] -> "world"
argv[2] -> NULL
Here, argv is an array of character pointers. But, in parse function, you are treating argv like simple character pointers in here:
*argv = commandPointer;
and here:
*argv = '\0';
Change your parse function into something like this:
int parse(char *commandLine, char *argv[]) {
int argc = 0;
char *commandPointer;
argv[argc++] = commandLine;
do{
commandPointer = (char*)malloc(sizeof(char) * STORAGE);
argv[argc++] = commandPointer;
getword(commandPointer);
}while(*commandPointer != '\0');
argv[argc] = NULL;
return argc;
}
Now, you should free allocated memory after if-else tree like:
for(int i = 0; i < argc; i++) free(argv[i]);
Related
[Code]
#include <stdio.h>
//#include <conio.h> // getch()
int main(int argc, char *argv[])
{
char c, *s;
printf("Enter the number...\n");
while ((c=getchar()) != EOF && c != '\n' && c != '\t') {
putchar(c);
*s++ = c; // **What is wrong in here, the code is crashing!!**
}
printf("The number: %s\n", s);
return 0;
}
Output:
c:\works\workout\c>gcc tmp.c -o tmp
c:\works\workout\c>tmp
Enter the number...
434232
4
c:\works\workout\c>
Expected Output:
The actual given input number/string! (e.g 434232 in here)
Expecting output by using 'pointer' only (Without using 'scanf', 'char s[10]' etc.)
Thanks in advance!
First, you have to allocate s because it is a pointer.
Secondly, declare int c instead of char c because the definition of the functions getchar() and putchar() as:
int putchar(int c);
int getchar(void);
Finally, Use s[i] = ... instead of *s++ =.
The complete code (In this code, i use the realloc function for allocating each time you get new value from keyboard):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int c;
char *s = malloc(sizeof(char) + 1);
if (s == NULL)
exit(-1);
printf("Enter the number...\n");
int i = 0;
while ((c=getchar()) != EOF && c != '\n' && c != '\t') {
putchar(c);
s[i] = c;
// re-allocate because the size of s has to increase to store the new value
s = realloc(s, sizeof(char));
if (s == NULL)
exit(-1);
i++;
}
printf("The number: %s\n", s);
return 0;
}
My situation is the output print "p2: " each time the child processes.
When I type a command line "echo NULL; echo void". My parse() will split each word and assign to *newargv[] by using getword() function.
The getword(char *w) is basically split each word in command line. In my situation, the ";" is treat as a newline character. Getword(char * w) will return '\0' when it encounters the semi-colon.
The output when I run the program.
p2: echo NULL;echo void
NULL
void
p2: p2:
I thought it will need to flush output buffer and tried fflush(stdout). But it doesn't work.
Here is my p2.c and p2.h
#include <stdio.h>
#include "p2.h"
#define MAXITEM 100 /* max number of words per line */
int BGFLAG = 0, INFLAG = 0, OUTFLAG = 0, AMFLAG = 0, SEMIFLAG = 0, PIPEFLAG = 0;
char argv[255]; //this array get a string from getword function
char *newargv[MAXITEM]; //this pointer array will keep the first character of argv array
char *in, *out; //pointers point to file in argv
int sizeC = 0; //check if it is EOF or not
int parse();
int main()
{
int argc;
signal(SIGTERM,myhandler);
for (;;)
{
printf("p2: ");
pid_t pid, child_pid;
int status;
//call parse function
argc = parse();
if (sizeC == -1)
break;
if (argc == 0) //reissue prompt if line is empty
continue;
if (newargv[0] == NULL){
fprintf(stderr, "Invalid command");
continue;
}
child_pid = fork();
if (child_pid < 0){
printf("ERROR! can't fork\n");
exit(1);
}
else if(child_pid == 0){ //return a new child process
execvp(*newargv,newargv);
printf("ERROR exec failed\n");
exit(1);
}
else {
pid = wait(NULL);
if (pid == child_pid)
continue;
}
}//end for
killpg(getpgrp(),SIGTERM);
printf("p2 terminated. \n");
exit(0);
}// main
int parse()
{
int p = 0;
//this pointer will keep track the argv array for each of loops
//the getword function will not overwrite the argv array
int ptr = 0;
int count = 0;
SEMIFLAG = 0;
int wCount = 0; //return the number of argc
int semiColon = 0;
/* Read from stdin to argv array. The ptr will keep track the argv array
If it is meta character, set a flag appropriately. Otherwise,
set the address of first char of argv to the newargv*/
while ((sizeC = getword(argv + ptr)) > 0)
{
if(sizeC == 0){
semiColon++;
continue;
}else{
//Put the address of first char of each argv to the pointer array
newargv[p] = argv + ptr;
p++;
}
argv[ptr + sizeC] = '\0';
//point to the next address of next word in argv, the getword will not overwrite the argv array
ptr = ptr + sizeC + 1;
wCount++;
}//end while
newargv[p] = NULL;
return wCount;
}//end parse
void myhandler(){
}//end myhandler
Here p2.h
#include <stdio.h>
#include "getword.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <signal.h>
#define MAXITEM 100
int parse();
void myhandler();
Here getword.c and getword.h
#include <stdio.h>
#include "getword.h"
int getword(char *w)
{
int count = 0; //represent how many characters are read
char iochar;
int flag = 0; // identify a pair of single quote
//set an infinite loop
while (1)
{
iochar = getchar(); //This variable stores a character from stdin
/* If the array is full, the character will put back for next call.
Return (STORAGE -1) characters*/
if (count >= (STORAGE -1))
{
ungetc(iochar,stdin);
w[count] = '\0';
return count;
}
/* This block code will eleminate the leading tab */
if (iochar == '\t') //ignore the tabs if it counters
continue;
/* Identify if the function hit EOF */
if (iochar == EOF)
{
/* Return an empty string and -1 for size of string array
Because the EOF put back when count > 0 and the getword() encounters right away at next call
Therefore, the count is 0.*/
if (count == 0)
{
w[count] = '\0';
return -1;
}
/* The getword() read some characters before hitting EOF
Set a null terminator to finish a string array.
Return the size of string array.
Put the EOF back to stdin for next call
to print the EOF*/
if (count > 0)
{
w[count] = '\0';
ungetc(iochar,stdin);
return count;
}
}
/* For backslash case, treat all metacharacter
and space character as a normal character
The ";" and newline char will not effect meaning of that newline
*/
if (iochar == '\\')
{
//identify next character is meta-char,
//or normal char or a single quote
char nextC = getchar();
/* Only put a meta-character or space into the array
the blackslash is ignored
flag = 0 means the SINGLE QUOTE MOD is OFF*/
if ((nextC == '\'' || nextC == '!' || nextC == '>' ||
nextC == '&' || nextC == ' ' || nextC == '\\' || nextC == '\t')
&& flag == 0)
{
w[count++] = nextC;
continue;
}
/* As in a pair of single quote
slash and meta char both put in the array
flag = 1 means the SINGLE QUOTE MOD is ON.
The metacharacter and backslash are treat as normal char*/
else if ( (nextC == '!' || nextC == '>' ||
nextC == '&' || nextC == ' ' || nextC == '\\' || nextC ==';')
&& flag == 1)
{
w[count++] = iochar;
w[count++] = nextC;
continue;
}
//the single quote character in a pair of single quote
//treat as a normal character
else if (nextC == '\'' && flag == 1)
{
w[count++] = nextC;
continue;
}
//return the string if it encounters new line
//put nextC back to stdin for next call
//because we need to print out to determine the new line exist
else if (nextC == '\n' || nextC == ';')
{
w[count] = '\0';
ungetc(nextC,stdin);
return count;
}
else
//the normal character is put into the string array
{
w[count++] = nextC;
continue;
}
}// end if blacknextC
/* Identify Space case
Treat a space char as a normal char if it's in a pair of single quotes.
Treat a space char as a delimeter if it's not in a pair of single quotes*/
if (iochar == ' ')
{
if (flag == 1) //SINGLE QUOTE MOD is ON
{
w[count++] = iochar; //space is treat as normal character instead of a delimeter
continue;
}
else if (count == 0)//ignore if it is leading char or space char
continue;
else
{
/*Set a null delimeter and return the size of the array
This case space is a delimeter*/
w[count] = '\0';
return count;
}
}// end if space
/* This block of codes will identify the single quote case*/
if (iochar == '\'')
{
//read the character after single quote to determine
//it is a newline or normal character or metacharacter
char c = getchar();
/* Detect the open quote
If it is not newline or delimeter char, put it back to stdin for next call
and move on*/
if (flag == 0 && (c != '>' || c != '<' || c != '!' || c != '&'
|| c != '|' || c != ' ' || c != ';' || c !='\n'))
{
flag = 1;
ungetc(c,stdin);
continue;
}
/* Detect the closed quote. Set flag on.
Put the character back to stdin and move on*/
else if (flag == 1 && (c != '>' || c != '<' || c != '!' || c != '&' || c != '|' || c != ' ' || c != ';' || c !='\n'))
{
//Set single quote mod back to normal(OFF) (flag = 0)
//get the character back to stdin for next call to read as normal character
flag = 0;
ungetc(c,stdin);
continue;
}
/* if it hit a new line, set a null delimeter to terminate the array
return size of the string array.
the newline char is put back to stdin to print out nextcall*/
else
{
w[count] = '\0';
ungetc(c,stdin);
return count;
}
}//end if single quote
/* This code handle when the character is meta-character
It is considered as a delimeter character */
if (iochar == '>' || iochar == '<' || iochar == '!'
|| iochar == ';' || iochar == '\n' || iochar == '&' || iochar == '|')
{
/* Special character ">!"
Need to read next character to identify "!" */
if (iochar == '>')
{
char c = getchar();
/* return the string if is "!". It becomes a delimeter
put two characters back to stdin for next getword() call.
Return the size of string array before the special char*/
if ( c == '!' && count > 0)
{
w[count] = '\0';
ungetc(c,stdin);
ungetc(iochar,stdin);
return count;
}
//Return size and the special character ">!"
if (c == '!' && count == 0)
{
w[count++] = iochar; //iochar = ">"
w[count++] = c; //c = "!"
w[count] = '\0';
return count;
}
/* Put c and iochar back to stdin for next call
make sure in order, ">" need to be read first to print out the ">" char
The delimeter is only ">". Return the size of string array before ">"*/
if ( c != '!' && count > 0)
{
ungetc(c,stdin);
ungetc(iochar,stdin);
w[count] = '\0';
return count;
}
//put the ">" in to the array
//make sure put the non-"!" back to stdin for next call
//Return the meta-character ">" and size = 1
if ( c != '!' && count == 0)
{
ungetc(c,stdin);
w[count++] = iochar;
w[count] = '\0';
return count;
}
}
/* This code identify when the character is a new line "\n" or ";" */
if (iochar == '\n' || iochar == ';')
{
if (count == 0) // return an empty string
{
w[count] = '\0';
return 0;
}
/* Return a string array after newline
Put newline back to stdin for next call
to print out*/
if (count > 0)
{
w[count] = '\0';
ungetc(iochar,stdin);
return count;
}
}
/* This code block handle the rest of the metacharacter.
Return the size of 1 and that metacharacter if count = 0.
Set null delimeter and return string array before the metacharacter.
Put the metacharacter back to stdin for next call to print out.*/
if (iochar == '<' || iochar == '!' || iochar == '&' || iochar == '|')
{
//return the delimeter and size of 1.
if (count == 0)
{
w[count++] = iochar;
w[count] = '\0';
return count;
}
/* Set null delimiter to and return size of 1 and string array.
put the meta-character back to stdin for next call to get the meta-character */
if (count > 0)
{
w[count] = '\0';
ungetc(iochar,stdin);
return count;
}
}
}//end if meta case
/* After handling all situation, this character is normal.
Put the normal character to the string array */
w[count++] = iochar;
}//end while
}// getword
getword.h
#include <stdio.h>
#include <string.h>
#include <strings.h>
#define STORAGE 255
/* This is one more than the max wordsize that getword() can handle */
int getword(char *w);
the following code:
corrects the problems listed in the comments and exposed by the compiler.
cleanly compiles
documents why each header file is being included
keeps data declarations local to where they are used
is not a complete answer because you have not indicated what you want to trigger the code to exit.
And now the code:
#include <stdio.h> // perror(), printf()
#include <stdlib.h> // exit(), EXIT_FAILURE
#include <unistd.h> // execvp(), fork(), pid_t
#include <sys/types.h>
#include <sys/wait.h> // waitpid()
char **parse( void );
int main( void )
{
for (;;)
{
printf("p2: ");
//pid_t pid;
pid_t child_pid;
//int status;
//call parse function
char **argv = parse();
child_pid = fork(); //fork a child process
if(child_pid < 0)
{ // then error occurred
perror( "fork failed" );
//printf("ERROR: can't fork! exec failed\n");
//exit(1);
exit( EXIT_FAILURE );
}
else if (child_pid == 0)
{ // then child process
execvp( argv[0], argv );
perror( "execvp failed" );
exit(1);
}
else
{ // parent process
int status;
waitpid( child_pid, &status, 0);
}
}//end for
}// main
I wrote a program in C, The expected result should be:
$ cat poem.txt
Said Hamlet to Ophelia,
I'll draw a sketch of thee,
What kind of pencil shall I use?
2B or not 2B?
$ ./censor Ophelia < poem.txt
Said Hamlet to CENSORED,
I'll draw a sketch of thee,
What kind of pencil shall I use?
2B or not 2B?
But I got this:
$ ./censor Ophelia < poem.txt
Said Hamlet tomlet CENSORED,
I'lllia drawlia arawlia sketcha ofetcha theecha,
Whatcha kindcha ofndcha pencila shallla Ihallla usellla?
2Bsellla orellla notllla 2Botllla?
I use tempWord to store every word and compare it with the word that needs to be censored. Then I use tempWord[0]='\0' to reset the temp String, so that I can do another comparison. But it seems not working. Can anyone help?
# include <stdio.h>
# include <string.h>
int compareWord(char *list1, char *list2);
int printWord(char *list);
int main(int argc, char *argv[]) {
int character = 0;
char tempWord[128];
int count = 0;
while (character != EOF) {
character = getchar();
if ((character <= 'z' && character >= 'a') ||
(character <= 'Z' && character >= 'A') ||
character == 39) {
tempWord[count] = character;
count++;
} else {
if (count != 0 && compareWord(tempWord, argv[1])) {
printf("CENSORED");
count = 0;
tempWord[0] = '\0';
}
if (count != 0 && !compareWord(tempWord, argv[1])) {
printWord(tempWord);
count = 0;
tempWord[0] = '\0';
}
if (count == 0) {
printf("%c", character);
}
}
}
return 0;
}
int printWord(char *list) {
// print function
}
int compareWord(char *list1, char *list2) {
// compareWord function
}
There are multiple issues in your code:
You do not test for end of file at the right spot: if getc() returns EOF, you should exit the loop immediately instead of processing EOF and exiting at the next iteration. The classic C idiom to do this is:
while ((character = getchar()) != EOF) {
...
For portability and readability, you should use isalpha() from <ctype.h> to check if the byte is a letter and avoid hardcoding the value of the value of the apostrophe as 39, use '\'' instead.
You have a potential buffer overflow when storing the bytes into the tempWord array. You should compare the offset with the buffer size.
You do not null terminate tempWord, hence the compareWord() function cannot determine the length of the first string. The behavior is undefined.
You do not check if a command line argument was provided.
The second test is redundant: you could just use an else clause.
You have undefined behavior when printing the contents of tempWord[] because of the lack of null termination. This explains the unexpected behavior, but you might have much worse consequences.
printWord just prints a C string, use fputs().
The compWord function is essentially the same as strcmp(a, b) == 0.
Here is a simplified and corrected version:
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char tempWord[128];
size_t count = 0;
int c;
while ((c = getchar()) != EOF) {
if (isalpha(c) || c == '\'') {
if (count < sizeof(tempWord) - 1) {
tempWord[count++] = c;
}
} else {
tempWord[count] = '\0';
if (argc > 1 && strcmp(tempWord, argv[1]) == 0) {
printf("CENSORED");
} else {
fputs(tempWord, stdout);
}
count = 0;
putchar(c);
}
}
return 0;
}
EDIT: chux rightfully commented that the above code does not handle 2 special cases:
words that are too long are truncated in the output.
the last word is omitted if it falls exactly at the end of file.
I also realized the program does not handle the case of long words passed on the command line.
Here is a different approach without a buffer that fixes these shortcomings:
#include <ctype.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
const char *word = (argc > 1) ? argv[1] : "";
int count = 0;
int c;
for (;;) {
c = getchar();
if (isalpha(c) || c == '\'') {
if (count >= 0 && (unsigned char)word[count] == c) {
count++;
} else {
if (count > 0) {
printf("%.*s", count, word);
}
count = -1;
putchar(c);
}
} else {
if (count > 0) {
if (word[count] == '\0') {
printf("CENSORED");
} else {
printf("%.*s", count, word);
}
}
if (c == EOF)
break;
count = 0;
putchar(c);
}
}
return 0;
}
tempWord[0] = '\0';
It will not reset the variable to null. It just assign the '\0' to the first position. But The values which are assigned are still in memory only. Only the first position is assigned to '\0'. So, to reset the character array try the below.
memset(tempWord, 0, 128);
Add the above line instead of your tempWord[0] = '\0'.
And also this will solves you don't need to add the '\0' at end of each word. This itself will work. But for the first time your have to reset the character array using the same memset function. Before entering to the loop you have to set the tempWord to null using the memset function.
Using tempWord[0]='\0' will not reset the whole array, just the first element. Looking at your code, there are 2 ways you could go forward, either reset the whole array by using memset:
memset(tempWord, 0, sizeof tempWord);
or
memset(tempWord, 0, 128);
(or you can only clear it by the size of last word, also it needs string.h which you have already included),
Or you could just set the element after the length of 'current word' to be '\0' (ex, if current word is the then set tempWord[3]='\0', since strlen checks the string till null char only) which can be placed before those 2 ifs checking if the strings are equal or not, your new while loop will look like this:
{
character = getchar();
if((character<='z' && character>='a')||(character<='Z' && character>='A')||character == 39)
{
tempWord[count]=character;
count++;
}else {
tempWord[count]='\0';
if(count!=0 && compareWord(tempWord, argv[1]))
{
printf("CENSORED");
count=0;
}
if(count!=0 && !compareWord(tempWord, argv[1]))
{
printWord(tempWord);
count=0;
}
if (count==0)
{
printf("%c", character);
}
}
}
(it works, tested)
I am writing a mini-shell program. It gives me seg fault in the line "args = my_str2vect(line);". The "my_str2vect" function works fine though, as well as the my_strncpy function and my_strlen function(returns the length of the char array). So what's wrong with that line?
#define _POSIX_SOURCE
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include "../../include/my.h"
pid_t pid;
int childrunning = 0;
void minishell_loop();
/*
Your shell must also support CTRL+C by using UNIX signals.
If you type CTRL+C your prompt must not exit
and instead kill the currently running process (or do nothing if there is none).
*/
void int_handler(int sig){
if(childrunning == 1){
kill(pid, SIGUSR2);
}else{
my_str("\n");
minishell_loop();
}
}
void killkillkill(int sig){
exit(0);
}
/*
Function Declarations for builtin shell commands:
*/
int minishell_cd(char **args);
int minishell_help(char **args);
int minishell_exit(char **args);
/*
List of builtin commands, followed by their corresponding functions.
*/
char *builtin_str[] = {
"cd",
"help",
"exit"
};
int (*builtin_func[]) (char **) = {
&minishell_cd,
&minishell_help,
&minishell_exit
};
int minishell_mum_bultins() {
return sizeof(builtin_str) / sizeof(char *);
}
/*
Builtin function implementations.
*/
/**
#brief Bultin command: change directory.
#param args List of args. args[0] is "cd". args[1] is the directory.
#return Always returns 1, to continue executing.
*/
int minishell_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "minishell: expected argument to \"cd\"\n");
} else {
if (chdir(args[1]) != 0) {
perror("minishell");
}
}
return 1;
}
/**
#brief Builtin command: print help.
#param args List of args. Not examined.
#return Always returns 1, to continue executing.
*/
int minishell_help(char **args)
{
int i;
printf("Tianwei's minishell, version 1.01\n");
printf("These shell commands are defined internally.\n");
for (i = 0; i < minishell_mum_bultins(); i++) {
printf(" %s\n", builtin_str[i]);
}
return 1;
}
/**
#brief Builtin command: exit.
#param args List of args. Not examined.
#return Always returns 0, to terminate execution.
*/
int minishell_exit(char **args)
{
return 0;
}
/**
#brief Launch a program and wait for it to terminate.
#param args Null terminated list of arguments (including program).
#return Always returns 1, to continue execution.
*/
int minishell_launch(char **args)
{
signal(SIGUSR2, killkillkill);
int status;
pid = fork();
if (pid == 0) {
// Child process
childrunning = 1;
if (execvp(args[0], args) == -1) {
perror("minishell");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("minishell");
} else {
// Parent process
do {
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
/**
#brief Execute shell built-in or launch program.
#param args Null terminated list of arguments.
#return 1 if the shell should continue running, 0 if it should terminate
*/
int minishell_execute(char **args)
{
int i;
my_str(args[0]);
if (args[0] == NULL) {
my_str("args[0] is NULL\n");
// An empty command was entered.
return 1;
}
for (i = 0; i < minishell_mum_bultins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
my_str("1\n");
return (*builtin_func[i])(args);
}
}
my_str("0\n");
return minishell_launch(args);
}
#define MINISHELL_RL_BUFSIZE 1024
/**
#brief Read a line of input from stdin.
#return The line from stdin.
*/
char *minishell_readln(void)
{
int bufsize = MINISHELL_RL_BUFSIZE;
int position = 0;
char* buffer = malloc(1024 * sizeof(char));
int c;
while (1) {
// Read a character
c = getchar();
// If we hit EOF, replace it with a null character and return.
if (c == EOF || c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if (position >= bufsize) {
fprintf(stderr, "minishell: Command line too long!\n");
exit(EXIT_FAILURE);
}
}
}
/**
#brief Loop getting input and executing it.
*/
void minishell_loop(void)
{
signal(SIGINT, int_handler);
char* line;
char** args;
int status;
do {
printf("MINISHELL: /home/tianwei/$ ");
line = minishell_readln();
my_str("0\n");
my_str(line);
args = my_str2vect(line);
my_str(args[0]);
my_str("0\n");
status = minishell_execute(args);
my_str("1\n");
free(line);
free(*args);
free(args);
} while (status);
}
/**
#brief Main entry point.
#param argc Argument count.
#param argv Argument vector.
#return status code
*/
int main(int argc, char **argv)
{
// Load config files, if any.
// Run command loop.
minishell_loop();
// Perform any shutdown/cleanup.
return EXIT_SUCCESS;
}
This is the code for my_str2vect function
#include "../../include/my.h"
char** my_str2vect(char* str){
// Takes a string
// Allocates a new vector (array of string ended by a NULL),
// Splits apart the input string x at each space character
// Returns the newly allocated array of strings
// Any number of ' ','\t', and '\n's can separate words.
// I.e. "hello \t\t\n class,\nhow are you?" -> {"hello", "class,", "how", "are","you?", NULL}
int max_num_words = 0;
int a = 0;
int b = 0;
int max_num_char = 0;
while(str[a] != '\0'){ // find the number of words and the length of the longest word
if(str[a] != ' ' && str[a] != '\t' && str[a] != '\n'){
++max_num_words;
++a;
++b;
while((str[a] != ' ' && str[a] != '\t' && str[a] != '\n') && str[a] != '\0'){
++a;
++b;
}
if(b > max_num_char){
max_num_char = b;
}
b = 0;
while((str[a] == ' ' || str[a] == '\t' || str[a] == '\n') && str[a] != '\0'){
++a;
}
}
}
char** output = (char **)malloc(sizeof(char *) * (max_num_words + 1)); // Allocate a 2D array first.
for(int c = 0; c < max_num_words + 1; ++c){
output[c] = (char *)malloc(sizeof(char) * (max_num_char + 1));
}
int i = 0;
int j = 0;
while(i < my_strlen(str) && j < max_num_words){ // Put the characters into the 2D array.
int k = 0;
while(i < my_strlen(str) && (str[i] == ' ' || str[i] == '\t' || str[i] == '\n')){
++i;
}
while(i < my_strlen(str) && (!(str[i] == ' ' || str[i] == '\t' || str[i] == '\n'))){
++i;
++k;
}
if(i-k < my_strlen(str) && j < max_num_words){
my_strncpy(output[j], &str[i-k], k);
}
++j;
}
output[j] = NULL;
return output;
}
This is my_strncpy function
#include "../../include/my.h"
char *my_strncpy(char *dst, char *src, int n)
{
/* Same as my_strcpy except:
* Only copies n chars or until the end of src*/
if(src != NULL && dst != NULL){
int i = 0;
while(i < n && src[i] != '\0'){
dst[i] = src[i];
++i;
}
dst[i] = '\0';
}
return dst;
}
I didn't try to execute your code but here is what strikes me at first sight in the my_str2vect function:
in the first while loop, a is only incremented in the if. You are likely to meet a forever loop if your line begins with a space
in the second while loop, you don't check for the end of string '\0'. This may well be the reason for your crash, but there may be others.
PS: since you are parsing a string, you should take a look at strtok and sscanf, they would ease your life
I'm able to reverse the array fine, but I can't get the program to terminate when I do CTRL+D(EOF) in terminal.
The only way I can get the program to terminate is if the very first thing I do after compiling is doing CTRL+D. But if I type in one string, then CTRL+D will not work after that.
I'm not quite sure where my error is.
#include <stdio.h>
#define MAXLINE 1000 // Maximum input.
// ----------------- reverseLine -----------------
// This method reads in chars to be put into an
// array to make a string. EOF and \n are the
// delimiters on the chars, then \0 is the
// delimiter for the string itself. Then the
// array is swapped in place to give the reverse
// of the string.
//------------------------------------------------
int reverseLine(char s[], int lim)
{
int c, i, newL;
// c is the individual chars, and i is for indices of the array.
for (i = 0; i < lim - 1 && (c=getchar()) != EOF && c != '\n'; ++i)
{
s[i] = c;
}
if (c == '\n') // This lets me know if the text ended in a new line.
{
newL = 1;
}
// REVERSE
int toSwap;
int end = i-1;
int begin = 0;
while(begin <= end) // Swap the array in place starting from both ends.
{
toSwap = s[begin];
s[begin] = s[end];
s[end] = toSwap;
--end;
++begin;
}
if (newL == 1) // Add the new line if it's there.
{
s[i] = '\n';
++i;
}
s[i] = '\0'; // Terminate the string.
return i;
}
int main()
{
int len;
char line[MAXLINE];
while ((len = reverseLine(line, MAXLINE)) > 0) // If len is zero, then there is no line to recored.
{
printf("%s", line);
}
return 0;
}
The only thing I can think of is the while loop in main checks if len > 0, so if I type EOF, maybe it can't make a valid comparison? But that wouldn't make sense as to why it works when that's the first and only thing I type.
Your program will never read the EOF because of this condition:
(c=getchar()) != EOF && c != '\n';
As soon as c is equal to '\n' the loop terminates and all the following characters are ignored. I think you should separate input from line reversing and make the usual checks on the reverse function parameters.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_MAX (256U)
static bool linereverse(char *line);
static bool deletenewline(char *s);
int main(void)
{
char buff[SIZE_MAX];
bool success;
(void) fputs("Enter a string: ", stdout);
if( NULL == fgets(buff,(size_t) SIZE_MAX, stdin))
{
(void) fputs("Error: invalid input!\n",stderr);
return EXIT_FAILURE;
}
success = deletenewline(buff);
if(false == success)
{
(void) fputs("Error: cannot remove newline\n",stderr);
return EXIT_FAILURE;
}
success = linereverse(buff);
if(false == success)
{
(void) fputs("Error: cannot reverse the line");
return EXIT_FAILURE;
}
(void) fputs("The line reversed is: ", stdout);
(void) fputs(buff, stdout);
(void) puchar('\n');
return EXIT_SUCCESS;
}
static bool linereverse(char *line)
{
size_t i;
size_t j;
char tmp;
if(NULL == line)
{
return false;
}
i = 0;
j = strlen(line) - 1;
while(i < j)
{
tmp = line[i];
line[i] = line[j];
line[j] tmp;
++i;
--j;
}
return true;
}
static bool deletenewline(char *s)
{
char *p;
if(NULL == s)
{
return false;
}
p = strrchr(s,'\n');
if(NULL != p)
{
*p = '\0';
}
return true;
}