Search code examples
cshellfork

I'm trying to execute read in lines from file in C in a shell environment


I can compile the code, execute it with the file as a command line argument, but nothing happens. The interactive mode function prompts as normal, but not the batchMode function.

I'm trying to read in a line, and then execute that line.

example file

date

ls -la

cd

(Without spacing between lines. I can't get the formatting right on here.)

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>


#define bSize 1000


void driveLoop();
char *userInput(void);
void removeExit(char *original, char *subString); // removes string with substring "exit"
void batchMode(char *c);


int main(int argc, char **argv){

  char *fTemp;
  if (argc == 1)
    driveLoop(); // calls the loop function that accepts input and executes commands.

  else if (argc == 2)
    batchMode(&argv[1][0]);

  return 0;

}


void driveLoop(void){
  char *comTokens[100];
  char *tempTokens;
  char *command;
  char *cd;
  char *cdDir;
  char* cdTemp;
  char cdBuf[bSize];
  char checkExit[] = "exit";

  for (;;){


    printf("> ");
    command = userInput(); // reads input


    if (!*command) // allows for empty string error
      break;


    char *exitPtr = strstr(command, "exit"); // returns a value to a pointer if substring is found
    removeExit(command, "exit"); 
    puts(command); // updates the array after the function filter

    int i = 0;
    tempTokens = strtok(command, " \t\n"); // tokens are how the computer recognizes shell commands


    while (tempTokens && i < 99){ // geeksforgeeks.com
      comTokens[i++] = tempTokens;
      tempTokens = strtok(NULL, "\t\n");
    }


    if (strcmp(comTokens[0], "exit") == 0) // exit if input is "exit" only
      exit(0); 




    if(strcmp(comTokens[0], "cd") == 0){ // built in change directory command
      cd = getcwd(cdBuf, sizeof(cdBuf));
      cdDir = strcat(cd, "/");
      cdTemp = strcat(cdDir, comTokens[1]); // cplusplus.com reference
      chdir(cdTemp);
      continue;
    }

    comTokens[i] = NULL;


    pid_t cFork = fork(); // creates duplicate child process of parent


    if (cFork == (pid_t) - 1){ // error check
      perror("fork");
    }


    else if (cFork == 0) { // error codes found on cplusplus.com
      execvp(comTokens[0], comTokens);
      perror("exec");      
    }

    else { // children are returned. parent executes
      int status;
      waitpid(cFork, &status, 0);
      if (exitPtr != NULL){ // if substring exit was found, exit the program
    exit(0);
      }
    }


  }

}


char *userInput(void){  // referenced Linux man page - getline(3) (linux.die.net)
  char *input = NULL;
  size_t size = 0;
  getline(&input, &size, stdin); // updates the size as it goes along
  return input;
}


void removeExit(char *original, char *subString){ // removes exit from string
  char *ex;
  int len = strlen(subString); 
  while ((ex = strstr(original, subString))){ // Referenced from a Stack Overflow page.
    *ex = '\0';
    strcat(original, ex+len);
  }
}


void batchMode(char *c){


  char *tok[100];
  char *batchTokens;
  char *batchBuffer = NULL;
  size_t batchSize = 0;


  FILE *fp = fopen(c, "r");


  unsigned int line = 1;
  char buffer[bSize];


  while(fgets(buffer, sizeof(buffer), fp)){

      int i = 0;


      char *toks = strtok(buffer, "\t\n");


      while (toks && i < 99){
           tok[i] = malloc (strlen(toks) + 1);
           strcpy(tok[i++], toks);
           toks = strtok(NULL, " \t\n");
      }


      tok[i] = NULL;
          pid_t bFork = fork();


      if (bFork == (pid_t) - 1)
          perror("fork");


      else if (bFork == 0){
         execvp(tok[i], tok);
         perror("exec");
      }


      else {
        int status;
        waitpid(bFork, &status, 0);
      }

  }

}

side note. This is a re-attempt from a previous question that was locked for inadequate information. I've updated my code and tried to be as detailed as possible. I'll happily provide anything further to help answer my question.

Thank you all.

edit I put in a fprintf to verify that it reads the file in, and it does.


Solution

  • First note in your input file, the last command cd isn't a system command, it is a shell built-it, so you would expect it to fail (unless handled specially).

    Allocating for each token (tok[i]) as discussed with either strdup (if available) or simply malloc (strlen(toks) + 1); allows you to copy the current token to the block of memory allocated. Now each tok[i] will reference the individual token saved (instead of all pointing to the last token -- due to all being assigned the same pointer)

    The biggest logic error in batchMode as your call to execvp with execvp (tok[i], tok); instead of properly providing the file to execute as tok[0]. Be mindful if the file to execute isn't in your PATH, you must provide an absolute path in your input file.

    Making the changes, your batchMode could be written as follows (and removing all the unused variables and moving char *tok[100]; within the while loop so it is declared within that scope):

    #include <sys/types.h>
    #include <sys/wait.h>
    ...
    void batchMode(char *c)
    {
        char buffer[bSize];
        FILE *fp = fopen (c, "r");
    
        if (!fp) {
            perror ("fopen-c");
            return;
        }
    
        while (fgets (buffer, sizeof(buffer), fp)) {
    
            int i = 0;
            char *tok[100];
            char *toks = strtok(buffer, " \t\n");
    
            while (toks && i < 99){
                tok[i] = malloc (strlen(toks) + 1);
                strcpy(tok[i++], toks);
                toks = strtok(NULL, " \t\n");
            }
    
            tok[i] = NULL;
                pid_t bFork = fork();
    
            if (bFork == (pid_t) - 1)
                perror("fork");
            else if (bFork == 0){
                execvp (tok[0], tok);
                perror("exec");
            }
            else {
                int status;
                waitpid(bFork, &status, 0);
            }
        }
    }
    

    Example Input Files

    I have two simple input files tested:

    $ cat dat/toksfile.txt
    echo hello
    echo goodbye
    
    $ cat dat/toksfile2.txt
    date
    ls -al dat/toksfile.txt
    cd
    

    Example Use/Output

    $ ./bin/shellorbatch dat/toksfile.txt
    hello
    goodbye
    
    $ ./bin/shellorbatch dat/toksfile2.txt
    Tue Feb  4 23:47:00 CST 2020
    -rw-r--r-- 1 david david 24 Feb  4 23:24 dat/toksfile.txt
    exec: No such file or directory
    

    Look things over and let me know if you have questions: