Using fscanf to read an unknown number of commands - c

I'm reading from a file descriptor (fp) to get my commands from a client to my TCP server. The current way to get the command looks something like this
char cmd[21];
while ( fscanf( fp, "%s", cmd ) == 1 && strcmp( cmd, "quit" ) != 0 ) {
//do work
}
My issue is that one of the commands can be "move" which is followed by two integers. I know that I can do something like
char cmd[21];
int row = 0;
int col = 0;
fscanf( fp, "%s%d%d", cmd, &row, &col );
but I can't do that unless I know that the cmd is "move". Is there a way that I can get the command string, check for "move" and then get the integers?

You can certainly do that. The trick is to wait until you know what kind of command you are processing.
At some point in your code your program would need to do something like this:
if (strcmp(cmd, "move") == 0) {
...
}
This is a good place to read your optional parameters for the move command:
fscanf( fp, "%d%d", &row, &col );
Note: It is not safe to use %s, no matter how big a buffer you allocate. You should always specify a limit for the number of characters your command buffer is prepared to accept, like this:
fscanf( fp, "%20s", cmd )
// ^^

You can't easily do it exactly as you've outlined above, but you could do something like:
if (2 == fscanf(fp, "move %d %d", &row, &col))
move(row, col);
// possibly similar checks for other commands here.
else if (fscanf(fp, "quit"))
quit();
If the commands you need to support are all known when you write the code, this can be easier/simpler than reading the command, sorting out which command you have, then reading parameters appropriate for that command.
Conversely, if you might (for example) have some sort of plug-ins that add new commands to be supported, then you probably want to read the command, check it against some collection of installed commands (e.g., a hash table) and use that to look up how to process that command (e.g., at least find a number signifying the number of parameters to read for that command, and a function to which to pass those parameters for processing).

In the while loop body:
if(strcmp(cmd, "move") == 0) {
fscanf(fp, "%d%d", &row, &col);
}
Note that your code as it is written now is not safe, due to the fact that a buffer overrun will occur if the command is longer than 20 characters. You may wish to use, for example, "%20s" as your format string, to limit the length of the command.
Alternatively, you can use fgets() to get a line of command, then parse it afterwards.

Related

how to extaract data from file starting from 2nd line in c

Im very new to this language, can you help me:
Instead of making the user input col, row, and direction(scanf). I want to extract the data from file(format below)
From the file format i do not want to extract the first line(5,6), i only want to extract the remaining lines.
Below is a code of how to extract data from a file(using command line arguments), but this code extract the first line also, and only prints the lines.I do not want to print the line but to extract the data from a file instead of making the user input it.
File format:
colrow direction(starting from 2nd line)
5,6
A0 H
D0 V
C1 V
A4 H
F0 v
code of scanf
yourcolumn = getchar();
col = charToNum(yourcolumn); //function to input column
printf("enter row");
scanf("%d",&row);
printf("h: horizontally or v: vertically?\n");
scanf(" %c",&direction);
Code for extracting data from file:
#include <stdio.h>
int main(int argc, char* argv[])
{
char const* const fileName = argv[1]; /* should check that argc > 1 */
FILE* file = fopen(fileName, "r"); /* should check the result */
char line[256];
while (fgets(line, sizeof(line), file)) {
/* note that fgets don't strip the terminating \n, checking its
presence would allow to handle lines longer that sizeof(line) */
printf("%s", line);
}
/* may check feof here to make a difference between eof and io failure -- network
timeout for instance */
fclose(file);
return 0;
}
Since you are reading line-by-line, I suggest you restructure you file reading to match your logic
while (EOF != fscanf(file, "%[^\n]\n", line)) {
printf("> %s\n", line);
}
Is a way that one can read every line, one at a time. You can lookup the caveats of using fscanf and how to adjust the code to safely read without overflowing your line buffer.
Then, if you want to skip the first line, your code could look like this
if (EOF != fscanf(file, "%[^\n]\n", line)) {
// skip the first line
}
while (EOF != fscanf(file, "%[^\n]\n", line)) {
printf("> %s\n", line);
}
And your processing logic will look a lot like your mental process.
Yes, you could use a line counter, and only process if the counter is high enough; but, it is generally better to avoid introducing variables, if you can live without them. This is because an extra added variable doesn't make the code too hard to reason about; but, after you've repeated that "extra variable" rationale five or six times, the code quickly turns into something that's harder to maintain and harder to reason about. By the time you hit twenty or more extra variables, the odds of maintaining the code quickly without breaking it are lower.
Read the first line also with fgets() into a string and then scan the string for row, direction.
char line[256];
if (fgets(line, sizeof(line), file)) {
if (sscanf(line, "%d %c", &row, &direction) != 2) {
printf("Invalid first line '%s'\n", line);
} else {
while (fgets(line, sizeof(line), file)) {
printf("%s", line);
}
}
}

How do I delete specific line and replace it

