Подстрока C переменной длины в согласованном формате файла

У меня есть следующая строка из файла:

Name:   variable_length_string

и я хотел бы извлечь подстроку:

variable_length_string

от него. Вот что у меня есть:

  char* getProcStatus(const char* path) {
      printf("path: %s", path);
      char* chrptr_ProcessStatus = NULL;
      FILE* fd_status = fopen (path, "rt");
      char line[300];
      char name[5];                                                                                                                                                                           
   
      char* procName = NULL;
      if ( fd_status ) {
          if (fgets(line, sizeof(line), fd_status) != NULL) {
              sscanf(line, "%s%s", name, procName);
              if (procName != NULL) {
                  printf("%s", procName);
                  fclose(fd_status);
                  return procName;
              }
          }
      }

      return procName;
  }

Я попытался создать буфер имен только для того, чтобы где-то сохранить «Имя:», но он вообще не работает (если (procName != NULL)) не выполняется.

Как получить подстроку переменной длины? Все, что я знаю, это то, что оно заканчивается на '\n', а строка всегда начинается с 'Next:'

Обновлено:

Я понял, что моя ошибка заключалась в том, что я не выделил память для procName. Добавление этой строки решает проблему:

procName = malloc(200);

Но я до сих пор не понял, как захватить всю строку до символа новой строки, потому что переменная_длина_строки может быть «строкой переменной длины» и содержать в ней пробелы.

Думаю, я спрашиваю, возможно ли регулярное выражение в sscanf для сопоставления регулярного выражения с чем-то вроде этого:

[a-zA-Z\-/]*$

Хорошо, сначала вы считываете данные строки в line, которая находится в стеке, и возвращаете на нее указатель. Когда функция выходит за пределы области видимости, стек освобождается. Вместо этого вам нужно будет strdup() это сделать, и вызывающему абоненту нужно будет free() это сделать.

trojanfoe 15.05.2024 20:37

@trojanfoe: код не возвращает указатель на line.

Eric Postpischil 15.05.2024 20:38

@EricPostpischil Действительно; однако это явно намерение.

trojanfoe 15.05.2024 20:41

Цель состоит в том, чтобы вернуть procName, а не всю строку, прочитанную fgets, как указано в «return procName».

Mathew 15.05.2024 20:42

@trojanfoe: Это явно не намерение. Очевидно, что OP ожидает, что после sscanf(line, "%s%s", name, procName);procName будет содержать указатель на нужные данные. Нет никаких указаний на то, ожидают ли они, что это будет указатель на line или указатель на вновь выделенную память. (Существуют расширения sscanf, которые распределяют память таким образом при наличии подходящей спецификации преобразования, хотя они, конечно, требуют, чтобы передавалось &procName, а не procName. Код OP не может работать ни с одним из этих ожиданий.)

Eric Postpischil 15.05.2024 20:46

@EricPostpischil Ну, по крайней мере, мне это было ясно. Почему бы вам не адресовать свои комментарии ОП и не уйти от меня?

trojanfoe 15.05.2024 20:51

<O/T> нет необходимости использовать оба return с представленным кодом, вы не в зациклении (может быть, так и должно быть?). Последний return появится сразу после fclose.

yano 15.05.2024 20:53

Почему бы просто не использовать strtok(line, ":"), чтобы разделить «имя» и «данные»? После этого используйте простую функцию для удаления начального и конечного пробелов из обеих строк. Я делал это, может быть, сотни раз, и это работает очень хорошо.

Some programmer dude 15.05.2024 21:01

@Someprogrammerdude Я даже не знал об этой функции ... не могли бы вы опубликовать ее как ответ с помощью fn, чтобы удалить ведущие и конечные пробелы?

Mathew 15.05.2024 21:03

@trojanfoe: Потому что утверждение в вашем комментарии на самом деле неясно и, следовательно, может привести ОП и других к неправильной логике рассуждений, поэтому на это следует указать. Нет никаких причин, по которым ваши комментарии должны быть защищены от критики и исправлений.

Eric Postpischil 15.05.2024 21:04

Или проблема в том, что вы не знаете, прочитали ли fgets всю строчку? Тогда у вас есть два варианта: 1) Считать посимвольно в массив, который вы выделяете и перераспределяете по мере необходимости, пока не получите новую строку (или EOF); Или 2) Используйте fgets для чтения в цикле, пока не будет прочитана полная строка (что можно обнаружить либо по возврату NULL, либо по тому, что строка заканчивается переводом строки). Как и прежде, выделяйте и перераспределяйте память по мере необходимости.

Some programmer dude 15.05.2024 21:04

fgets определенно читает всю строку (по крайней мере, в моем случае), но теперь, когда я думаю об этом, это хорошая идея, потому что эти имена procNames, я думаю, превысят 500 символов.

Mathew 15.05.2024 21:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
12
100
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Я не думаю, что sscanf (line, "%s%s", ... поступит так, как вы ожидаете. Первый %s, вероятно, загрузит всю строку в первый аргумент.

В приведенном ниже коде я ищу символ :, а затем увеличиваю указатель после всех пробелов. strdup используется для выделения указателя, который можно вернуть. Дополнительные улучшения могут заключаться в удалении всех конечных пробелов в строке.

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

char *
getProcStatus (const char* path) {
  FILE  * fd_status = fopen (path, "rt");
  char  line [300];
  char  * procName = NULL;
  const char  * tptr;

  printf ("path: %s\n", path);

  if ( fd_status ) {
    if (fgets (line, sizeof(line), fd_status) != NULL) {
//      sscanf (line, "%s%s", name, procName);
      tptr = strchr (line, ':');
      if (tptr == NULL) {
        fclose (fd_status);
        return NULL;
      }
      ++tptr;
      while (*tptr == ' ') {
        ++tptr;
      }
      if (*tptr == '\n' || *tptr == '\0') {
        fclose (fd_status);
        return NULL;
      }
      procName = strdup (tptr);
      size_t len = strlen (procName);
      if (len > 0 && procName [len - 1] == '\n') {
        procName [len - 1] = '\0';
      }
      printf ("procname: %s\n", procName);
    }
  }

  fclose (fd_status);
  return procName;
}

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

  str = getProcStatus ("zz.txt");
  if (str != NULL) {
    free (str);
  }
}

Я внес изменения в исходное сообщение, выделение памяти решило проблему, sscanf заполнит предоставленные буферы и отбросит остальные, поэтому sscanf(line, "%s%s", name, extra) заполнит имя "Name:" и extra на «extra», если строка имеет вид «Name: extra», где разделителем по умолчанию является пробел. Теперь я пытаюсь проверить, возможно ли регулярное выражение

Mathew 15.05.2024 20:58

Я редко использую семейство scanf, что объясняет мое невежество. Как отмечено в комментариях, strtok (лучше strtok_r) очень полезно.

Brad Lanam 15.05.2024 21:07

Или я мог быть невежественным, используя scanf вместо strtok ;P

Mathew 15.05.2024 21:08

Это продолжение моего ответа на ваш предыдущий вопрос: Программа на C для подсчета общего использования памяти любым процессом

Несколько вопросов...

  1. (Как упоминалось другими) код должен использовать strdup, потому что буфер основан на стеке.
  2. В синтаксисе есть двоеточие (:): Sym : Val
  3. Код не выбирает какой-либо конкретный символ

Вот пример программы. Это переписанный на C код perl, который у меня валялся:

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

#define DVAL_BAD        (1LL << 62)

typedef long long s64;
struct symval {
    const char *sym;
    const char *val;
    s64 dval;
};

// sysproc_status -- load up data from /proc/<pid>/status
struct symval *
sysproc_status(pid_t pid)
{
    char file[100];
    char buf[1000];
    char *sym;
    char *val;
    int symcount = 0;
    struct symval *symlist = NULL;

    do {
        // open the file -- bug out on error
        sprintf(file,"/proc/%d/status",pid);
        FILE *xf = fopen(file,"r");
        if (xf == NULL)
            break;

        // read in all lines
        // FORMAT:
        // Sym     :      Value
        while (1) {
            // get next line
            sym = fgets(buf,sizeof(buf),xf);
            if (sym == NULL)
                break;

            // strip newline
            buf[strcspn(buf,"\n")] = 0;

            // look for ":" separater
            val = strchr(buf,':');
            if (val == NULL)
                continue;
            *val = 0;

            // remove trailing whitespace from symbol
            for (sym = val - 1;  sym >= buf;  --sym) {
                if (! isspace(*sym))
                    break;
                *sym = 0;
            }
            sym = buf;

            // remove leading whitespace from value
            for (val += 1;  *val != 0;  ++val) {
                if (! isspace(*val))
                    break;
            }

            // increase size of list
            symlist = realloc(symlist,sizeof(*symlist) * (symcount + 1 + 1));
            if (symlist == NULL) {
                perror("malloc");
                exit(1);
            }

            // save data
            struct symval *symcur = &symlist[symcount++];
            symcur->sym = strdup(sym);
            symcur->val = strdup(val);

            // a convenience: get binary value
            if (isdigit(*val))
                symcur->dval = strtoll(val,&val,10);
            else
                symcur->dval = DVAL_BAD;

            // add EOT
            ++symcur;
            symcur->sym = NULL;
            symcur->val = NULL;
        }

        fclose(xf);
    } while (0);

    return symlist;
}

