sscanf: get first and last token in a string - c

Is it possible by using sscanf to get the first token then skip some tokens and then get the last one?
For example, the output of /bin/ps -fu1000
cm 2249 1548 0 0:00.00 ttys001 0:00.01 man sscanf
I have tried:
sscanf(line, "%s %[^\n]", user, cmd);
The result should be:
user = "cm";
cmd = "man sscanf":
But it does not work.

Yes, but it's ugly and can't be error checked properly.
/* assuming `user` and `cmd` are character arrays defined with 42 bytes */
if (sscanf(line, "%41s%*s%*s%*s%*s%*s%*s %41[^\n]", user, cmd) != 2) {
/* handle error */
} else {
/* hopefully ok */
}
You can replace some of the %*s with %*d. The * means that the item is parsed but not assigned anywhere (it is ignored).
In the statement, there are 6 ignored items corresponding to the items between "cm" and "man sscanf" in your example.
Also note I limited the input to 41 characters in the scanf itself. Make sure you do not write outside the objects.
EDIT: I added a space before the last conversion because, unlike %s or %d conversions, the %[ conversion does not skip leading whitespace.

Perhaps you'd better look at strtok

Related

How to scanf until I get something not of the same form

I am inputting a text file from Stdin, which will have an undetermined number of coordinates, then a #, then another set of coordinates. How do I scanf the coordinates into a loop that stops once I hit the #, that will let me scanf the rest of the coordinates after the #?
I have tried a couple of loops like:
`while(scanf("[%d,%d]\n", &x, &y) == 1){
//do stuff//
}
but I dont feel like im getting any closer to an answer, any help would be greatly appreciated, cheers
input example (the # doesnt have "" around it but it disappears on here if it doenst):
[0,0]
[1,1]
[2,2]
"#"
[1,3]
[3,6]
[9,8]
You probably want to wrap the code into fgets(or readline):.
char buff[2048] ;
while ( fgets(buff, sizeof(buff), stdin )) {
// Check of 'last' marker
if ( buff[0] == '#' ) break ;
// Check if looks like coordinates
if ( sscanf(buff, "[%d,%d]\n", &x, &y) == 2 ) {
// do something
} ;
} ;
Minor fix to sscanf return code (2 instead of 1) - check for parsing of 2 fields
Your input data delimited by a double-quoted "#" complicates matters slightly. Your attempt to read with scanf is destined to fail. Your scanf format string of "[%d,%d]\n" does not read/discard the '\n' at the end of the line. In fact it doesn't match a '\n' at all. scanf does not interpret control characters, so what the '\n' in your format string is looking for is a literal 'n' leading to an input-failure after the two conversions take place.
You have two options:
remove the '\n' from your format string, continue with scanf (not recommended) and then manually read/discard all remaining characters in the line until a '\n' is reached (using either getchar() or fgetc()); or
read each line into a buffer using a line-oriented input function like fgets() or POSIX getline() to ensure a complete line of data is read each time and then parse the information you need from the buffer with sscanf (preferred method).
Taking the preferred approach above, you could do something similar to:
#include <stdio.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
int main (void) {
char buf[MAXC]; /* buffer for each line */
int x, y, n = 0; /* coordinates & counter */
printf ("set[%d]:", n++); /* initial set[]: label */
while (fgets (buf, MAXC, stdin)) { /* read each line */
if (strncmp (buf, "\"#\"", 3) == 0) /* line starts with "#"? */
printf ("\nset[%d]:", n++); /* output new set[]: label */
else if (sscanf (buf, " [%d,%d]", &x, &y) == 2) /* 2 conversions? */
printf (" %d,%d", x, y); /* output coordinates */
}
putchar ('\n'); /* tidy up with newline */
return 0;
}
(note: if your file simply contained # instead of "#", you could simply check the first character in the buffer instead of using strncmp)
Example Input File
$ cat dat/coordgroups.txt
[0,0]
[1,1]
[2,2]
"#"
[1,3]
[3,6]
[9,8]
Example Use/Output
$ ./bin/readcoords < dat/coordgroups.txt
set[0]: 0,0 1,1 2,2
set[1]: 1,3 3,6 9,8
Look thing over and let me know if you have further questions.

using brackets in printf not working correctly

I am using GCC 6.4 and my printf statement is as follows:
printf("Initial Colour: RGB(%s,%s,%s)\n",userdata[k],userdata[k+1],userdata[k+2]);
It prints )nitial Colour: RGB(1,0.0 which is wrong. Note where the second bracket is printed.
It should be Initial Colour: RGB(1,0.0) as expected.
If I use GCC 4.5 with the same printf, it prints as expected.
What should my printf look like?
You are reading a data file that was created on a Windows system. Lines are terminated by \r\n (carriage return, line feed). You are either processing this file on that same Windows system, but you are opening the file in binary ("rb") mode. Or, you are transferring the file to a Unix or Linux (or Mac) system and processing it there, but you are transferring it in a "binary" mode, that preserves the CRLF, without converting it to the Unix single-newline ('\n') convention.
Then, you are reading lines of text, perhaps with fgets. You are discarding the newline ('\n'), but you are not discarding the carriage return ('\r'). So, each line ends with \r.
Then, you are splitting up the line into fields userdata[0], userdata[1], userdata[2], ... . I'm not sure if you're splitting it up at commas or at whitespace, but in any case, the \r is remaining attached to the last field.
Finally, when you print out that last field userdata[k+2], that carriage return at the end of it is causing the cursor to return to the beginning of the line before the final ) is printed.
You can fix this in several ways:
Don't create the file with \r\n in the first place.
If processing the file on a Windows system, open it in text ("r" or maybe "rt") mode, not binary.
If transferring files from a Windows to a non-Windows system, use a "text" transfer mode that converts line endings.
When reading lines from the file, strip off trailing \r as well as \n characters.
If splitting fields on whitespace, include '\r' in the set of whitespace characters to split on. For example, if you are calling strtok, with separators " " or " \t", change to " \r" or " \t\r".
Now that you've posted code, I can be more specific.
To achieve #4, add the line
if (buffer[strlen(buffer)-1] == '\r') buffer[strlen(buffer) - 1] = '\0';
after the line where you strip off the \n.
To achieve #5, change your two strtok calls to
data = strtok(buffer, " \r");
and
data = strtok(NULL, " \r");
As a matter of fact, you could also change those two lines to
data = strtok(buffer, " \r\n");
and
data = strtok(NULL, " \r\n");
and then you wouldn't need the newline-stripping step at all.
One more thing: your usage of feof is wrong. See Why is while(!feof (fp)) always wrong?.

fopen a file and skip character

Is there a clean way to open a file like this without system calls:
ID*_LogConfig.csv
I tried the following but it didn't worked.
/*Read setup file*/
getcwd(cwd, sizeof(cwd));
snprintf(source, sizeof(source),"%s/ID%*d_LogConfig.csv",cwd);
if( NULL == (input = fopen(source,"r")))
{
snprintf(errbuffer,sizeof(errbuffer), "Could not open file %s - check existence/rights\n", source);
exitHandler(1, errbuffer);
}
It outputs:
/mnt/dataflash/canfilter.d/ID*_LogConfig.csv not found
But with e.g. cat /mnt/dataflash/canfilter.d/ID*_LogConfig.csv it shows the file content.
My compromise solution would be a system call ll ID*_LogConfig.csv and using the output as filename.
This line
snprintf(source, sizeof(source),"%s/ID%*d_LogConfig.csv",cwd);
likely does not produce what you think it does.
The %*d portion is an format specifier with a field-width, per the POSIX printf() documentation
A field width, or precision, or both, may be indicated by an
( '*' ). In this case an argument of type int supplies the
field width or precision. Applications shall ensure that arguments
specifying field width, or precision, or both appear in that order
before the argument, if any, to be converted. A negative field width
is taken as a '-' flag followed by a positive field width. A negative
precision is taken as if the precision were omitted. In format strings
containing the "%n$" form of a conversion specification, a field width
or precision may be indicated by the sequence "*m$", where m is a
decimal integer in the range [1,{NL_ARGMAX}] giving the position in
the argument list (after the format argument) of an integer argument
containing the field width or precision, for example:
printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
So, this line
snprintf(source, sizeof(source),"%s/ID%*d_LogConfig.csv",cwd);
expects two more integer arguments to be passed. Since you don't pass them, you invoke undefined behavior.
See this answer: https://stackoverflow.com/a/19897395/4756299
Is there a clean way to open a file like this without system calls
No.
fopen() makes use of a system call. You cannot 'open' a file without a system call.
If you're referring to the system(3) function, then you're probably in for some pain - it's best to avoid it if possible, from a performance, reliability and security point of view.
If you want to open 'all files that match the pattern', then look at glob(3), which is likely what your shell is using to handle such wildcards.
You will need to iterate over each of the resulting paths, calling fopen(), fread() and fclose() on each.
Example usage of glob(3):
#include <stdio.h>
#include <glob.h>
void main(void) {
int ret;
int i;
glob_t glob_info;
ret = glob("*.csv", 0, NULL, &glob_info);
if (ret != 0)
{
fprintf(stderr, "glob() failed: %d\n", ret);
return;
}
for (i = 0; i < glob_info.gl_pathc; i++) {
printf("%d: %s\n", i, glob_info.gl_pathv[i]);
}
globfree(&glob_info);
}
It is not really a good idea to open lots of files and treat the stream as a single 'thing' (as you are doing with your cat example).
As #Andrew has pointed out, you must be careful with your use of printf() format strings...
You have provided the following: %s/ID%*d_LogConfig.csv. A % denotes the beginning of a format specifier, you have thus given the following:
%s - a char * (string) parameter follows
%*d - similar to %d, but the * means that the precision is provided as an int parameter, followed by the number itself.
For example:
printf(">%s< >%*d<\n", "Hello", 5, 3);
Will output: (note the 5 characters that the %d outputs)
>Hello< > 3<
If you are after a *, then just put a * in the format string.
If you are after a %, then you need to escape the % but putting %% in the format string.
Ok I solved the "problem" by using the following:
(Processing the output of ls -t and use the newest file as config-file)
/*Search for config-file*/
FILE *file_config;
file_config = popen("ls -t ID*_LogConfig.csv","r");
if (file_config == NULL) {
exitHandler(1, "Error opening date pipe.");
}
fgets(configfile, sizeof(configfile), file_config);
if (strlen(configfile) > 0)
configfile[strlen(configfile)-1] = '\0';
else {
exitHandler(1, "Could not find a ID*_LogConfig.csv\n");
}
getcwd(cwd, sizeof(cwd));
snprintf(source, sizeof(source),"%s/%s",cwd,configfile);
/*Read setup file*/
if( NULL == (input = fopen(source,"r")))
{
snprintf(errbuffer,sizeof(errbuffer), "Could not open file |%s| - check existence/rights\n", source);
exitHandler(1, errbuffer);
}
It seems that this is the only simple way.
Thanks to all.

String compare isn't working correctly in C

I can't figure out why my string compare isn't comparing correctly. This is for C.
It's reading from a file that is set up like this:
1 - ls
2 - cd
3 - history
If I type !c it's suppose to grab the last used string that started with 'c' and run the command. Yet it never goes into the if(strcmp(())=0) line.
Part of my code is here:
char currLine[MAXINPUTLINE];
else if (isalpha(input[1])){
int count = 1;
fileRead = fopen(".simpleshell_history", "r");
while(fscanf(fileRead, "%s\n", currLine) != EOF){
printf(input+1);
printf(currLine);
if(strcmp((input+1), currLine) == 0){
printf("%s\n", currLine);
parse(currLine);
}
}
}
This is what the printf in the while loop prints, I can't figure out how to fix this and I've been stuck on it for a while. This is when I enter '!c'
c
1c
-c
lsc
2c
-c
cdc
3c
-c
historyc
4c
-c
!c!c
This loop:
while(fscanf(fileRead, "%s\n", currLine) != EOF) {
will read whitespace delimeted tokens into currLine rather than lines. So the first iteration will be 1, the second -, the 3rd ls, etc as you are seeing with your printfs. The name currLine suggests you want to read lines rather than tokens.
You then compare the read tokens with the rest of your input line which is apparently "c\n". Since you never get a token with a newline, it never matches. Even if you got rid of the newline, it would never match, as your file does not contain the token c
edit
You say you want to compare the rest of the input line against a prefix of the command on
the line. To do that you want to first figure out how long the prefix is, then use strncmp. You also want to parse the line from the file to separate the command from the index. So you could do something like:
else if (isalpha(input[1])){
int count = 1;
int pfxlen = 1;
while (!isspace(input[pfxlen+1])) pfxlen++;
fileRead = fopen(".simpleshell_history", "r");
while(fscanf(fileRead, "%d - %[^\n]", &index, currLine) == 2) {
if(strncmp((input+1), currLine, pfxlen) == 0) {
printf("%s\n", currLine);
parse(currLine);
}
}
}
If input is the string !c and you are looking to match that against the line 2 - cd, you will have to be careful. strcmp will definitely not work, as it will only return success if the two strings it is comparing are exact matches.
In order to test whether one string (cd) starts with another string (c), you want to use strncmp(), which will take a limit on the number of characters to compare.
Also: you will need to be careful to start comparing from the second character of input (skipping the !) and from the fifth character of currLine (skipping the 2 - characters).
This should get you there:
while (fgets(currLine, sizeof currLine, fileRead) != NULL) {
printf(input+1);
printf(currLine);
if (strncmp(input + 1, currLine + 4, strlen(input)-1) == 0) {
printf("%s\n", currLine);
parse(currLine);
}
}
One possibility is that input may contain a trailing newline, while curline definitely will not because of the scanf specification.
Your problem is with the manner in which you get input. (notice when you print, it has a line feed). It has a trailing \n and your currLine does not. Hence the compare fails.
Suggest OP uses fgets() for both user and file input.
Something like
char buf[MAXINPUTLINE];
while(fgets(buf, sizeof buf, fileRead) != NULL) {
int LineNo;
if (sscanf(buf, "%d - %s", &LineNo, currLine) != 2) Handle_UnexpectedInput();
...
}
Note: "%s\n" does the same as "%s " does the same as "%s\t": %s skips optional leading whitespace, then scans non-whitespace. The whitespace after s in "%s\n" scans for optional whitespace.

Parsing Specific Input File Using Fscanf

i`ve been trying o use fscanf to read the an input file on my application.
This is the input file
3
Process#1 - 2Kb
3
exec 20
io 5
exec 50
Process#2 - 8Kb
1
exec 100
Process#3 - 8Kb
4
exec 50
io 50
exec 50
io 50
First of all i want to read the First "3", which i am having no problems doing so.
After that, i will need to read the information after the # (which is 1) and the number right after the "kb" string (which is 2).
Why is this fscanf failing to do so ?
fscanf(inputFile, "Process#%d - %dKb", &id, &mem );
How can i do it ?
Thanks in advance
Probably because the newline left behind after the 3 is not being recognized by the P in Process.
This is why many people avoid scanf(); it is usually simpler to use fgets() or a related function (but not gets()!) and then use sscanf().
Demo code
Notice the rigorous checking of the return from scanf(). If you don't do that, you will not know when things have gone wrong. Note that the check is for the correct number of conversions, too.
#include <stdio.h>
int main(void)
{
int i;
int id;
int mem;
if (scanf("%d", &i) != 1)
printf("oops!\n");
/* With space - reads past newline on first line */
/* Without space - prints 'thank goodness!' */
if (scanf(" Process#%d - %d", &id, &mem) != 2)
printf("thank goodness!\n");
return 0;
}

Resources