int run_add_line_after(Document *doc, char *command) {
int paragraph_num, line_num;
char com[MAX_STR_SIZE + 1], extra[MAX_STR_SIZE + 2], line[MAX_STR_SIZE + 1];
if (sscanf(command, " %s %d %d %s", com, ¶graph_num, &line_num, extra)
== 4 && paragraph_num > 0 && line_num >= 0 && extra[0] == '*') {
strcpy(line, &(extra[1]));
if (add_line_after(doc, paragraph_num, line_num, line) == FAILURE) {
printf("add_line_after failed\n");
}
return SUCCESS;
}
return FAILURE;
}
I want sscanf to read everything left in command to extra but it's only taking the first word. For example, if I have:
command: "add_line_after 1 0 *first line of the document"
I want to see:
com: "add_line_after"
paragraph_num: 1
line_num: 0
extra: "first line of the document"
but instead I get:
com: "add_line_after"
paragraph_num: 1
line_num: 0
extra: "first"
because %s stops when it hits the space. How do I read the rest of the line while still ignoring any whitespace between '0' and '*'?
For reference, MAX_STR_SIZE is 80 and command is a 1025 character array (though I don't think that matters). Just assume extra is large enough to hold the rest of the line.
sscanf is really the wrong tool to use here. It can be done, but probably should not be used the way you are trying. Fortunately, you are not passing com to add_line_after, which means it is not necessary to ensure that com is a properly null-terminated string, and this allows you to avoid all of that unnecessary string copying. (If you were passing com, you would either have to copy it, or write a null terminator into command.) You don't want or need to use sscanf to move data at all. You can just use it to parse the numeric values. It's not clear to me if you want to discard any whitespace that follows the *, and this code does not. If you want to do so, removing that whitespace is trivial and left as an exercise for the reader:
int
run_add_line_after(struct document *doc, const char *command)
{
int paragraph_num, line_num, n;
const char *line;
if( 2 == sscanf(command, "%*s %d %d %n", ¶graph_num, &line_num, &n)
&& paragraph_num > 0 && line_num >= 0 && command[n] == '*' )
{
line = command + n + 1;
if( add_line_after(doc, paragraph_num, line_num, line) == FAILURE) {
fprintf(stderr, "add_line_after failed\n");
} else {
return SUCCESS;
}
}
return FAILURE;
}
The idea here is to simply use sscanf to figure out where the extra data is in the string and to parse the integer values. This is absolutely the wrong tool to use (the scanf family is (almost) always the wrong tool), and is used here only for demonstration.
-- edit --
But, of course, this doesn't do exactly what you want. It would be much cleaner to move some functionality into add_line_after to handle the newline, but since you also need to remove the newline, it becomes necessary to do something like:
int
run_add_line_after(struct document *doc, char *command)
{
int paragraph_num, line_num, n;
char *line;
if( 2 == sscanf(command, "%*s %d %d %n", ¶graph_num, &line_num, &n)
&& paragraph_num > 0 && line_num >= 0 && command[n] == '*' )
{
line = command + n + 1;
line[strcspn(line, "\n")] = '\0';
if( add_line_after(doc, paragraph_num, line_num, line) == FAILURE) {
fprintf(stderr, "add_line_after failed\n");
} else {
return SUCCESS;
}
}
return FAILURE;
}
This is not ideal. It would be better if you modify the API so that you can avoid both copying the data and modifying the string that you are given.
Got it. Format string should be " %s %d %d %[^\n]s". It will keep reading into the last string variable until it hits the enter key.
Related
I have input like the following:
Someting sth
example
5 15
3
I want to scanf input by lines to get whole content of the line. But when reaching first digit (there can be spaces/tabs before it) I want to scanf it as int.
That's what I have come up with but it does not work as expected - cursor still does not stop at digit character.
char person_name[1000];
int n;
while (scanf("%[^\n/D]%*c", person_name) > 0) {
if (checkIfContainsNumber(person_name) == 0) {
appendToLinkedList(&head_ref, person_name);
} else {
break;
}
}
while (scanf("%d", &n) > 0) {
printf("%d ", n);
}
As far as I understand the problem, each line could be considered either as
a sequence of names or a sequence of integers.
So I would try to read the file line by line and analyse each extracted line
as one sequence or another (spaces are implicitly consumed).
The trick here is the usage of "%n" to go further in the analyse of the same line.
#include <stdio.h>
int
main(void)
{
FILE *input=fopen("input.txt", "r");
if(!input)
{
return 1;
}
char line[1024];
while(fgets(line, sizeof(line), input))
{
int pos=0;
int value, count;
char name[256];
if(sscanf(line+pos, "%d%n", &value, &count)==1)
{
pos+=count;
printf("a line with values: <%d>", value);
while(sscanf(line+pos, "%d%n", &value, &count)==1)
{
pos+=count;
printf(" <%d>", value);
}
printf("\n");
}
else if(sscanf(line+pos, "%255s%n", name, &count)==1)
{
pos+=count;
printf("a line with names: <%s>", name);
while(sscanf(line+pos, "%255s%n", name, &count)==1)
{
pos+=count;
printf(" <%s>", name);
}
printf("\n");
}
}
fclose(input);
return 0;
}
Read the input line-wise with fgets and keep a mode: TEXT for text, NUMBER for numbers and ERROR for an error condition. (The error condition is undescribed. It could occur when you encounter non-numeric data in NUMBER mode, for example.)
Start out with TEXT. Before processing a line in text mode, check whether it could be a digit by a simple sscanf into the line. If you can read a number, switch to number mode, where you scan all numbers from a line.
char line[80];
enum {TEXT, NUMBER, ERROR = -1} mode = TEXT;
while (mode != ERROR && fgets(line, sizeof(line), stdin)) {
if (mode == TEXT) {
int n;
if (sscanf(line, "%d", &n) > 0) mode = NUMBER;
}
if (mode == TEXT) {
line[strcspn(line, "\n")] = '\0';
process_string(line);
} else if (mode == NUMBER) {
char *p = line;
char *end;
int n = strtol(p, &end, 0);
if (end == p) mode = ERROR;
while (end > p) {
process_number(n);
p = end;
n = strtol(p, &end, 0);
}
}
}
(But this approach will fail if the numbers are all in one very long. fgets truncates the input so that the specified size will nor be exceeded.)
Consider changing the scan strategy - ignore all characters that are non-digit, and then read the integer from that digits forward
if ( scanf("%*[^0-9]%d", &n) == 1 ) { ... }
The first field '%*[...]' will skip over anything that is non-digit. Note that it's possible to reach EOF before finding a digit - if statement is needed to check.
Scenario:
I am trying to validate some user-input.
In my case the user is only allowed to
enter between 0 and 3 lowercase characters,
optionally including whitespace.
In Regex: ^[a-z ]{0,3}$
If the user enters more than 3 characters or
the input string contains invalid values,
in each case a different return-value and
error message has to be printed.
What I tried is to read the input into a temporary
char array and defining the scanset as 4[a-z ],
so that only the correct characters will be read
and one char more, in order to check if the maximal
number of desired characters has been read.
I.e. if the last element in this temporary array
is not empty the user input was bigger than 3.
Problem:
When the user enters 3 correct chars
and a 4th wrong char, the 4th won't be read,
therefore we read 3 valid chars,
we "allegedly" never read an invalid char and
the length of read chars is also valid,
all tough it of course is not!
Code:
//--------------------------------------
int scan_input(char* char_array)
{
int status = 0;
int max_size = 3;
char temp_array[max_size+1];
// Print system prompt:
printf("plain text: ");
// Read user input:
status = scanf("%4[a-z ]", temp_array);
if (temp_array[max_size] != '\0')
{
printf("[ERR] too many characters\n");
return -1;
}
if (status != 1)
{
printf("[ERR] invalid characters\n");
return -2;
}
strcpy(char_array,temp_array);
printf("[OK] Input is valid!\n");
return 0;
}
Output:
$ gcc -Wall -std=c11 application.c && ./a.out
plain text: abcD
[OK] Input is valid!
I am grateful for every hint to fix this blind spot!
PS.:
If you know a better approach to solve this problem, than by doing it with scanf() and the scanset, your thoughts are welcome!
to validate some user-input
Separate the input from the parsing
Use a wide buffer and fgets().
char buf[80];
if (fgets(buf, sizeof buf, stdin)) {
// we have some input
Then parse and use "%n", which records the scan position, to test success.
int max_size = 3;
char temp_array[max_size+1];
int n = 0;
temp_array[0] = '\0';
sscanf(buf, "%3[a-z ]%n", temp_array, &n);
bool success = buf[n] == '\n' || buf[n] == '\0';
If sscanf() did not scan anything, n == 0 and the prior temp_array[0] = 0 insures a null character.
If the scan succeeded, n > 0 and code inspects the next character.
Alternative staying with scanf()
status = scanf("%3[a-z ]", temp_array);
// When nothing read, form "" string
if (status != 1) {
temp_array[0] = '\0';
}
bool success = true;
if (status == EOF) {
success = false;
} else {
// consume rest of line, noting if extra junk followed
int next_ch;
while ((next_ch = fgetc(stdin)) != '\n' && next_ch != EOF) {
success = false; //Extra junk
}
}
I am having difficulty scanning from user input an integer (and storing it) only if printed directly after a !:
char cmd[MAX_LINE/2 + 1];
if (strcmp(cmd, "history") == 0)
history(hist, current);
else if (strcmp(cmd, "!!") == 0)
execMostRecHist(hist, current-1);
else if (strcmp(cmd, "!%d") == 0)
num = %d;
else
{//do stuff}
I understand this is completely wrong syntax for strcmp(), but just as an example of how I am gathering user input.
strcmp doesn't know about format specifiers, it just compares two strings. sscanf does what you want: It tests whether a string has a certain format and converts parts of the string to other types.
For example:
int n = 0;
if (sscanf(cmd, " !%d", &num) == 1) {
// Do stuff; num has already been assigned
}
The format specifier %d tells sscanf to look for a valid decimal integer. The exclamation mark has no special meaning and matches only if there is an exclamation mark. The space at the front means that the command may have leading white space. Nothe that there may be white space after the exclam and before the number and that the number may well be negative.
The format specifier is special to the scanf family and related to, but different from the ยด%dformat ofprintf`. Is usually has no meaning in other strings and certainly not when it is found unquoted in the code.
Don't you like writing a checker by yourself?
#include <ctype.h>
#include <stdio.h>
int check(const char *code) {
if (code == NULL || code[0] != '!') return 0;
while(*(++code) != '\0') {
if (!isdigit(*code)) return 0;
}
return 1;
}
/* ... */
if (check(cmd))
sscanf(cmd + 1, "%d", &num);
Use sscanf() and check its results.
char cmd[MAX_LINE/2 + 1];
num = 0; // Insure `num` has a known value
if (strcmp(cmd, "history") == 0)
history(hist, current);
else if (strcmp(cmd, "!!") == 0)
execMostRecHist(hist, current-1);
else if (sscanf(cmd, "!%d", &num) == 1)
;
else
{//do stuff}
I can't seem to figure out, how to correctly read in a .txt file that has the following appereance: (example)
+ 1
+ 2
- 2
+ 5
p -1
? 5
and so on...
what I need now is to store the operator / token which can be '+' '-' 'p' or something like that, and the int that follows in two different variables because I need to check them later on.
char oprtr[1];
int value;
FILE *fp = fopen(args[1], "r");
while(!feof(fp) && !ferror(fp)){
if(fscanf(fp, "%s %d\n", oprtr, &value) < 1){
printf("fscanf error\n");
}
if(strcmp(oprtr, "+") == 0){
function1(bst, value);
} else if(strcmp(oprtr, "-") == 0){
function2(bst, value);
} else if((strcmp(oprtr, "p") == 0) && value == -1){
function3(root);
//some other functions and so on...
}
printing out oprtr and value in the loop shows that they are not being red in correctly, but it does compile. Does someone have a solution?
You have single characters, you can use == to compare them instead of strcmp. Just read the input in pairs and use a switch for example.
char c;
int x;
while(fscanf(fp, "%c %d", &c, &x) == 2)
{ switch(c)
{ case '+': /* ... */
}
}
Your string oprtr is too small to hold anything but an empty string (remember that C strings need a terminating 0 character!). So:
char oprtr[1];
needs to be at least:
char oprtr[2]; // string of maximum size 1
or more defensively:
char oprtr[256]; // string of maximum size 255
You can use the fscanf function, you can get the input from the file.
int fscanf(FILE *stream, const char *format, ...);
fscanf(fp," %c %d",&c,&d);
I have a function to read a text file with the following format
string int int
string int int
string int int
I want to write a function that will assign the values from the text file into variables, but there will also be some cases where the format of the text file will be
string int
string int
string int
In that case, I'd like to set the value of the last int variable to 1. My code I have so far works with the first example but I'm a bit stuck on getting the second scenario to work:
void readFile(LinkedList *inList, char* file)
{
char tempName[30];
int tempLoc, tempNum;
FILE* f;
f = fopen(file, "r");
if(f==NULL)
{
printf("Error: could not open file");
}
else
{
while (fscanf(f, "%s %d %d\n", tempName, &tempLoc, &tempNum) != EOF)
{
insertFirst (inList, tempName, tempLoc, tempNum);
}
}
}
In the second case, fscanf will return 2 instead of 3. So you can rewrite the code like this:
while (1) {
int ret = fscanf(f, "%s %d %d\n", tempName, &tempLoc, &tempNum);
if (ret == EOF) {
break;
}
if (ret == 2) {
tempNum = 1;
} else if (ret != 3) {
// line appear invalid, deal with the error
}
insertFirst (inList, tempName, tempLoc, tempNum);
}
A more hacky way would be to set tempNum to 1 before calling fscanf and just check for EOF as you did above. But I think the code above is clearer.
Edit: to avoid overflows, this would be better. The code would perform better but this is harder to write. Just like above, I did not write any code for the error conditions but you definitely want to handle them
char lineBuf[255];
while (fgets(lineBuf, sizeof(lineBuf), f) != NULL) {
int spaceIdx, ret;
const int len = strlen(lineBuf);
if (len == (sizeof(lineBuf) - 1) {
// line is too long - either your buf is too small and you should tell the user
// that its input is bad
// I recommend to treat this as an error
}
lineBuf[len - 1] = '\0'; // remove \n
--len; // update len, we've removed one character
if (isspace(*lineBuf)) {
// error, line should not start with a space
}
spaceIdx = strcspn(lineBuf, "\t ");
if (spaceIdx == len) {
// error, no space in this line
}
// Ok, we've found the space. Deal with the rest.
// Note that for this purpose, sscanf is a bit heavy handed (but makes the code
// simpler). You could do it with strtol.
// Also, the first space in the format string is important, so sscanf skips
// all the space at the beginning of the string. If your format requires only
// one space between fields, you can do sscanf(lineBuf + spaceIdx + 1, "%d %d"...
ret = sscanf(lineBuf + spaceIdx, " %d %d", &tempLoc, &tempNum);
if (0 == ret) {
// error, no ints
}
else if (1 == ret) {
tempNum = 1;
}
// at that point, you could copy the first part of lineBuf to tempName, but then
// you have to deal with a potential overflow (and spend time on an useless copy),
// so use lineBuf instead
lineBuf[spaceIdx] = '\0';
insertFirst (inList, lineBuf, tempLoc, tempNum);
}