Macro to do fgets stripping newline

Viewed 169

Given the following two statements to use fgets and strip the newline:

puts("Enter the name.");
fgets(temp.name, 40, stdin);
temp.name[strcspn(temp.name, "\n")] = 0;

Is the following macro sufficient to take the place of this?

#define FGETS(str, len)  fgets(str, len, stdin); str[strcspn(str, "\n")] = 0
FGETS(temp.name, 40);

Are there any deficiencies or ways it can be improved?

4 Answers

File input/output is a back-hole of time. Little to gain using a macro over a clean function.

Perhaps a function to do the prompt too?

Code uses a leading size per C2x priciple.

char *getaline(int size, char *s, const char *prompt) {
  if (prompt) {
    fputs(prompt, stdout);
  }
  if (fgets(s, size, stdin)) {
    // Perhaps add detection of failing to read the whole line?

    s[strcspn(s, "\n")] = '\0';
    return s;
  }
  if (size > 0) {
    *s = '\0';
  }
  return NULL;
}

Some remarks:

  • When a macro is composed with several instructions, it is preferable to surround it with "do { ... } while(0)" to be able to use it in "if" statements without brackets, prevent some arithmetic operations on the macros...
  • The parameters of the macros must be surrounded by parenthesis as they may not be simple variables but some expressions;
  • fgets() returns NULL if EOF is entered or upon error;

Hence the following proposition:

#define FGETS(str, len) do {  if (fgets((str), (len), stdin) != NULL) (str)[strcspn((str), "\n")] = 0; else (str)[0] = '\0'; } while(0)

Then you can use it as:

if (expression)
  FGETS(str, len);
else
  do_something_else;
...
FGETS(buf + 34, LEN);

N.B.: This page provides lots of tips to make secured macros.

#define READLINE(str, len) do { if(fgets((str), (len), stdin) != NULL) { (str)[strcspn((str), "\n")] = 0; } else { /*TODO: Handle error*/ } } while (0)

Notice, I specifically renamed it from FGETS because it has hard-coded stdin as the "file" part. In my mind it's misleading to call it FGETS. Also, added brackets to arguments (pretty much a must for macros), wrapped it in a do - while(0) (here's why) and a basic framework for handling errors.

Usually, I do the do { } while (0); trick.

But, if you want a macro that is a drop in replacement for fgets where you can test the return value transparently, how about:

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

#define FGETS(_buf,_len,_xf) \
    ({ \
        char *_cp = fgets(_buf,_len,_xf); \
        if (_cp != NULL) \
            _buf[strcspn(_buf,"\n")] = 0; \
        _cp; \
    })

#define FGETOF(_buf,_xf) \
    FGETS(_buf,sizeof(_buf),_xf)

int
main(void)
{
    char buf[100];

    while (1) {
        if (FGETOF(buf,stdin) == NULL)
            break;
        printf("buf: '%s'\n",buf);
    }

    return 0;
}

The multiline macro is fine, but can be cleaned up [with no loss in speed] using an inline function:

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

static inline char *
xfgets(char *buf,size_t siz,FILE *xf)
{
    char *cp;

    cp = fgets(buf,siz,xf);

    if (cp != NULL)
        buf[strcspn(buf,"\n")] = 0;

    return cp;
}

#define FGETS(_buf,_len,_xf) \
    xfgets(_buf,_len,_xf)

#define FGETOF(_buf,_xf) \
    FGETS(_buf,sizeof(_buf),_xf)

int
main(void)
{
    char buf[100];

    while (1) {
        if (FGETOF(buf,stdin) == NULL)
            break;
        printf("buf: '%s'\n",buf);
    }

    return 0;
}
Related