Home > database >  How to correctly use getline?
How to correctly use getline?

Time:11-24

I was trying to use getline for user input and I manage to do so but each time I try to print everything after %s (like .) it gets put to the next line and I am also trying to get rid of trailing space characters input so I use the function below but for some reason this isn't working, any help, sorry for anything improper still learning:

Also can I print the contents of buff like an ordinary array (for loop) or is it different if I use getline?

/******************************************************************************



*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int trim (char *s)
{
    int i = strlen(s) - 1;
    while (i > 0){
        if (s[i] == ' '|| s[i] == '0'){
            i--;
        }
        else {
            break;
        }
    }
    s[i   1] = '\0';
    printf("%d\n", i);
    return (strlen(s)); 
    
}

int main()
{
    char *buff = NULL;
    size_t sizeAllocated = 0;
    printf("Begining!!\n");
    printf("> ");
    size_t numCh = getline(&buff, &sizeAllocated, stdin);
    //numCh = trim(buff);
    printf("%s.", buff);
    
    free (buff);
    buff = NULL;
    
    return 0;
}

Thank you in advance for any help

CodePudding user response:

You have several problems:

numCh must be a ssize_t, which is the preferred type to handle errors on POSIX, use it :)

Always check the result of getline

int i = strlen(s) - 1; is very prone to errors because strlen returns an unsigned (size_t) and an empty string will give you a very big number (SIZE_MAX).

Check if this helps:

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>

static char *trim(char *str)
{
    while (*str && isspace((unsigned char)*str))
    {
        str  ;
    }
    if (*str)
    {
        // Here it is safe to use `strlen - 1` because
        // `if (*str)` ensures that there is a character
        char *end = str   strlen(str) - 1;

        while (*end && isspace((unsigned char)*end))
        {
            end--;
        }
        *(end   1) = '\0';
    }
    return str;
}

int main(void)
{
    char *str = NULL;
    size_t size = 0;
    ssize_t len = 0;

    if ((len = getline(&str, &size, stdin)) != -1)
    {
        const char *trimmed = trim(str);

        printf("<%s>\n", trimmed); // Use trimmed
    }
    free(str);
    return 0;
}

CodePudding user response:

As noted in the other very good answers, you have a type issue with numCh which is technically a POSIX type ssize_t. (one drawback of getline() if needing to port to systems that don't support POSIX -- thankfully, other than a few embedded systems, almost all do)

The other issue is automatically checking strlen(s) - 1 where strlen(s) can very likely be 0 if there is no '\n' to read.

In addition to the other approaches, since you already include string.h the function strcspn() provides a convenient way to locate the '\n' at the end of the string -- returning the number of characters up to the '\n' (if present) -- eliminating the issue of blindly subtracting strlen(s) - 1 potentially resulting in a negative value.

Using strcspn() your trim() function can be written as:

size_t trim (char *s)
{
    size_t len;   /* length of s after trailing whitespace trimmed */
    
    s[(len = strcspn (s, "\n"))] = 0;   /* trim \n saving length */
    
    /* trim remaining whitespace */
    while (len && isspace ((unsigned char)s[len - 1])) {
        len--;
    }
    s[len] = 0;   /* nul-terminate at len */
    
    return len;   /* return new len */
}

(note: all ctype.h classification function require the parameter be unsigned char. While this is virtually guaranteed if reading ASCII from stdin, the pedantic approach is a cast to the proper type)

Additional notes:

  1. you only need one output function for any continual block of text, not one printf() for each line. Additionally, if there are no conversions involved, then fputs() is fine -- though a good optimizing compiler should make the substitution for you,
  2. ALWAYS validate each user-input by checking the return of whatever input function you are using to determine whether the input succeeded or failed. Blindly assuming your input is good tempts Undefined Behavior,
  3. printing buff before and after trim() along with the length provides a convenient visual way of confirming the behavior your want.

Putting the pieces together, you could do:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

size_t trim (char *s)
{
    size_t len;   /* length of s after trailing whitespace trimmed */
    
    s[(len = strcspn (s, "\n"))] = 0;   /* trim \n saving length */
    
    /* trim remaining whitespace */
    while (len && isspace ((unsigned char)s[len - 1])) {
        len--;
    }
    s[len] = 0;   /* nul-terminate at len */
    
    return len;   /* return new len */
}

int main()
{
    char    *buff = NULL;
    size_t  sizeAllocated = 0;
    ssize_t numCh = 0;
    
    fputs ("Begining!!\n> ", stdout);   /* no conversion, fputs() is fine */
    
    /* validate EVERY input function */
    if ((numCh = getline(&buff, &sizeAllocated, stdin)) == -1) {
        fputs ("error: during getline read.\n", stderr);
        return 1;
    }
    
    printf ("buff '%s' (numCh: %zu)\n", buff, numCh);
    numCh = trim (buff);
    printf ("\nbuff '%s' (numCh: %zu)\n", buff, numCh);
    
    free (buff);
}

Example Use/Output

Sentence with 10 additional trailing spaces:

$ ./bin/getline-trimws
Begining!!
> my dog   has fleas
buff 'my dog   has fleas
' (numCh: 29)

buff 'my dog   has fleas' (numCh: 18)

An empty line:

$ ./bin/getline-trimws
Begining!!
>
buff '
' (numCh: 1)

buff '' (numCh: 0)

User generating a manual EOF Ctrl d (or Ctrl z on windows):

$ ./bin/getline-trimws
Begining!!
> error: during getline read.

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

CodePudding user response:

getline reads the newline character if there's space in the buffer passed to it. So you need to look at removing \n as well. But you can use isspace so that trim can handle other whitespace characters too.

Here's a modified version:

int trim (char *s)
{
    size_t i = strlen(s);
    if (i == 0) return 0;
    while(isspace((unsigned char)s[i-1])) {
            i--;
    }
    s[i] = '\0';
    return i;    
}

Since strlen returns size_t, that should be used for i - int may not be able to hold all possible size_t values.

  • Related