sscanf()
It wasn’t written for that. It’s to read tabular data, like csv files. If you use a mask with many specifiers --- those things that start with %
and not by %%
, --- can read a certain number of them. But it’s like a vector: the total of specifiers has to be a constant. And it has the throw of pointers. See below.
Example
const char* mascara = "%d %d %d %d %d %d %d %d %d %d %d %d "
// 1 2 .. 12
With this you can read up to 12 of them. sscanf()
will return a number between 0 and 12 indicating how many could read.
A few test lines
const char* linha[] =
{
"-5 -4 -3 -2 -1",
"4 3 2 1",
"3 2 1",
"2 1",
" 1", // #4
"",
NULL
};
Consider these sample lines. Using that mask will read all without problem, but:
has to have a defined maximum of elements (12 in the example)
is very annoying because it has to have all variables in the list of arguments because sscanf()
and family do not allocate memory
consider line #4: sscanf()
will skip any amount of spaces, TAB
and '\n'
trying to satisfy the input, only it won’t tell you how much of the line jumped to get to the 1
. And so you can’t use for example 1 + numero/10
to advance the pointer on the line: you can advance the digits of the number but do not know how many spaces, TAB
and newline
function read and ignored. And still have signal problem because number may be negative
A program to read these lines
#include<stdio.h>
#include<string.h>
int main(void)
{
const char* linha[] =
{
"-5 -4 -3 -2 -1",
"4 3 2 1",
"3 2 1",
"2 1",
" 1", // #4
"",
NULL
};
const char* mascara = "\
%d %d %d %d %d %d %d %d %d %d %d %d "; // 12
int numero = 0;
for(int i = 0; linha[i] != NULL; i++)
{
printf("==> \"%s\"\n", linha[i]);
int res = sscanf(*(linha + i), mascara, &numero, &numero,
&numero, &numero, &numero, &numero, &numero,
&numero, &numero, &numero, &numero, &numero);
printf("sscanf() retornou %d\n", res);
}
return 0;
}
exit
==> "-5 -4 -3 -2 -1"
sscanf() retornou 5
==> "4 3 2 1"
sscanf() retornou 4
==> "3 2 1"
sscanf() retornou 3
==> "2 1"
sscanf() retornou 2
==> " 1"
sscanf() retornou 1
==> ""
sscanf() retornou -1
This program consumes lines up to 12 numbers, no problem. But note that you need 12 pointers in the call. Of course I used the same address and sscanf()
doesn’t care. It’s just to illustrate what I’m explaining. If it’s a small number the maximum number works fine.
But if the number is more than a half dozen better try another way.
Replicating scanf()
A naive path would be to replicate scanf()
, skip whitespace, assemble strings and pass to sscanf()
or atoi()
to get the number :)
Example
void processa_linha(char*);
The following program does this.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void processa_linha(char*);
int main(void)
{
char* linha[] = {"-5 -4 -3 -2 -1",
"4 3 2 1",
"3 2 1",
"2 1",
" 1", // #4
"",
NULL};
int numero = 0;
for (int i = 0; *(linha + i) != NULL; i++)
{ // processa linha 'linha'
char* p = *(linha + i); // o inicio da linha
printf("==>\tLinha %d: \"%s\"\n", 1 + i, p);
processa_linha(p);
}
return 0;
}
void processa_linha(char* linha)
{
printf("\tExtraidos: [ ");
char fase = 0;
long long len = 0;
const char* mascara = "%d";
int numero = 0;
char* p = linha;
char* pal = NULL;
int res = 0;
char* st = NULL;
while (1)
{
switch (fase)
{
case 0:
switch (*p)
{
case 0:
// acabou
printf(" ]\n"); // termina a linha
return;
break;
case ' ':
case '\t':
case '\n':
break;
default:
st = p; // incio de string
fase = 1;
break;
};
p += 1;
break;
case 1:
switch (*p)
{
case ' ':
case '\t':
case '\n':
case 0:
// terminou: monta string
len = p - st;
pal = (char*)malloc(1 + len);
memcpy(pal, st, len);
*(pal + len) = 0; // termina a string
//printf("achou \"%s\"\n", pal);
// sera um numero?
res = sscanf(pal, mascara, &numero);
if (res == 1) printf("%d ", numero);
free(pal);
if (*p == 0)
{
printf("]\n"); // termina a linha
return;
}
fase = 0;
break;
default:
break;
};
p += 1;
break;
}; // switch()
}; // while()
return;
}
example output
==> Linha 1: "-5 -4 -3 -2 -1"
Extraidos: [ -5 -4 -3 -2 -1 ]
==> Linha 2: "4 3 2 1"
Extraidos: [ 4 3 2 1 ]
==> Linha 3: "3 2 1"
Extraidos: [ 3 2 1 ]
==> Linha 4: "2 1"
Extraidos: [ 2 1 ]
==> Linha 5: " 1"
Extraidos: [ 1 ]
==> Linha 6: ""
Extraidos: [ ]
And it can adapt to what it needs. A common way is to use
int** processa_linha(char*);
And return a vector of int*
finished by NULL
. So you can call the function and it returns the neat values for you to process.
Another would be to create something like
typedef struct
{
unsigned N; // quantos
int* valor;
} Valores;
Valores* processa_linha(char*);
To return a vector of int
and the total number on the line
If you need an example of this and can’t find it let me know and I’ll write one and add it here
sscanf()
you fscanf()
If the data is in a file fscanf()
does not have the problem of advancing the string pointer, since the file is, of course, FILE*
a pointer.
So it’s simpler to consume the data.
But to not lose the notion of line it is necessary to use a delimiter, and can not be space nor TAB
. As I said, these functions were written with csv type files in mind. The delimiter can be '\n'
but it wouldn’t be very useful ;)
If you use a comma, the classic, see this program:
#include <stdio.h>
int main(void)
{
FILE* in = fopen("teste.txt", "r");
if (in == NULL) return -1;
const char* mask = "%30[^,\n]%c"; // virgula
char delim;
int linha = 1;
int numero = 0;
int res = 0;
char campo[30];
printf("linha %d: [ ", linha);
while (1)
{
res = fscanf(in, mask, campo, &delim);
switch (res)
{
case -1: // tanto faz se deu erro
case 0:
printf("]\n\n");
return 0;
break;
case 1:
if (sscanf(campo, "%d", &numero) == 1)
printf("%d ", numero);
break;
case 2:
default: // so pode ser 2
if (delim == '\n')
{
linha += 1;
if (sscanf(campo, "%d", &numero) == 1)
printf("%d ]\nlinha %d: [ ", numero, linha);
}
else if (sscanf(campo, "%d", &numero) == 1)
printf("%d ", numero);
break;
}
}; // while()
return 0;
}
who reads this file
-5, -4, x3x, -3, -2, -1
4, 3, 2, 1
3,2,1
2,1
1
x
And shows
linha 1: [ -5 -4 -3 -2 -1 ]
linha 2: [ 4 3 2 1 ]
linha 3: [ 3 2 1 ]
linha 4: [ 2 1 ]
linha 5: [ 1 ]
linha 6: [ ]
Yes, it’s very boring :D