1

I need to set an integer n using an argument -n that will be set as the amount of characters to print from the end of a given .txt file. This needs to be done without the <stdio.h> library as it is a homework piece about system calls.

I have a program that is able to accept the argument -n and prints the amount of characters as specified by the user. It however prints an unreadable list of characters and NULLS after the required output and causes my terminal to malfunction.

#include <sys/types.h> 
#include <fcntl.h> 
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[]) 
{ 
    //declaration of variables
    char buf[1000]; 
    int fd;
    int n;

    //initialising n to zero
    n = 0;

    //checking if the program run call is greater than one argument
    if(argc > 1){
    //if the second argument is equal to '-n' then take the 3rd argument (the int) and put it into n using stroll (string to long)
        if(!strncmp(argv[1], "-n", 2)){
    n = atoi(argv[2]);
    }

    //if n has no set value from -n, set it to 200
    if(n == 0){
    n = 200;}

    // open the file for read only 
    fd = open("logfile.txt", O_RDONLY); 
    //Check if it can open and subsequent error handling
    if(fd == -1){
        char err[] = "Could not open the file";
        write(STDERR_FILENO, err, sizeof(err)-1);
        exit(1);
    }

    //use lseek to place pointer n characters from the end of file and then use read to write it to the buffer
    lseek(fd, (n-(2*n)), SEEK_END);
    read(fd, buf, n);

    //write out to the standard output
    write(STDOUT_FILENO, buf, sizeof(buf)-1);

    //close the file fd and exit normally with code 0
    close(fd);
    exit(0);
    return(0);
}
deezy
  • 49
  • 3
  • Evidently related to [this question](https://stackoverflow.com/questions/57674081/declaring-a-n-argument-to-accept-an-integer-in-the-command-line-c). – Steve Summit Aug 27 '19 at 18:06
  • 1
    What's that strange computation `n-(2*n)`? – Steve Summit Aug 27 '19 at 18:07
  • Well done for posting the code. – Weather Vane Aug 27 '19 at 18:07
  • @SteveSummit It's just to get negative n. And yeah my previous post got put on hold, so being new I have no idea how this site works fully yet so I thought best start a new one with my updated problem. – deezy Aug 27 '19 at 18:08
  • 4
    When you call `read` to read characters, always capture the return value: `r = read(...)`. If `r` is less than 0, there's been an error. If `r` is 0, you've reached end-of-file. If `r` is greater than 0, you've read some characters, but you should then do something with `r` number of characters, not `sizeof(buf)` (and certainly not `sizeof(buf)-1`). I suspect that's why you're getting strange characters, but you're ignoring `read's attempt to tell you something's wrong. – Steve Summit Aug 27 '19 at 18:09
  • Thank you so much @SteveSummit . It is working perfectly now. It does however display the next command line in the terminal on the same line as the output. Any advice would be great thank you. – deezy Aug 27 '19 at 18:15
  • 1
    If the last character in the file is *not* a newline, then you'll need to output a newline before exiting the program. BTW, `exit(0)` and `return(0)` do the same thing (in `main`) so you only need one of them. – user3386109 Aug 27 '19 at 18:33
  • 1
    ...and `return 0;` is usual. Note that unlike `exit(0)` the `return 0;` isn't a function. – Weather Vane Aug 27 '19 at 18:36

1 Answers1

0

Note: this answer is an adaptation of the original answer to the first version of this question (now on hold)

I need to set an integer n using an argument -n.

Assuming the command line would look similar to prog.exe -n 4, the arguments of main(int argc, char* argv[]) will be populated as:

argc == 3
argv[] == {"programName.exe", "-n", "4"}

argc could be reduced to 2 by either combining -n and 4: -n4, or eliminating the -n altogether, and just recognizing in the code that argv[1] (the 2nd argument) is a string representation of the value you need, and convert it to an int type.

Example:

programName.exe 3

Then code it like this:

int main(int argc, char *argv[])
{
    char *dummy;
    int val;
    if(argc != 2)
    {
        printf("Usage: prog.exe <n> where <n> is a positive integer value.\nProgram will now exit");
        return 0;
    }
    // Resolve value of 2nd argument:
    val = strtol(argv[1], &dummy, 10);
    if (dummy == argv[1] || ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
    {
            //handle error
    }
    //use val to read desired content from file
    ...

    return 0;
}

Some other general suggestions:

User input in general needs to be kept as predictable as possible. Be explicit when requesting what is acceptable for the user to enter, and reject anything that does not comply. Example, if a program is expecting a single digit argument, then force it to be just that, and reject everything else. Given user input that does not comply:

prog.exe 3 4 5

detect in in the code:

int main(int argc, char *argv[]) 
{
    int val = 0;
    char *dummy = NULL;

    if(argc != 2)
    {
        printf("Usage: prog.exe <n> - Where <n> is an integer with value > 0\n Program will now exit.");
        return 0;
    }
    val = strtol(argv[1], &dummy, 10);
    if (dummy == argv[1] || ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
    {
        printf("Usage: prog.exe <n> - Where <n> is an integer with value > 0\n Program will now exit.");
        return 0;

    }

    ...

Using portable functions (replacing lseek with fseek, and open with fopen, etc.) (read this for an example of the reason why) here is an adaptation of your code that does what you described you needed it to do, including using the prog.exe -n <n> argument, ( argv == 3 ).

int main(int argc, char *argv[])
{
    char *dummy;
    char buf[1000]; 
    int n;    


    //initialising n to zero
    n = 0;

    //checking if the program run call is greater than one argument
    if(argc != 3)
    {
        printf("Usage: prog.exe -n <n> - Where <n> is an integer with value > 0\n Program will now exit.");
       return 0;
    }
    n = strtol(argv[2], &dummy, 10);
    if (dummy == argv[2] || ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE))
    {
        printf("Usage: prog.exe <n> - Where <n> is an integer with value > 0\n Program will now exit.");
        return 0;

    }


    // open the file for read only 
    FILE *fd = fopen(".\\data.txt", "r");

    //Check if it can open and subsequent error handling
    if(!fd)
    {
        char err[] = "Could not open the file";
        fputs(err, stdout);
        return 0;
    }

    //use lseek to place pointer n characters from the end of file and then use read to write it to the buffer
    fseek(fd, (n-(2*n)), SEEK_END);
    if(fgets(buf, n, fd))
    {
        //write out to the standard output
        fwrite(buf, 1, n, stdout);
    }
    else 
    {
        ;//handle error
    }

    //close the file fd and exit normally with code 0
    fclose(fd);

    return(0);
}
ryyker
  • 22,849
  • 3
  • 43
  • 87