Как заставить sscanf читать до символа '\ 0' - PullRequest
0 голосов
/ 21 мая 2018

Я хочу, чтобы имя оставило все символы в строке до '\0'.

#include <stdio.h>

int main(){
    char line[] = "1999-08-01,14.547,0.191,United Kingdom";
    unsigned int year, month, day;
    float temp, uncertainty;
    char name[100];
    sscanf(line, "%u - %u - %u, %f , %f , %s", &year, &month,
                       &day, &temp, &uncertainty, name);
    printf("%u-%u-%u,%lf,%lf,%s\n", year, month, day, temp, uncertainty, name);
}

. Я могу сделать так:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
    char line[] = "1999-08-01,14.547,0.191,United Kingdom";
    char* newline = malloc(strlen(line) + 2);
    strcpy(newline, line);
    newline[strlen(newline)] = '\n';
    newline[strlen(newline)] = '\0';
    unsigned int year, month, day;
    float temp, uncertainty;
    char name[100];
    sscanf(line, "%u - %u - %u, %f , %f , %[^\n]", &year, &month,
                       &day, &temp, &uncertainty, name);
    printf("%u-%u-%u,%lf,%lf,%s\n", year, month, day, temp, uncertainty, name);
}

Но я нахожуэто очень не элегантно.

Ответы [ 4 ]

0 голосов
/ 21 мая 2018

Вариация на @ rici . Хороший подход:

Как заставить sscanf читать до символа '\ 0'

Использовать "%n" чтобы справиться с «остатком строки».
"%n" записывает смещение сканирования до этой точки, если он сделал это слишком далеко.
%*[^\n] сканирует, но не сохраняет все символыдо '\n' на каждый OP, «оставшийся в строке»

Используйте это для выделения конечной строки.

// Some untested code
typedef struct {
  unsigned int year, month, day;
  float temp, uncertainty;
  char *name;
} data_T;

// return 0 on success
int foo(data_T *dest, const char *line) {
  int start = 0;
  int end = 0;
  memset(dest, 0, sizeof *dest); // zero `dest`
  sscanf(line, "%u - %u - %u, %f , %f , %n%*[^\n]%n", 
    &dest->year, &dest->month, &dest->day, &dest->temp, &dest->uncertainty, 
     &start, &end);
  if (start == 0) {
    // line did nor scan properly, return error
    return 1;
  }
  if (end == 0) {
    end = start; // there was no non-white-space text after the `,`
  }
  size_t len = end - start;   
  dest->name = malloc(len + 1u);
  if (dest->name == NULL) {
    // Out of memory
    return 1;
  }
  memcpy(dest->name, line + start, len);
  dest->name[len] = '\0';

  printf("%u-%u-%u,%f,%f, %s\n", 
    dest->year, dest->month, dest->day, dest->temp, dest->uncertainty, dest->name);

  return 0;  // be sure to free dest->name when done with it.
}
0 голосов
/ 21 мая 2018

Несколько дней назад я читал главу 2 из книги «Программирование в Unix: связь, параллелизм, потоки», издание 2003 года, и я изучил пример, который приводил к разбивке строки на токены с помощью пользовательского разделителя (он могбыть или _ или пробел или что-то).Он использовал библиотечную функцию strtok ().Вот пример, настроенный так, чтобы как-то удовлетворить ваши потребности.Я дам 2 файла:

makeargv.c

#include <errno.h>
#include <stdlib.h>
#include <string.h>

/* frees all the memory that was allocated by makeargv */
void freemakeargv(char **argv)
{
   if (argv == NULL)
      return;
   if (*argv != NULL)
      free(*argv);
   free(argv);
}
/* Now the function that breaks string s into tokens */ 
int makeargv(const char *s, const char *delimiters, char ***argvp) 
{
   int error;  int i;
   int numtokens;
   const char *snew;
   char *t;
   if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) 
    { errno = EINVAL; return -1; }
   *argvp = NULL; /* so that a failed call to malloc,will leave it NULL */
      /* now we consume any initial delimiters characters of input s */
   snew = s + strspn(s, delimiters);   /* snew is real start of string */
   if ((t = malloc(strlen(snew) + 1)) == NULL)
      return -1;
   strcpy(t, snew);
   numtokens = 0;
   if (strtok(t, delimiters) != NULL)  /* count number of tokens in s */
      for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;
      /* next,create argument array for ptrs to the tokens */
   if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) 
    {
      error = errno;
      free(t);
      errno = error;
      return -1;
    }
      /* now insert pointers-to-tokens into the argument array */
   if (numtokens == 0)  free(t);
    else 
    {
      strcpy(t, snew);
      **argvp = strtok(t, delimiters);
      for (i = 1; i < numtokens; i++)
          *((*argvp) + i) = strtok(NULL, delimiters);
    }
    *((*argvp) + numtokens) = NULL;      /* append final NULL pointer */
    return numtokens;
}

