У меня есть следующая строка из файла:
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\-/]*$
@trojanfoe: код не возвращает указатель на line.
@EricPostpischil Действительно; однако это явно намерение.
Цель состоит в том, чтобы вернуть procName, а не всю строку, прочитанную fgets, как указано в «return procName».
@trojanfoe: Это явно не намерение. Очевидно, что OP ожидает, что после sscanf(line, "%s%s", name, procName);procName будет содержать указатель на нужные данные. Нет никаких указаний на то, ожидают ли они, что это будет указатель на line или указатель на вновь выделенную память. (Существуют расширения sscanf, которые распределяют память таким образом при наличии подходящей спецификации преобразования, хотя они, конечно, требуют, чтобы передавалось &procName, а не procName. Код OP не может работать ни с одним из этих ожиданий.)
@EricPostpischil Ну, по крайней мере, мне это было ясно. Почему бы вам не адресовать свои комментарии ОП и не уйти от меня?
<O/T> нет необходимости использовать оба return с представленным кодом, вы не в зациклении (может быть, так и должно быть?). Последний return появится сразу после fclose.
Почему бы просто не использовать strtok(line, ":"), чтобы разделить «имя» и «данные»? После этого используйте простую функцию для удаления начального и конечного пробелов из обеих строк. Я делал это, может быть, сотни раз, и это работает очень хорошо.
@Someprogrammerdude Я даже не знал об этой функции ... не могли бы вы опубликовать ее как ответ с помощью fn, чтобы удалить ведущие и конечные пробелы?
@trojanfoe: Потому что утверждение в вашем комментарии на самом деле неясно и, следовательно, может привести ОП и других к неправильной логике рассуждений, поэтому на это следует указать. Нет никаких причин, по которым ваши комментарии должны быть защищены от критики и исправлений.
Или проблема в том, что вы не знаете, прочитали ли fgets всю строчку? Тогда у вас есть два варианта: 1) Считать посимвольно в массив, который вы выделяете и перераспределяете по мере необходимости, пока не получите новую строку (или EOF); Или 2) Используйте fgets для чтения в цикле, пока не будет прочитана полная строка (что можно обнаружить либо по возврату NULL, либо по тому, что строка заканчивается переводом строки). Как и прежде, выделяйте и перераспределяйте память по мере необходимости.
fgets определенно читает всю строку (по крайней мере, в моем случае), но теперь, когда я думаю об этом, это хорошая идея, потому что эти имена procNames, я думаю, превысят 500 символов.





Я не думаю, что 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», где разделителем по умолчанию является пробел. Теперь я пытаюсь проверить, возможно ли регулярное выражение
Я редко использую семейство scanf, что объясняет мое невежество. Как отмечено в комментариях, strtok (лучше strtok_r) очень полезно.
Или я мог быть невежественным, используя scanf вместо strtok ;P
Это продолжение моего ответа на ваш предыдущий вопрос: Программа на C для подсчета общего использования памяти любым процессом
Несколько вопросов...
strdup, потому что буфер основан на стеке.:): Sym : ValВот пример программы. Это переписанный на 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)
Спасибо! Я обязательно проверю и постараюсь следовать этому, очень ценю потраченное вами время! Посмотреть, как пишут другие, более опытные программисты, очень полезно для тех, кто учится впервые.
Когда мне нужно прочитать данные «ключ-значение» из файла (или где-то еще), я обычно использую функцию 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 Потому что функция не знает времени жизни исходной строки, переданной в функцию get_key_value. Таким образом, возвращаемая структура становится независимой от файла и данных, считанных из файла, и вы можете использовать ее еще долго после завершения функции чтения файла.
аааа, окей, понял, спасибо! Я использую приведенный выше код (надеюсь, вы не возражаете), и мне интересно, как мне передать файл в программу после его компиляции. Я пробовал echo /proc/[PID]/status > ./strip (здесь Strip — скомпилированный результат), но безуспешно
@Mathew Операция > — это перенаправление в файл. Он перезапишет файл. И echo напечатает аргумент как строку, поэтому echo /proc/[PID]/status запишет /proc/[PID]/status в стандартный вывод. Вам следует использовать команду cat и конвейер: cat /proc/[PID]/status | ./strip. Не забудьте пересобрать программу, поскольку предыдущее перенаправление перезаписало ее.
что-то вроде этого:
[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);
Хорошо, сначала вы считываете данные строки в
line, которая находится в стеке, и возвращаете на нее указатель. Когда функция выходит за пределы области видимости, стек освобождается. Вместо этого вам нужно будетstrdup()это сделать, и вызывающему абоненту нужно будетfree()это сделать.