0

I have a simple shell program in C that needs to be able to run as a command line and take in input from the user and also take in input from a file piped in as < commands.txt. for the purposes of this project this is done with a make file command make run < commands.txt

When I run the program and enter commands manually, everything works as expected. However, when I run the command with < commands.txt the command input doesn't print, so it prints my command prompt followed by the command output.

The only obvious way I can think of to resolve this is to print the command input myself, but then it'll just repeat the command input when I'm typing manually.

How can I print the input when it's piped in from a file, but not reprint it when it's being entered manually?

Just to note, I previously had an error where my output was printing out of order that I resolved by using Output of printf out of order, I don't know if that's necessarily the correct thing to do in this case though.

Here's my code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX 102

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

    // here we set whether the prompt is user
    // input or our default
    char* prompt;
    if (argc > 1) {
        prompt = argv[1];

    }
    else {
        prompt = "> ";
    }


    char commandString[MAX];
    char exitString[MAX] = "exit";
    //setbuf(stdout, NULL);
    setvbuf(stdout, NULL, _IONBF, 0);
    
    // this is the main loop that prompts the user successively.
    // It will take in a command from the user, run it, and
    // return to the prompt.
    while (1){
        printf("%s", prompt);

        // we make sure our command string is completely overwritten with
        // null in order to ensure there's always a null terminator whereever
        // the command string ends        
        for (int i = 0; i < MAX; i++){
            commandString[i] = '\0';
        }

        // here we read stdin char by char scanning for
        // a newline or an end of file, while also filling out
        // our command string character by character for the first
        // MAX characters of the input stream. This way we can
        // clear the entire input buffer while only saving the portion
        // we want to save.
        char c;
        int counter = 0;
        while ((c = getchar()) != '\n' && c != EOF){
            if (counter < MAX){
                commandString[counter] = c;
            }

            counter++;
        }
        if (c == EOF){
            printf("EOF found, exiting\n");
            break;
        }
        //printf("\n\nCommand string: %s\n\n", commandString);

        // quit if the user entered exit
        if (strcmp(commandString, exitString) == 0) break;


        // here we break commandString up into tokens and output
        // the tokens to an array of arguments, ending the array
        // with NULL
        char* commandToken = strtok(commandString, " ");
        int argsSize = 6;
        char* commandArgs[argsSize];
        int argsCount = 0;
        while(commandToken != NULL && argsCount < argsSize - 1){
            commandArgs[argsCount] = commandToken;
            argsCount++;
            commandToken = strtok(NULL, " ");
        }
        commandArgs[argsCount] = NULL;

        // If we have a command, we fork the process into a parent and child,
        // pause the parent process, and execute the command on the child process.
        // once the child process returns, we resume the parent process and 
        // print the child's id and status.
        if (commandArgs[0] != NULL){
            
            /*
            printf("command name - %s\n\n", commandArgs[0]);
            argsCount = 0;
            while(commandArgs[argsCount] != NULL){
                printf("command arg %i - %s\n\n", argsCount, commandArgs[argsCount]);
                argsCount++;
            }
            */

            pid_t parentProcess = getpid();
            pid_t result = fork();
            if (result < 0){
                printf("Error in fork");
                break;
            }
            // Child
            else if (result == 0) {
                pid_t childProcess = getpid();
                //printf("Child process id - %i\n\n", (int)childProcess);
                execvp(commandArgs[0], commandArgs);
                printf("%s: command not found\n", commandArgs[0]);
                return 2;
            }
            // Parent
            else {
                int status;
                waitpid(result, &status, 0);
                if ( WIFEXITED(status) ) {
                    int exitStatus = WEXITSTATUS(status);
                    printf("Child %i exited with status %i\n", result, exitStatus);
                }
            }

        }
        


    }

    
    return 0;

}

As far as I can figure there shouldn't be anything wrong with the fork - exec - wait steps of the code, but maybe I didn't think of something?

fifthfiend
  • 25
  • 8
  • You need to use isatty() to find out whether it is a file or if it is a terminal: https://stackoverflow.com/questions/36258224/what-is-isatty-in-c-for Then you can print out the input if it is a file. – Jerry Jeremiah Feb 27 '23 at 01:38
  • nowhere do you attempt to output the input read from the file, why would you expect it to appear – pm100 Feb 27 '23 at 01:40
  • 1
    Shells don't normally show the commands when they're being read from a file. When they're used interactively this is just the result of the terminal driver's normal input echoing. – Barmar Feb 27 '23 at 01:40
  • You seem to be mixing two ideas. If you use `program < file.txt` the shell redirects the file to standard input for your program, but you will not get an argument in main. – Emanuel P Feb 27 '23 at 01:41
  • Note also that you *could* use termios and then write unconditionally. But normally you don't write prompts or input at *all* when not interactive. – o11c Feb 27 '23 at 01:42
  • Note that `(c = getchar()`, and the subsequent test for `EOF` is wrong, because `getchar()` returns an `int`, and `c` was declared as a `char`. – Harith Feb 27 '23 at 01:45
  • @Haris Zero initializing the buffer is only applicable if the definition of `commandString` is moved into the `while (1)` loop, where this initialization would be repeated. The buffer is not null terminated otherwise, and would become jumbled given only your suggestion. Alternatively, one might recommend `memset`, explicitly terminating the buffer, or using simply using `fgets` instead. – Oka Feb 27 '23 at 02:29
  • @Oka That's correct. – Harith Feb 27 '23 at 05:03

0 Answers0