I am working on a c project and I need to remove a file from within a directory. For some reason though it keeps on saying that it can't delete because the file or directory doesn't exist. Below is the code that I am using to remove the file.
void deleteOldestLog()
{
FILE *fp;
char path[FILE_PATH_BUF_LEN], *fileName;
fp = popen("ls -tr /home/myfolder/logs/ |head -1", "r");
if (fp == NULL)
{
printf("Failed to run command");
}
else
{
char removalPath[FILE_PATH_BUF_LEN];
while ((fileName = fgets(path, sizeof(path)-1, fp)) != NULL)
{
sprintf(removalPath, "/home/myfolder/logs/%s", fileName, sizeof(fileName)-1);
printf("Removing file: %s", removalPath);
if (remove(removalPath) != 0)
{
perror("ERROR DELETING LOG");
}
else
{
printf("Successfully deleted %s", removalPath);
}
break;
}
pclose(fp);
}
}
Even though it says that it can't find the file because it doesn't exist I know that this isn't true because if I run ll followed by the path that the c program printed it returns the file that I am trying to delete.
I think it might be because fgets is putting '\0' on the end of the string which is stopping the remove from working.
How can I fix this?
There's a newline at the end of the file name read by fgets(). Your file name doesn't actually end with a newline.
You attempt to remove the newline with:
sprintf(removalPath, "/home/myfolder/%s", fileName, sizeof(fileName)-1);
However, to be effective, you'd need to use strlen() instead of sizeof(), and you'd need to modify the format string:
sprintf(removalPath, "/home/myfolder/%.*s", (int)strlen(fileName)-1, fileName);
The argument for the * must be an int and strlen() returns a size_t; hence the cast. (GCC will warn about that sort of thing if you turn on the warnings; use at least -Wall.)
A tip for you: when in doubt, print the string. I'll typically use a format like this. Note the angle brackets around the string:
printf("Removing: <<%s>>\n", removalPath);
When you see:
Removing: <</home/myfolder/something
>>
you know there's a problem with a newline in the string. Without the markers, you might not notice that there's a newline in the string causing the extra newline in the output.
Why does the format string need to be modified?
Let's look at the original sprintf() again:
sprintf(removalPath, "/home/myfolder/%s", fileName, sizeof(fileName)-1);
The format string expects 1 argument, a string. The call provides two values, a string and a length. So, the first problem is that there is a left-over argument. This usually does no damage, but be aware of it. Presumably, the reason for passing the length minus one was to lose the last character. The formats in the printf() family can be adorned with one or two numbers, and either or both can have a * instead of an integer value. These numbers constrain the lengths of the formatted value. When you write:
%.*s
you state the length of the output shall be exactly the length specified by an int value passed as an argument before the string itself. Hence the revision:
sprintf(removalPath, "/home/myfolder/%.*s", (int)strlen(fileName)-1, fileName);
(which I just fixed while adding this information.)
I've also not added error checking to the output of sprintf() etc. That's not unusual; however, best coding practices do ensure that functions like sprintf() return the value you expect (which is the number of characters written to the string, excluding the trailing null '\0'.
(Aside: in general, it is better to use snprintf() than sprintf(); that can avoid buffer overflows.
snprintf(removalPath, sizeof(removalPath), "/home/myfolder/%.*s",
(int)strlen(fileName)-1, fileName);
However, the behaviour of the *snprintf() functions under MSVC is different from the behaviour mandated by the C Standards (C99, C11). Worse, in the case of vsnprintf_s() and the other _s functions, the argument lists are different between the MSVC and the C Standard.)
Related
Very new to programming. I am trying to create a txt file that asks the user for a file name and then text for the file. At first I got an error about a null so I put an if statement and it seems like the code cannot open the file with that name. Tried to do some research and ended up making some changes but still resulted in the same error.
include <stdio.h>;
include <stdlib.h>;
int main()
{
char *fileName[100];
char inputText[100];
printf("What is the .txt file name? \n");
scanf_s("%123s", &fileName);
strcat(fileName, ".txt");
FILE *textFile;
textFile = fopen_s(&textFile, fileName, "w");
if (textFile != 0)
{
printf("Cannot get file");
return -1;
}
printf("What should be written in the text file? \n");
scanf_s("%123s", &inputText);
fprintf(textFile, "%s", inputText);
fclose(textFile);
return 0;
}
The most important thing you can do in learning C is to -- slow down. There is a lot to learn and you have to take it one step at a time. There is no use in guessing, compiling, seeing if anything changes, changing something else and (repeat). Look it up.
That said, you are interested in basic input/output to/from stdin/stdout and to a file opened for writing. It is unclear whether you are working on windows with scanf_s or using the non _s version as they are mixed and matched below. Regardless, the primary difference there will be the required parameters.
In C, you declare the arrays to hold your filename and inputtext to hold 100 characters. When working with strings, each string requires a nul-terminating character at the end ('\0'... or just 0, numerically the same). That means you can store a maximum of 99 characters +1 nul-terminating character in either filename or inputtext (side note: C generally avoids mixed-case variable names in favor of all lower-case, but that is up to you)
To protect against writing beyond the end of your filename or inputtext, you need to insure that you limit the number of characters you attempt to store in either. You do that with the field-width option to the format specifier. e.g.,
scanf ("%99s", inputtext);
or for the windows _s version:
scanf_s ("%99s", inputtext, 100u);
However, using a format specifier of "%99s" does not allow the input to include whitespace as the %s format specifier will read up to the first whitespace or newline. Second, it does NOT read (or in anyway handle) the '\n' at the end of user-input generated as the result of pressing [Enter]. This will cause problems if your next input is character input as scanf will happily accept '\n' as the next character to be read. Now %s will skip leading whitespace ('\n' being whitespace) should not present a problem, but this is the level of thought process you must go through in forming something as simple as your scanf format string.
Get in the habit of accounting for all characters in the input stream every time. That way you are not caught off-guard with some error you cannot explain.
To allow your input to contain whitespace, you can use a character class format specifier for scanf. For instance you could use "%99[^\n]" as the format string. However the character class does not automatically ignore leading whitespace, but you can provide that flexibility by leaving a space before the % beginning the format specifier, e.g. " %99[^\n]". It is important. (it is also why fgets or POSIX getline are generally preferred over scanf for handling user-input.
Now how do you handle the '\n' you left in the input buffer (e.g. stdin here)? In addition to leaving the space, you can make use of the assignment suppression operator within the format string. " %99[^\n]%*c" The %*c is a format specifier for reading a character %c, but by including the '*' (assignment suppression operator), you tell scanf to read and discard the character.
It is not enough to simply provide the correct format specifier when taking user-input. You must VALIDATE that you have actually received the input you expect. With any of the input routines, that, at minimum, means checking the return for scanf (or fgets or getline, etc..). For scanf, the return is the "match count", which is the number of successful conversions performed according the the format string. e.g. the %s (or %[^\n]) constitute a request for a single conversion. (any conversion associated with the assignment suppression operator is NOT included in the match count) So your anticipated return is the number of conversions in your format string. Putting that together, you could handle your inputtext with:
printf ("What should be written in the text file? "); /* prompt */
/* validate user input -- limit to 99 chars (+1 for nul char) */
if (scanf (" %99[^\n]%*c", inputtext) != 1) {
fprintf (stderr, "error: invalid input (inputtext).\n");
return 1;
}
Wouldn't the same thing also work for filename? Answer: No. Why? You plan on appending ".txt" to filename after entered by the user, right? How many characters are in ".txt"? Answer: 4 (you will only have 1 nul-terminating char for the combined string). So what must you limit filename to? " %95[^\n]%*c"
To do file I/O, you have several choices. By far the fstream buffered I/O functions are the most common for basic text I/O. In order to read from, or write to, a file, you must first open a FILE stream. You do that by declaring a FILE *pointer and then calling fopen and then checking the return (the value of pointer) to validate your file was successfully opened. The same rules, format specifiers, etc.. apply to reading/writing to a file (on disk), just as they do to writing to stdin or stdout as all are simply files from C's perspective.
With that in mind, you could do something similar to the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAXC = 100 };
int main (void) {
/* declare and initialize variables */
char filename[MAXC] = "", inputtext[MAXC] = "";
FILE *fp = NULL;
printf ("What is the .txt file name? "); /* prompt */
/* validate user input -- limit to 95 chars */
if (scanf (" %95[^\n]%*c", filename) != 1) {
fprintf (stderr, "error: invalid input (filename).\n");
return 1;
}
strcat (filename, ".txt"); /* +4 chars = 99 chars */
/* open file/validate file open for reading */
if (!(fp = fopen (filename, "w"))) {
fprintf (stderr, "error: file open failed '%s'.\n", filename);
return 1;
}
printf ("What should be written in the text file? "); /* prompt */
/* validate user input -- limit to 99 chars (+1 for nul char) */
if (scanf (" %99[^\n]%*c", inputtext) != 1) {
fprintf (stderr, "error: invalid input (inputtext).\n");
return 1;
}
/* output status to stdout & inputtext to fp */
printf ("\nwriting to '%s'\n%s\n", filename, inputtext);
fprintf (fp, "%s\n", inputtext);
if (fclose (fp)) /* close file - validate stream close */
fprintf (stderr, "error: on file stream close.\n");
return 0;
}
note: after writing to a file, it is important to check the return of fclose to insure a stream error did not occur during the write. (for closing streams you read from, that concern isn't there)
Example Use/Output
$ ./bin/inputtext
What is the .txt file name? dat/inputtext
What should be written in the text file? A quick brown fox jumps over the lazy dog.
writing to 'dat/inputtext.txt'
A quick brown fox jumps over the lazy dog.
Check the file contents:
$ cat dat/inputtext.txt
A quick brown fox jumps over the lazy dog.
Look the code over and let me know if you have any questions regarding any character in the code. Everyone needs a little help getting started, and the most important thing I can convey is to slow down and understand every character you code, read and understand your compiler warnings (fix every one), and if you are not sure about what you are doing, look it up. Either man function on Linux/Unix, or search MSDN for windows (e.g. scanf_s,...). They tell you in reasonably clear term what type and requirements there are for every parameter to every function (and a lot provide examples).
Good luck with your coding.
According to the MSDN documentation fopen_s takes an argument of form FILE** rather than FILE*. It also returns an error code rather than the file handle, which is not the return value of the function. So what you've done in this code is overwritten your file handle with some irrelevant integer. If you store your error code in a different variable it should resolve that issue.
More information on fopen_s can be found here: https://msdn.microsoft.com/en-us/library/z5hh6ee9.aspx
I am trying to search a string in a text file,when the text file is like what given below :
"Naveen; Okies
PSG; Diploma
SREC; BECSE"
When output console ask for input string and when i type naveen it will result in printing Okies, when i typed PSG it will print Diploma. This works fine as I am using the below code :
fscanf(fp, "%[^;];%s\n", temp, Mean);
However below text file is not working,
"Naveen; Okies Is it working
PSG; Diploma Is it working
SREC; BECSE Is it working"
My code still gives me Okies as output for Naveen, where i need "Okies Is it working" as output.
So i changed my code to fscanf(fp, "%[^;];%[^\n]s", temp, Mean); where i am getting 'Okies Is it working' as output. But for searching string it's not searching next line. When i search PSG, I dont get any ouput.
Kindly help me to understand my issue.
Side-bar
Note that you should check the return value from fscanf().
You say you tried:
fscanf(fp, "%[^;];%[^\n]s", temp, Mean);
This is probably a confused format. The s at the end is looking for a literal s in the input, but it will never be found and you'll have no way of knowing that it is not found. The %[^\n] scan set conversion specification looks for a sequence of 'non-newlines'. It will only stop when the next character is a newline, or EOF. The s therefore is a literal s that will never be matched. But the return values from fscanf() is the number of successful conversions, which would probably be 2. You have no way of spotting whether that s was read. It should be removed from the format string.
Main answer
To address your main question, the %s format stops at the first blank. If you want to process the whole line, don't use %s. Use either POSIX getline() or standard C fgets() to read the line, and then analyze it.
You can analyze it with strtok(). I wouldn't do that in any library code because any library function that calls strtok() cannot be used from code that might also be using strtok(), nor can it call any function where that function, or one of the functions it calls directly or indirectly, uses strtok(). The strtok() function is poisonous — you can only use it in one function at a time. These comments do not apply to
strtok_r() or the analogous Microsoft-provided variant strtok_s() — which is similar to but different from the strtok_s() defined in optional Annex K of C11. The variants with a suffix are reentrant and do not poison the system like strtok() does.
I'd probably use strchr() in this context; you could also look at
strstr(),
strpbrk(),
strspn(),
strcpsn(). All of these are standard C functions, and have been since C89/C90.
I think Jonathan has explained it pretty well, however, I am adding a sample to show how to deal with your example for learning purposes. Bear in mind you might wanna change some functions as I used the deprecated (insecure) ones, probably this could be an exercise for you.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main(int argc, char *argv[]) {
FILE *fp = NULL;
char szBuffer[1024] = { '\0' };
char szChoice[256] = { '\0' };
char szResult[256] = { '\0' };
if ((fp = fopen("test.txt", "r")) == NULL) {
printf("Error opening file\n");
return EXIT_FAILURE;
}
printf("Enter your choice: ");
scanf("%s", &szChoice);
while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) {
if (!strncmp(szBuffer, szChoice, strlen(szChoice))) {
char *pch = szBuffer;
pch += (strlen(szChoice) + 1);
printf("Result: %s", pch);
}
}
getchar();
return EXIT_SUCCESS;
}
I want to fscanf a csv file which is a output of fprintf, I set the same format but it didn't work, which means when I use that function to fscanf the file I just made, it didn't succuss, even didn't get into the while-loop. So, how to modify it to make it work?
Below part of my code
part of fprintf
fp = fopen("Out.csv", "w");
fprintf(fp, "%99s,%d,%99s,%99s\n", current->group, current->id, current->name,
current->address);
part of fscanf
fp = fopen("Out.csv", "r");
while (fscanf(fp, "%99s,%d,%99s,%99s\n", group, &id, name, address) == 4) {
head = push_sort(head, group, name, id, address);
printf("%99s", name);
}
I suspect it's because "%s" specifier in *scanf() family stops scanning when it finds a white space, you can tell fscanf() which specific character to ignore, and it will stop at that character.
I believe the following format string will work
"%99[^,],%d,%99[^,],%99[^,\n]\n"
read this link to find out why I think the pattern will work, search specifically for the [ specifier.
The *scanf() functions are hard, it's always difficult to make them work correctly, although if you are generating the line and you're sure of what it contains and no surprises will happen, you can trust it to work.
You will be safe if you check the return value, which you do, so if you fail to read lines that you consider valid, then you can try to fgets() a line from the file, and parse it with strchr() or strtok(), I prefer strchr() because
It doesn't need to alter the input string.
It's thread safe and reentrant.
It allows you to infere more, like the length of the token, without strlen().
#include <stdlib.h>
#include <stdio.h>
int main() {
char ch, file_name[25];
FILE *fp;
printf("Enter the name of file you wish to see\n");
gets(file_name);
fp = fopen(file_name,"r"); // is for read mode
if (fp == NULL) {
printf(stderr, "There was an Error while opening the file.\n");
return (-1);
}
printf("The contents of %s file are :\n", file_name);
while ((ch = fgetc(fp)) != EOF)
printf("%c",ch);
fclose(fp);
return 0;
}
This code seems to work but I keep getting a warning stating "warning: this program uses gets(), which is unsafe."
So I tried to use fgets() but I get an error which states "too few arguments to function call expected 3".
Is there a way around this?
First : Never use gets() .. it can cause buffer overflows
second: show us how you used fgets() .. the correct way should look something like this:
fgets(file_name,sizeof(file_name),fp); // if fp has been opened
fgets(file_name,sizeof(file_name),stdin); // if you want to input the file name on the terminal
// argument 1 -> name of the array which will store the value
// argument 2 -> size of the input you want to take ( size of the input array to protect against buffer overflow )
// argument 3 -> input source
FYI:
fgets converts the whole input into a string by putting a \0 character at the end ..
If there was enough space then fgets will also get the \n from your input (stdin) .. to get rid of the \n and still make the whole input as a string , do this:
fgets(file_name,sizeof(file_name),stdin);
file_name[strlen(file_name)] = '\0';
Yes: fgets expects 3 arguments: the buffer (same as with gets), the size of the buffer and the stream to read from. In your case your buffer-size can be obtained with sizeof file_name and the stream you want to read from is stdin. All in all, this is how you'll call it:
fgets(file_name, sizeof file_name, stdin);
The reason gets is unsafe is because it doesn't (cannot) know the size of the buffer that it will read into. Therefore it is prone to buffer-overflows because it will just keep on writing to the buffer even though it's full.
fgets doesn't have this problem because it makes you provide the size of the buffer.
ADDIT: your call to printf inside the if( fp == NULL ) is invalid. printf expects as its first argument the format, not the output stream. I think you want to call fprintf instead.
Finally, in order to correctly detect EOF in your while-condition you must declare ch as an int. EOF may not necessarily fit into a char, but it will fit in an int (and getc also returns an int). You can still print it with %c.
Rather than ask how to use fgets() you should either use google, or look at the Unix/Linux man page or the VisualStudio documentation for the function. There are hundreds of functions in C, C++ and lots of class objects. You need to first figure out how to answer the basics yourself, so that your real questions stand a chance of being answered.
If you are new to C, you are definitely doing the right thing of experimenting, but take a look at other code, as you go along, to learn some of the tips/tricks of how code is written.
Today I've looked over some C code that was parsing data from a text file
and I've stumbled upon these lines
fgets(line,MAX,fp);
if(line[strlen(line)-1]=='\n'){
line[strlen(line)-1]='\0');
}else{
printf("Error on line length\n");
exit(1);
}
sscanf((line,"%s",records->bday));
with record being a structure
typedef struct {
char bday[11];
}record;
So my question here regards the fgets-sscanf combination to create a type/length safe stream reader:
Is there any other way to work this out beside having to combine these two readers?
What about the \n checking-removing sequence?
The combination of fgets() with sscanf() is usually good. However, you should probably be using:
if (fgets(line, sizeof(line), fp) != 0)
{
...
}
This checks for I/O errors and EOF. It also assumes that the definition of the array is visible (otherwise sizeof gives you the size of a pointer, not of the array). If the array is not in scope, you should probably pass the size of the array to the function containing this code. All that said, there are worse sins than using MAX in place of sizeof(line).
You have not checked for a zero-length birthday string; you will probably end up doing quite a lot of validation on the string that is entered, though (dates are fickle and hard to process).
Given that MAX is 60, but sizeof(records->bday) == 11, you need to protect yourself from buffer overflows in the sscanf(). One way to do that is:
if (sscanf(line, "%10s", records->bday) != 1)
...handle error...
Note that the 10 is sizeof(records->bday) - 1, but you can't provide the length as an argument to sscanf(); it has to appear in the format string literally. Here, you can probably live with the odd sizing, but if you were dealing with more generic code, you'd probably think about:
sprintf(format, "%%%zus", sizeof(records->bday) - 1);
The first %% maps to %; the %zu formats the size (z is C99 for size_t); the s is for the string conversion when the format is used.
Or you could consider using strcpy() or memcpy() or memmove() to copy the right subsection of the input string to the structure - but note that %10s skips leading blanks which strcpy() et al will not. You have to know how long the string is before you do the copying, of course, and make sure the string is null terminated.