main.c

#include <stdio.h>
#include <stdlib.h>
int makeargv(const char *s, const char *delimiters, char ***argvp);
void freemakeargv(char **argv);

int main()
{
    char delim[] = ",";
    int i, numtokens;
    char **myargv;  /* memory will be allocated dynamicaly, and has to bee freed before exit */

    char line[] = "1999-08-01,14.547,0.191,United Kingdom-UK";
    if ((numtokens = makeargv(line, delim, &myargv)) == -1) /* nakeargv() allocates memory for myargv, it may fail */
    {
      fprintf(stderr, "Failed to construct an argument array for %s\n", line);
      return 1;
    }
   printf(" The argument array contains:\n");
   for (i = 0; i < numtokens; i++)
      printf("%d:%s\n", i, myargv[i]);
   freemakeargv(myargv);    /* do not forget to free the memory! */
   return 0;
}

gcc -Wall -std = c99 -o токенизатор main.cmakeargv.c

и запустить его

. / tokenizer

0 голосов
/ 21 мая 2018

sscanf не самый элегантный интерфейс, но он имеет много функций.Одним из них является возможность выяснить, где вы находитесь во входной строке, что позволяет извлекать (или просто указывать) «остальную часть ввода».

Например, после;

int nchar = -1;
int nfield = sscanf(line, "%u - %u - %u, %f , %f , %n", &year, &month,
                    &day, &temp, &uncertainty, &nchar);

nchar будет содержать смещение в line поля имени (если оно не равно -1, что указывает на то, что sscanf не может соответствовать строке формата).Если это поле простирается до конца line, вы можете использовать его напрямую (line + nchar) или скопировать в другую строку после проверки, что оно не слишком длинное.

Если line,вопреки его названию, содержит несколько строк, и вы хотите извлечь строку до символа новой строки, вы можете использовать два формата %n с %*[^\n] между ними (звездочка подавляет копию, чтобы избежать проблем с переполнением):

char name[NAME_MAX + 1];
int nstart = -1, nend = -1;
int nfield = sscanf(line, "%u - %u - %u, %f , %f , %n%*[^\n]%n", &year, &month,
                    &day, &temp, &uncertainty, &nstart, &nend);
if (nend > 0) {
  if (nend - nstart <= NAME_MAX) {
    memcpy(name, line + nstart, nend - nstart);
    name[nend - nstart] = 0;
  }
  else {
    /* name is too long */
  }
}
else if (nstart > 0) {
  /* Name was 0 bytes long. Sscanf requires that %[ match at least
   * one character; if not, it fails the scan.
   */
  name[0] = 0; /* Perhaps you wanted to signal an error
}
else {
  /* Line didn't match format */
}

Очевидно, я мог бы избежать использования буфера фиксированной длины и необходимости проверять переполнение, динамически выделяя буфер, когда я знаю, насколько он велик:

char* name = NULL;
// ...
if (nend > 0) 
  name = strndup(line + nstart, nend - nstart);

// or, if you don't like strndup
//   name = malloc(nend - nstart + 1);
//   memcpy(name, line + nstart);
//   name[nend - nstart] = 0;

Если вам действительно нужна динамически размещаемая строка, и у вас есть Posix-совместимый sscanf, вы можете избежать этой проблемы, используя модификатор длины m, который является универсальным простым решением.

char* name = NULL;
int nfield = sscanf(line, "%u - %u - %u, %f , %f , %m[^\n]", &year, &month,
                    &day, &temp, &uncertainty, &name);

Подробнее см. На странице sscanf.Во всех случаях, когда name выделяется динамически, не забывайте освобождать (), когда закончите.

0 голосов
/ 21 мая 2018

Это должно работать:

char line[] = "1999-08-01,14.547,0.191,United Kingdom";
unsigned int year, month, day;
float temp, uncertainty;
char name[100];
sscanf(line, "%u - %u - %u, %f , %f , %99[^\n]", &year, &month,
                   &day, &temp, &uncertainty, name);
printf("%u-%u-%u,%lf,%lf,%s\n", year, month, day, temp, uncertainty, name);

'\n' не будет найдено, но, поскольку предел 99 не будет достигнут, sscanf будет продолжать чтение до маркера конца строки.

...