// sysproc_delete -- delete symbol list
void
sysproc_delete(struct symval *symlist)
{
    struct symval *symcur;

    do {
        if (symlist == NULL)
            break;

        for (symcur = symlist;  symcur->sym != NULL;  ++symcur) {
            free((void *) symcur->sym);
            free((void *) symcur->val);
        }

        free(symlist);
    } while (0);
}

// symval_print -- print a single key/value pair
void
symval_print(const struct symval *symcur)
{
    printf("%s: '%s'",symcur->sym,symcur->val);
    if (symcur->dval != DVAL_BAD)
        printf(" (%lld)",symcur->dval);
    printf("\n");
}

// sysproc_print -- print all list values
void
sysproc_print(const struct symval *symlist)
{
    const struct symval *symcur = symlist;

    if (symcur != NULL) {
        for (;  symcur->sym != NULL;  ++symcur)
            symval_print(symcur);
    }
}

// sysproc_find -- find a given entry by symbol name
const struct symval *
sysproc_find(const struct symval *symlist,const char *sym)
{
    const struct symval *symcur = symlist;
    const struct symval *symret = NULL;

    for (;  symcur->sym != NULL;  ++symcur) {
        if (strcmp(symcur->sym,sym) == 0) {
            symret = symcur;
            break;
        }
    }

    return symret;
}

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

    --argc;
    ++argv;

    pid_t pid;
    if (argc > 0)
        pid = atoi(*argv);
    else
        pid = getpid();

    struct symval *symlist = sysproc_status(pid);
    sysproc_print(symlist);

    printf("\n");
    const struct symval *find = sysproc_find(symlist,"VmSize");
    if (find != NULL)
        symval_print(find);

    sysproc_delete(symlist);

    return 0;
}

Вот результат:

Name: 'procstatus'
Umask: '0022' (22)
State: 'R (running)'
Tgid: '3077758' (3077758)
Ngid: '0' (0)
Pid: '3077758' (3077758)
PPid: '5951' (5951)
TracerPid: '0' (0)
Uid: '1000  1000    1000    1000' (1000)
Gid: '1000  1000    1000    1000' (1000)
FDSize: '64' (64)
Groups: '470 1000 ' (470)
NStgid: '3077758' (3077758)
NSpid: '3077758' (3077758)
NSpgid: '3077758' (3077758)
NSsid: '5951' (5951)
VmPeak: '2516 kB' (2516)
VmSize: '2300 kB' (2300)
VmLck: '0 kB' (0)
VmPin: '0 kB' (0)
VmHWM: '756 kB' (756)
VmRSS: '756 kB' (756)
RssAnon: '64 kB' (64)
RssFile: '688 kB' (688)
RssShmem: '4 kB' (4)
VmData: '176 kB' (176)
VmStk: '132 kB' (132)
VmExe: '8 kB' (8)
VmLib: '1460 kB' (1460)
VmPTE: '40 kB' (40)
VmSwap: '0 kB' (0)
HugetlbPages: '0 kB' (0)
CoreDumping: '0' (0)
THP_enabled: '1' (1)
Threads: '1' (1)
SigQ: '0/47763' (0)
SigPnd: '0000000000000000' (0)
ShdPnd: '0000000000000000' (0)
SigBlk: '0000000000000000' (0)
SigIgn: '0000000000000000' (0)
SigCgt: '0000000000000000' (0)
CapInh: '0000000000000000' (0)
CapPrm: '0000000000000000' (0)
CapEff: '0000000000000000' (0)
CapBnd: '0000003fffffffff' (3)
CapAmb: '0000000000000000' (0)
NoNewPrivs: '0' (0)
Seccomp: '0' (0)
Speculation_Store_Bypass: 'thread vulnerable'
Cpus_allowed: 'ff'
Cpus_allowed_list: '0-7' (0)
Mems_allowed: '00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001' (0)
Mems_allowed_list: '0' (0)
voluntary_ctxt_switches: '0' (0)
nonvoluntary_ctxt_switches: '0' (0)

VmSize: '2300 kB' (2300)

Спасибо! Я обязательно проверю и постараюсь следовать этому, очень ценю потраченное вами время! Посмотреть, как пишут другие, более опытные программисты, очень полезно для тех, кто учится впервые.

Mathew 15.05.2024 21:38
Ответ принят как подходящий

Когда мне нужно прочитать данные «ключ-значение» из файла (или где-то еще), я обычно использую функцию strtok, чтобы разделить ключ и данные на отдельные строки.

Вероятно, вам придется удалить начальные и конечные пробелы как для ключа, так и для данных.

Тогда возможное решение будет примерно таким:

// Take a pointer to the first character in a string, and returns
// a pointer to the first non-space character of it
// Effectively "stripping" leading space
static char *strip_leading(char *str)
{
    if (str == NULL)
        return NULL;  // Just to be safe

    size_t i;
    for (i = 0; isspace(str[i]) != 0; ++i)
    {
        // Empty
    }

    // Here the variable i will be the index of the first non-space character
    return &str[i];
}

