Thread: Confusion with new line char - command line shell arguments

  1. #1
    Registered User
    Join Date
    Feb 2020
    Posts
    4

    Confusion with new line char - command line shell arguments

    Hi, I am trying to add a feature to a simulated shell I'm making.
    Currently, I have it executing commands entered by a user on a loop.

    I'm trying to make it execute commands from a file if they run the program with a file argument. Converting the code over to work on a pointer filled with file data is proving to be troublesome, however. I'm pretty new to C, so while I am fairly certain the issue is with the new line char (I think it's trying to execute the entire pointer as one command), I can't fix it.

    example file:
    date
    ls -la
    cd

    My latest version has a segmentation fault.

    Code:
    void batchMode(char *c){ char *tok[100]; char *batchTokens; char *batchBuffer = NULL; size_t batchSize = 0; FILE *fp = fopen(c, "r"); fseek(fp, 0, SEEK_END); batchSize = ftell(fp); rewind(fp); batchBuffer = malloc((batchSize + 1) * sizeof(*batchBuffer)); fread(batchBuffer, batchSize, 1, fp); batchBuffer[batchSize] = '\0'; for (int i = 0; i < batchSize; i++){ batchTokens = strtok(batchBuffer[i], "\t\n"); // my attempt at getting past the newline char while (batchTokens && i < 99){ tok[i] = batchTokens; batchTokens = 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); } } }
    Thank you.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,730
    You should rename the c parameter to filename as "c" does not describe what the parameter is for.

    Since you intend to read line by line, why not use fgets instead?
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Feb 2020
    Posts
    4
    Quote Originally Posted by laserlight View Post
    You should rename the c parameter to filename as "c" does not describe what the parameter is for.

    Since you intend to read line by line, why not use fgets instead?

    I looked into fgets, and assumed that it was for just one character. I only know how to do strtok on a pointer array. (C is brand new to me).

    It's pretty confusing since it's very barebones.

    Could you show me how fgets would work?

    Thank you!

    *edit*
    Here's my attempt at that.

    Code:
    void batchMode(char *c){     
         char *tok[100];     
         char *batchTokens;    
         char *batchBuffer = NULL;     
         size_t batchSize = 0;     
    
         FILE *fp = fopen(c, "r");
    
            if (fp != NULL){
           
                 while (fgets(batchTokens, sizeof batchTokens, fp) != NULL){
                      int i = 0;
    
                      char *btok = strtok(batchTokens, "\t\n");
                      while (btoks && i < 99) {
                           tok[i++] = btok;
                           btok = 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);
    
                else {
                     int status;
                     waitpid(bFork, &status, 0);
               }
    
              }
    
           }
    
    }
    Last edited by jjordan33; 02-04-2020 at 09:13 PM.

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,730
    Start with something simple, e.g., reading the file line by line and printing its contents:
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int processBatch(const char *filename);
    
    int main(void) {
        const char *filename = "example.sh";
        if (!processBatch(filename)) {
            fprintf(stderr, "Error: could not process batch file '%s'\n", filename);
            return EXIT_FAILURE;
        }
        return 0;
    }
    
    int processBatch(const char *filename) {
        FILE *fp = fopen(filename, "r");
        if (!fp) {
            fprintf(stderr, "Error: could not open batch file\n");
            return 0;
        }
    
        unsigned int line_no = 1;
        char buffer[BUFSIZ];
        while (fgets(buffer, sizeof(buffer), fp)) {
            char *newline = strchr(buffer, '\n');
            if (!newline) {
                fprintf(stderr, "Error: batch file line %u exceeds %zu characters\n", line_no, sizeof(buffer) - 1);
                return 0;
            }
    
            *newline = '\0'; // remove the trailing newline
            printf("Line %u: %s\n", line_no, buffer);
            ++line_no;
        }
    
        fclose(fp);
        return 1;
    }
    You could then either improve this by say, skipping blank lines, or handling a last line that doesn't end in a new line (or perhaps leaving it as an error), or even using dynamic memory allocation to have a variable length buffer (or switching to the non-standard though POSIX standard getline function). When you're satisfied, then you change the printf into the part where you fork and do the execvp call.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Registered User
    Join Date
    Feb 2020
    Posts
    4
    Thank you very much. I'll work with it now.

    Code:
    #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++] = 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);
          }
        
      }
      
    }
    How's my fget?
    Last edited by jjordan33; 02-04-2020 at 10:19 PM.

  6. #6
    Registered User
    Join Date
    Feb 2020
    Posts
    4
    *Resolved*

    Code:
    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");
            elseif(bFork ==0){
                execvp (tok[0], tok);
                perror("exec");
            }
            else{
                int status;
                waitpid(bFork,&status,0);}
    Thanks for turning me onto fgets

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Command Line arguments
    By sidhuram in forum Windows Programming
    Replies: 2
    Last Post: 09-06-2010, 08:49 AM
  2. command line arguments
    By AJOHNZ in forum C++ Programming
    Replies: 1
    Last Post: 10-28-2009, 07:16 PM
  3. command line arguments
    By KBriggs in forum C Programming
    Replies: 7
    Last Post: 06-25-2009, 01:57 PM
  4. Command line arguments
    By KBriggs in forum C Programming
    Replies: 2
    Last Post: 06-15-2009, 09:55 AM
  5. Command line arguments...
    By AeonMoth in forum C++ Programming
    Replies: 5
    Last Post: 06-30-2007, 07:41 AM

Tags for this Thread