I wrote this simple shell in C for a university assignment on operating systems (My guess is that it's just the beginning and the next assignments will add to this code).
The program prints the prompt to the user, receives an input and try to execute it. The program ends when the input is 'done', and prints some statistics.
cd
command is not supported- For some reason (maybe the real shell does that too?) if
getpwuid
orgetcwd
fails we've been told to print the prompt withNULL
so I didn't check for failure. - About casting
malloc
s, I know that it's not necessary
my concerns are:
- Should the
main
be broken into more functions? - Is the idea behind
parseString
good? - About
freeArr
- In this case I can know for sure that the array will end by aNULL
, is it still bad practice to free that way? should I pass the array size to each functions instead? - Any other suggestion will be welcomed
Here is the code:
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>
#define SENTENCE_LEN 511
#define PATH_MAX 512
void printPrompt();
char *getUserName();
int numOfWords(const char sentence[]);
void parseString(char sentence[], char** parsedStr);
void freeArr(char** parsedStr);
void exeCommand(char** command);
void donePrint(int numOfCommands, int lengthOfCommands);
int main() {
char sentence[SENTENCE_LEN];
char** parsedStr;
int numOfCommands = 0, lengthOfCommands = 0, parsedStrLen;
while(1)
{
printPrompt();
if(fgets(sentence , SENTENCE_LEN, stdin) != NULL) {
parsedStrLen = numOfWords(sentence);
lengthOfCommands += (int) (strlen(sentence) - 1);
numOfCommands++;
if(parsedStrLen > 0) {
parsedStr = (char **) malloc((parsedStrLen + 1) * sizeof(char*));
if (parsedStr == NULL) {
fprintf(stderr, "malloc failed");
exit(1);
}
parsedStr[parsedStrLen] = NULL;
parseString(sentence, parsedStr); //allocates the words in the array
if(strcmp(parsedStr[0], "done") == 0 && parsedStrLen == 1)
{
donePrint(numOfCommands, lengthOfCommands);
freeArr(parsedStr);
break;
}
pid_t id;
id = fork();
if (id < 0) {
perror("ERR");
freeArr(parsedStr);
exit(1);
}
else if (id == 0) {
exeCommand(parsedStr);
freeArr(parsedStr);
}
else {
wait(NULL);
freeArr(parsedStr);
}
}
}
}
return 0;
}
//Prints prompt in the following format user@current dir>, if getUserName or getcwd fails -
// print NULL in their place
void printPrompt()
{
char* userName = getUserName();
char currentDir[PATH_MAX];
getcwd(currentDir, sizeof (currentDir));
printf("%s@%s>", userName, currentDir);
}
//Returns the user name as a char*, if getpwuid fails - return NULL
char *getUserName()
{
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if (pw)
return pw->pw_name;
else {
return NULL;
}
}
//receives a sentence and returns the number of words in it, ignoring blank spaces
int numOfWords(const char sentence[] )
{
int i = 0, wordCounter = 0;
while(sentence[i] != '\n')
{
if(sentence[i] != ' ' && (sentence[i+1] == ' ' || sentence[i+1] == '\n'))
wordCounter++;
i++;
}
return wordCounter;
}
/*receives a sentence as a char[] and a char**
*assign dynamically the sentence words into the char**
*/
void parseString(char sentence[], char** parsedStr) {
char tmpWord[SENTENCE_LEN];
int tmpIndex = 0, parsedIndex = 0;
for (int i = 0; i < strlen(sentence); i++) {
if (sentence[i] != ' ' && sentence[i] != '\n') {
tmpWord[tmpIndex] = sentence[i];
tmpIndex++;
}
//End of word
else if ((sentence[i] == ' ' || sentence[i] == '\n') && tmpIndex > 0) {
tmpWord[tmpIndex] = '\0';
parsedStr[parsedIndex] = (char *) malloc((strlen(tmpWord)) + 1);
if (parsedStr[parsedIndex] == NULL) {
freeArr(parsedStr);
fprintf(stderr, "malloc failed");
exit(1);
}
strcpy(parsedStr[parsedIndex], tmpWord);
parsedIndex++;
tmpIndex = 0;
}
}
}
//Receives an array and free each cell from 0 to the first NULL
void freeArr(char** parsedStr) //---should maybe get the array size although?
{
int i =0;
while(parsedStr[i])
free(parsedStr[i++]);
free(parsedStr);
}
// Receives a command to execute as an array** and execute it using execvp, cd not supported
void exeCommand(char** command)
{
if(strcmp(command[0], "cd") == 0) {
printf("command not supported (YET)\n");
freeArr(command);
exit(0);
}
else if(execvp(command[0], command) != -1)
{
freeArr(command);
exit(0);
}
else {
perror("command not found");
freeArr(command);
exit(1);
}
}
//Prints the total num of commands, the total length of commands and the average command length
void donePrint(int numOfCommands, int lengthOfCommands) {
printf("Num of commands: %d\n", numOfCommands);
printf("Total length of all commands: %d\n", lengthOfCommands);
printf("Average length of all commands: %f\n", (double) (lengthOfCommands) / numOfCommands);
printf("See you Next time !\n");
}
SENTENCE_LEN
constant doesn't seem to be defined anywhere in the code. Are you perhaps missing a header file? \$\endgroup\$main
be broken into more functions? Can you summarize what is happening without going into detail? If so, consider the summary to be your new function names and see how it would work. \$\endgroup\$