// Skips over all trailing spaces in a null-terminated string,
// and set the null-terminator at the last of those
// Effectively "stripping" all trailing space
static char *strip_trailing(char *str)
{
    size_t length = strlen(str);

    if (str == NULL || length == 0)
        return NULL;  // Just to be safe

    char *last = &str[length - 1];

    for (; isspace(*last); --last)
    {
        // Empty
    }

    // Here last will point to the last non-space character of the string
    *(last + 1) = '\0';  // Replace next (space) character with the terminator

    // Return a pointer to the beginning of the string
    return str;
}

// Strip both leading and trailing space from a string
static char *strip(char *str)
{
    return strip_leading(strip_trailing(str));
}

struct data
{
    char *key;
    char *value;
};

// This function takes a "line" of input, and extracts the "key" and "value" from it
// Returns a structure holding the key and value as strings
static struct data get_key_value(char *line, const char *separator)
{
    if (separator == NULL)
    {
        // No separator provided, default to ':'
        separator = ":";
    }

    // Get the key
    char *key = strtok(line, separator);
    if (key == NULL)
    {
        // Error, return a structure with null key/data pointers
        return (struct data){ NULL, NULL };
    }

    // Get the value
    char *value = strtok(NULL, "");  // Use "" as the separator to read until the end
    if (value == NULL)
    {
        // Error, return a structure with null key/data pointers
        return (struct data){ NULL, NULL };
    }

    // Strip leading and trailing spaces from both key and value
    key = strip(key);
    value = strip(value);

    // Return a structure with *duplicates* of the key and value
    // Those need to be passed to `free` once done with them
    return (struct data){ strdup(key), strdup(value) };
}

// Finally a function to read all the input from the file
static void get_all_key_value(FILE *file)
{
    if (file == NULL)
    {
        return;
    }

    char line[512];  // No need to be greedy about the space

    while (fgets(line, sizeof line, file) != NULL)
    {
        char *linep = strip_leading(line);

        // If the first non-space character is a '#', then this line is a comment
        if (linep[0] == '#')
        {
            continue;  // Continue with the next line
        }

        // Now get the key and value from the line
        struct data data = get_key_value(linep, NULL);

        // TODO: Do something with the returned structure!
    }
}

Вот пример приведенного выше кода, читающего строки из стандартного ввода.

Спасибо! Можете ли вы объяснить эту строку: return (struct data) {strdup(key), strdup(value)}, зачем создавать дубликаты?

Mathew 16.05.2024 17:12

@Mathew Потому что функция не знает времени жизни исходной строки, переданной в функцию get_key_value. Таким образом, возвращаемая структура становится независимой от файла и данных, считанных из файла, и вы можете использовать ее еще долго после завершения функции чтения файла.

Some programmer dude 16.05.2024 17:17

аааа, окей, понял, спасибо! Я использую приведенный выше код (надеюсь, вы не возражаете), и мне интересно, как мне передать файл в программу после его компиляции. Я пробовал echo /proc/[PID]/status > ./strip (здесь Strip — скомпилированный результат), но безуспешно

Mathew 16.05.2024 17:36

@Mathew Операция > — это перенаправление в файл. Он перезапишет файл. И echo напечатает аргумент как строку, поэтому echo /proc/[PID]/status запишет /proc/[PID]/status в стандартный вывод. Вам следует использовать команду cat и конвейер: cat /proc/[PID]/status | ./strip. Не забудьте пересобрать программу, поскольку предыдущее перенаправление перезаписало ее.

Some programmer dude 16.05.2024 17:40

что-то вроде этого: [a-zA-Z\-/]*$

Просканируйте строку с помощью "%*[-/a-zA-Z]" и выполните проверку ошибок.

  • Ставьте '-' первым в скансете.

  • Не нужно бежать /.

  • Используйте *, чтобы сканировать, но не сохранять.

  • Используйте "%n" для записи смещений.

  • Нажмите "Name: " , чтобы просмотреть ключевое слово и использовать пустое пространство для опций.

  • Сканирование определяет только смещение длины нужного токена. Более поздний код выделяет память и копирует ее.

        if (fgets(line, sizeof(line), fd_status) != NULL) {
          int *start;
          int *end = 0;
          sscanf(line, "Name: %n%*[-/a-zA-Z]%n", &start, &end);
          if (end == 0 || line[end+1] != '\n') {
            Handle_incomplete_scan();
          }
    
          // Since 'end' is not 0, scan completed. 
          size_t len = (size_t) (end - start);
          char *procName = malloc(len + 1);
          if (procName == NULL) {
            Handle_out_of_memory();
          }
    
          memcpy(procName, line + start, len);
          procName[len] = '\0';
    
          // Use string in procName
    
          // Free when done
          free(procName);
    

Другие вопросы по теме