This is the code.
FILE* fPtr;
FILE* fTemp;
char path[100];
char buffer[BUFFER_SIZE];
char newline[BUFFER_SIZE];
int line, count;
printf("Enter path of source file: ");
scanf("%s", path);
printf("Enter line number to replace: ");
scanf("%d", &line);
/* Remove extra new line character from stdin */
fflush(stdin);
printf("Replace '%d' line with: ", line);
scanf("%s", &newline);
/* Open all required files */
fPtr = fopen(path, "r");
fTemp = fopen("replace.tmp", "w");
/* fopen() return NULL if unable to open file in given mode. */
if (!fPtr)
{
/* Unable to open file hence exit */
printf("\nUnable to open file.\n");
printf("Please check whether file exists and you have read/write privilege.\n");
exit(EXIT_SUCCESS);
}
/*
* Read line from source file and write to destination
* file after replacing given line.
*/
count = 0;
while ((fgets(buffer, BUFFER_SIZE, fPtr)) != 0)
{
count++;
/* If current line is line to replace */
if (count == line)
fputs(newline, fTemp);
else
fputs(buffer, fTemp);
}
/* Close all files to release resource */
fclose(fPtr);
fclose(fTemp);
/* Delete original source file */
remove(path);
/* Rename temporary file as original file */
rename("replace.tmp", path);
printf("\nSuccessfully replaced '%d' line with '%s'.", line, newline);
return 0;
I wanted to replace a line supposedly the content of the text file is this
> Andy,06/05/2000,US,0654852,254845,313132
> Fan,865644,4654654,654654,465456
> Ben,04/01/1995,SG,0674874,213454,132158
Supposedly I wanted to change the of Fan so I run the code above, it gave me this. I do not want this to happen.
> Andy,06/05/2000,US,0654852,254845,313132
> Fanny,865644,4654654,654654,465456Ben,04/01/1995,SG,0674874,213454,132158
And if I want to change the name of Andy it gave me this
Landy,06/05/2000,US,0654852,254845,313132Fanny,865644,4654654,654654,465456Ben,04/01/1995,SG,0674874,213454,13215
Why it does that?
How do I delete specific line and replace it ?
Assume that the replacement line has a different size than the original one. You cannot do that in standard C11 (check n1570) without copying the file to a new place (because you cannot overwrite a sequence of bytes in the middle of a file by another sequence of different length).
Read carefully the documentation of <stdio.h>
Lines are just a convention in C: they are ending by some end-of-line character (\n). A file could have a single line and contain a megabyte.
So you could use getline to read lines. Or use fgets. In both cases you should check for failure. With fgets what would happen if the line is bigger than the buffer? With getline what would happen with a file containing a single line of a gigabyte which does not fit into memory?
Be aware that stdout is buffered (and the buffer size could vary from one run to the next one and could be different if you use command pipelines). See setvbuf and fflush. In practice, take the habit of ending your printf format control string with \n and/or explicitly calling fflush
Many open source programs doing what you want already exist. GNU ed comes to mind. Consider studying its source code for inspiration.
Please read how to debug small programs. If you use a recent GCC compiler with some GDB debugger, compile with all warnings and debug info, so gcc -Wall -Wextra -g then use gdb to understand the behavior of your program. Specify on paper the input file syntax using EBNF and read more about parsing techniques, including recursive descent parsing.
Notice that:
fflush(stdin);
is undefined behavior. You should fflush output streams only.
PS. You could later read about databases then consider using sqlite.
fgets will read from the file up to and including the newline character at the end of the line. The scanf call you use to get the replacement string does not, so when you write out newline it does not contain a newline character.
Solutions include explicitly adding the newline (possibly with fputc('\n', fTemp);, or using fgets(newline, BUFFER_SIZE, stdin); instead of the scanf to read your input string.
Yes I want to use
fgets(new,line,buffer_sizze,stdin);
but it just won't ask for input unless I put it inside of main().
When I put it inside of a function that I created, it won't ask for input from the user which is why I used scanf.
Is there a way to put it \n without asking the user to type \n.
Or any solution to why it's not getting input when I used fgets.
For the people that has the same problem as me.
Fgets not asking for any input.
Try use getchar().
That solved my problem.
For unknown reason.

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.

C, reading a multiline text file

I know this is a dumb question, but how would I load data from a multiline text file?
while (!feof(in)) {
fscanf(in,"%s %s %s \n",string1,string2,string3);
}
^^This is how I load data from a single line, and it works fine. I just have no clue how to load the same data from the second and third lines.
Again, I realize this is probably a dumb question.
Edit: Problem not solved. I have no idea how to read text from a file that's not on the first line. How would I do this? Sorry for the stupid question.
Try something like:
/edited/
char line[512]; // or however large you think these lines will be
in = fopen ("multilinefile.txt", "rt"); /* open the file for reading */
/* "rt" means open the file for reading text */
int cur_line = 0;
while(fgets(line, 512, in) != NULL) {
if (cur_line == 2) { // 3rd line
/* get a line, up to 512 chars from in. done if NULL */
sscanf (line, "%s %s %s \n",string1,string2,string3);
// now you should store or manipulate those strings
break;
}
cur_line++;
}
fclose(in); /* close the file */
or maybe even...
char line[512];
in = fopen ("multilinefile.txt", "rt"); /* open the file for reading */
fgets(line, 512, in); // throw out line one
fgets(line, 512, in); // on line 2
sscanf (line, "%s %s %s \n",string1,string2,string3); // line 2 is loaded into 'line'
// do stuff with line 2
fgets(line, 512, in); // on line 3
sscanf (line, "%s %s %s \n",string1,string2,string3); // line 3 is loaded into 'line'
// do stuff with line 3
fclose(in); // close file
Putting \n in a scanf format string has no different effect from a space. You should use fgets to get the line, then sscanf on the string itself.
This also allows for easier error recovery. If it were just a matter of matching the newline, you could use "%*[ \t]%*1[\n]" instead of " \n" at the end of the string. You should probably use %*[ \t] in place of all your spaces in that case, and check the return value from fscanf. Using fscanf directly on input is very difficult to get right (what happens if there are four words on a line? what happens if there are only two?) and I would recommend the fgets/sscanf solution.
Also, as Delan Azabani mentioned... it's not clear from this fragment whether you're not already doing so, but you have to either define space [e.g. in a large array or some dynamic structure with malloc] to store the entire dataset, or do all your processing inside the loop.
You should also be specifying how much space is available for each string in the format specifier. %s by itself in scanf is always a bug and may be a security vulnerability.
First off, you don't use feof() like that...it shows a probable Pascal background, either in your past or in your teacher's past.
For reading lines, you are best off using either POSIX 2008 (Linux) getline() or standard C fgets(). Either way, you try reading the line with the function, and stop when it indicates EOF:
while (fgets(buffer, sizeof(buffer), fp) != 0)
{
...use the line of data in buffer...
}
char *bufptr = 0;
size_t buflen = 0;
while (getline(&bufptr, &buflen, fp) != -1)
{
...use the line of data in bufptr...
}
free(bufptr);
To read multiple lines, you need to decide whether you need previous lines available as well. If not, a single string (character array) will do. If you need the previous lines, then you need to read into an array, possibly an array of dynamically allocated pointers.
Every time you call fscanf, it reads more values. The problem you have right now is that you're re-reading each line into the same variables, so in the end, the three variables have the last line's values. Try creating an array or other structure that can hold all the values you need.
The best way to do this is to use a two dimensional array and and just write each line into each element of the array. Here is an example reading from a .txt file of the poem Ozymandias:
int main() {
char line[15][255];
FILE * fpointer = fopen("ozymandias.txt", "rt");
for (int a = 0; a < 15; a++) {
fgets(line[a], 255, fpointer);
}
for (int b = 0; b < 15; b++) {
printf("%s", line[b]);
}
return 0;
This produces the poem output. Notice that the poem is 14 lines long, it is more difficult to print out a file whose length you do not know because reading a blank line will produce the output "x�oA". Another issue is if you check if the next line is null by writing
while (fgets(....) != NULL)) {
each line will be skipped. You could try going back a line each time to solve this but i think this solution is fine for all intents.
I have an even EASIER solution with no confusing snippets of puzzling methods (no offense to the above stated) here it is:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string line;//read the line
ifstream myfile ("MainMenu.txt"); // make sure to put this inside the project folder with all your .h and .cpp files
if (myfile.is_open())
{
while ( myfile.good() )
{
getline (myfile,line);
cout << line << endl;
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
Happy coding

Optional read from STDIN in C

My application is basically a shell which expects an input of type cmd [x], where cmd is constant and x is optional. So cmd 1 is legal as well as cmd by itself - then I assume a default parameter for x.
I am doing this:
char cmd[64];
scanf("%s", cmd);
int arg;
scanf("%d", &arg); // but this should be optional
How can I read the integer parameter, and set it to a default if none is currently available in the prompt? I do not want the prompt to wait for additional input if it was not given in the original command.
I tried several versions using fgetc() and getchar() and comparing them to EOF but to no avail. Each version I tried ends up waiting on that optional integer parameter.
The easy way:
char b[73]; //powers of 2 magic is evil.
if(fgets(b,sizeof b,stdin) != NULL) {
char cmd[59]; //59, no command will ever be > 58. Ever I say.
int arg;
if(sscanf(b,"%58s %d",cmd,&arg) == 2) {
//handle cmd and arg
} else if(sscanf(b,"%58s",cmd) == 1) {
//handle cmd only
} else {
// :-/
}
}
Simple answer, you can't. The C runtime takes input from the OS, but doesn't control it. To do something like this you will need to interact directly with the OS using platform specific APIs.
Are you reading line-by-line? Can't you just read the whole command until you reach a "\n" (newline)? If you get two tokens before the newline, it is a command and the argument; if you read only one, it is the command only and you set the second argument to the default.
Here's a program that works (sorry my previous answer was made in haste).
int main(){
char cmd[100], line[100];
int man = 0;
printf("OK: ");
fgets(line, 100, stdin);
int num = sscanf(line,"%s %d",cmd,&man);
if (num==1)printf("one! %s %d\n", cmd, man);
else if (num==2)printf("two! %s %d\n", cmd, man);
}
fgets reads the line (with bounds checking), and sscanf will assess whether one or two tokens were entered.

Resources