I have a C-program built on macOS High Sierra using gcc and a makefile. The first thing the program does is read a binary inputfile. The filename can be either specified from the terminal command line together with the shell command or the program will ask for it when it’s not specified.
My issue is that when the input file is not specified together with the shell command, the program returns an error, saying it cannot open the file.
Here’s what works and what doesn’t:
(program and input file are in the same directory)
open terminal
from the command line type:
./program_name –i input.dat
=> works fine
open terminal
from the commandline type:
./program_name
program prompts:
Inputfile:
I type: input.dat
=> error opening file
open Finder and go to directory with program and input file
doubleclick on program_name icon
program starts in terminal and prompts:
Inputfile:
I type: input.dat
=> error opening file
I run the very same source code on linux and windows where it works ok, so I think it must be an OS thing that I don't understand?
I can add that the program was untrusted because it doesn't come from the app store. CTRL-click on the icon solved that.
--EDIT - sorry for not adding the verifyable code.
To clarify: the argc/argv part works fine. it's the last section of the routine where it prompts for the file name where it goes wrong. Maybe it's indeed the path as Jabberwocky suggested. I'll check on that tonight and will follow-up here.
void GetFileName(nr_args, args, filename, json)
int nr_args;
char **args;
char *filename;
int* json;
{
int i = 1;
filename[0] = '\0';
/* the command 'interpreter' itself is stored in argv[0] */
while (i<nr_args) {
if (strcmp(args[i], "-e") == 0) {
/* we cannot set the json flag here, because */
/* flags have not been initialized yet */
*json = 1;
i++;
}
else {
if (strcmp(args[i], "-i") == 0) {
if (nr_args > i+1) {
/* inputfile was specified */
strncpy(filename, args[++i], MAX_ID_LENGTH);
i++;
}
}
else {
PrintError(41, NULL, args[i]);
i++;
}
}
}
if (filename[0] == '\0') {
printf("\n\nInputfile: ");
scanf("%19s", filename);
filename[MAX_ID_LENGTH] = '\0';
/* clear the input buffer, to prevent parsing an */
/* empty string as the first user command */
/* always do a getchar() independent of OS */
getchar();
printf("\n");
}
}
And this is the part where the file is opened (from main() )
/* Get filename */
GetFileName(argc, argv, inputfile, &json);
/* Open the datafile */
if ((datafile = fopen(inputfile, "rb")) == NULL) {
PrintError(40, NULL, inputfile);
ExitProgram();
return(OK);
}
EDIT2-- As per Andrew Henle's reply, this is the prototype.
void GetFileName(int, char**, char*, int*);
The function is called from the same file as it is defined in.
I run the very same source code on linux and windows where it works ok
That is meaningless.
This is an old K&R-style function definition:
void GetFileName(nr_args, args, filename, json)
int nr_args;
char **args;
char *filename;
int* json;
{
...
You can not call that function safely if the calling code uses a function prototype. A K&R-defined function expects all of its arguments to have underdone default argument promotion. A prototype for the function means the caller won't perform those promotions. The mismatch will result in undefined behavior.
Don't use such ancient functions. You can't use them with a prototype, and without a prototype you have no type safety in the function call.
Use a proper, C standard-compliant function definition:
void GetFileName( int nr_args, char **args, char *filename, int* json )
{
...
And then provide a proper prototype for all calls to the function.
I made the following routine that works for me.
So, as an example:
if the program is in location /usr/mac/my-name/programs/
and the input filename that was entered is input.dat
Then this routine will return: /usr/mac/my-name/programs/input.dat
Thanks for all your help.
#include <sys/param.h> /* realpath() */
#include <limits.h> /* PATH_MAX */
#include <mach-o/dyld.h> /* _NSGetExectablePath() */
char *GetFullPath(const char*);
#define MAX_FILENAME_LEN 100 /* or another value */
char *GetFullPath(const char *filename)
{
char path_buf[PATH_MAX + 1];
char resolved_name[PATH_MAX + 1];
char *real_path;
char *return_path;
uint32_t buf_size = sizeof(path_buf);
int index = 1;
/* this functions returns the full path of the current */
/* running application appended with var 'filename' at */
/* the end. In case of an error it returns NULL. */
if ((return_path = (char *) malloc(MAX_FILENAME_LEN)) == NULL) {
printf("GetFullPath(): error in Malloc()\n");
return(NULL);
}
/* get relative path */
if (_NSGetExecutablePath(path_buf, &buf_size) != 0) {
/* buffer too small */
printf("File Path too long.");
free(return_path);
return(NULL);
}
/* convert to absolute path */
if ( (real_path = realpath(path_buf, resolved_name)) == NULL) {
printf("Could not determine path.\n");
free(return_path);
return(NULL);
}
/* strip the application name from the end of the path */
index = strlen(real_path) - 1;
while (real_path[index] != '/') {
index--;
}
/* now check if there's enough room in return_path */
if (strlen(real_path) + strlen(filename) >= MAX_FILENAME_LEN) {
printf("File path too long.\n");
free(return_path);
return(NULL);
}
/* there's enough room, copy path and filename to return_path */
strncpy(return_path, real_path, index+1);
/* do not try to free() real_path */
return_path[index+1] = '\0';
strncat(return_path, filename, strlen(filename));
return(return_path); /* caller must free() return_path */
}
Related
This is my first time asking on Stack Overflow, i'll try my best to make a good question.
Fell free to correct me if i miss relevant information or stuff like that.
I'm writting a little program that creates a simple options menu.
My plan consist in very few steps:
Read file names from that macro #define file_dir "/home/me/dir"
Store that file names into names.txt.
I have to display the content of names.txt as options in my simple menu.
At the moment i was able to accomplish two of three steps but not so well i guess.
I create 2 function to do these 2 jobs. create_file(), read_file(), respectively.
Now is where my question really begins:
Each function works ok when i execute isolated. If i call as it intended to be
The second function read_file() instead to print the content of the file to stdout
it rewrite the names.txt and put a "square" character at the end of the file.
My plan is to redirect the return of the read_file() to an array.
So i can display as options in this bare bone menu.
Please help me understand.
Why i can't use this two functions like that ?
I know i am new to C and this program is far from be complete.
Here's my code:
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#define my_dir "/home/me/dir"
int roms_list;
int create_list()
{
/* redirect stdout to a file */
freopen("names.txt", "a+", stdout);
/* open dir and print their content */
DIR *dir;
struct dirent *ent;
if ((dir = opendir (nes_dir)) != NULL)
{
while ((ent = readdir (dir)) != NULL)
{
printf ("%s\n", ent->d_name);
}
}
closedir(dir);
close(names.txt);
}
int read_list()
{
FILE * list;
char ch;
list = fopen("names.txt", "r+");
if(NULL == list)
{
printf("file cant' be opened \n");
return 1;
}
do
{
ch = fgetc(list);
printf("%c", ch);
}
while (ch != EOF);
fclose(list);
}
int main()
{
create_list();
read_list();
return 0;
}
As MikeCAT points out, you attempt to printf("%c", ch); before checking ch != EOF resulting in attempting to print the int EOF values with the %c conversion specifier resulting in Undefined Behavior due to the mismatch in argument type and conversion specifier. ch must be type int to match the return type of fgetc() and to make a valid comparison with EOF.
If a conversion specification is invalid, the behavior is undefined.
If any argument is not the correct type for the corresponding
conversion specification, the behavior is undefined.
C11 Standard - 7.21.6.1(p9)
Additional Areas Where Your Code Needs Improvement
Your create_list() function is type int, but fails to return any value. Since create_list() can succeed or fail, it is imperative that the return type be able to communicate whether it succeeded or failed. Type int is fine, you can for example return 0; on a failure to read or on success, return the number of entries written to the file;
Your read_list() function is simply an output function that outputs the contents of the file written. While it can succeed or fail, it isn't critical to the continued operation of your program. Choosing type void for an output function is fine.
Do not hardcode file or directory names in functions. You shouldn't have to recompile your program just to read from a different directory or write to a different filename. Pass the directory to read and the filename to write as arguments to your program. That is what the arguments to main() are for, e.g. int main (int argc, char **argv). (or prompt the user to input both string values)
open your file in main() once and on successful open, pass a FILE* pointer for the open file stream to each of your functions as a parameter. You validate the open in main() because there is no need to call either function if fopen() fails.
pass the directory name to read to create_list() as a const char * parameter.
condition your call to read_list() on a successful return from create_list(). If create_list() fails, there is no need to call read_list().
Putting the improvements together, you could do something similar to the following:
#include <stdio.h>
#include <dirent.h>
/* returns 0 on failure, no. of files written on success */
int create_list (FILE *fp, const char *dname)
{
/* open dir and print their content */
DIR *dir;
struct dirent *ent;
int n = 0; /* simple counter for no. of entries read */
if ((dir = opendir (dname)) == NULL) { /* return 0 on failure to open */
return 0;
}
while ((ent = readdir (dir)) != NULL) {
/* skip dot files */
if ((ent->d_name[0] == '.' && !ent->d_name[1]) ||
(ent->d_name[0] == '.' && ent->d_name[1] == '.')) {
continue;
}
fprintf (fp, "%s\n", ent->d_name);
n++; /* increment counter */
}
closedir(dir);
return n; /* return the number of enteries written */
}
/* read list can be type void - it simply outputs contents of file */
void read_list (FILE *fp)
{
int ch; /* must be int */
while ((ch = fgetc (fp)) != EOF) { /* read char, validate not EOF */
putchar (ch); /* write to stdout */
}
}
int main (int argc, char **argv) {
char *dname, *fname; /* dirname and filename pointers */
int nfiles = 0; /* no. of files written */
FILE *fp = NULL; /* file pointer */
if (argc != 3) { /* validate 2 arguments given (dirname filename) */
fputs ("error: dirname and filename required\n"
"usage: ./program \"/path/to/files\" \"filename\"\n", stderr);
return 1;
}
dname = argv[1]; /* assign arguments to give descriptive names */
fname = argv[2]; /* (you could just use argv[x], a name helps) */
fp = fopen (fname, "w+"); /* open file for reading/writing */
if (!fp) { /* validate file open for reading/writing */
perror ("file open failed");
return 1;
}
/* validate create_list succeeds */
if ((nfiles = create_list (fp, dname))) {
printf ("%d files:\n\n", nfiles); /* number of entries in file */
rewind (fp); /* rewind file pointer */
read_list (fp); /* read list */
}
if (fclose (fp) != 0) { /* always validate close-after-write */
perror ("fclose fp");
}
}
Example Use/Output
You provide the directory to read as the first argument and the filename to write as the second. ./progname /path/to/read /file/to/write
A short example:
$ ./bin/dirlist_names ./km dat/dnames.txt
47 files:
startstop.o
kernelmod_hello1.c
.chardev.o.cmd
hello-4.o
.hello-2.mod.cmd
hello-2.mod
<snip>
hello-5.mod
.startstop.o.cmd
.hello-4.mod.cmd
chardev.mod
Makefile
hello-2.c
It looks like you are printing EOF. You should check if ch is EOF before printing that.
Also fgetc() returns int and convering the return value to char will prevent it from distinguishing EOF from one of valid byte, so you should use int instead of char for ch.
Instead of this:
char ch;
/* ... */
do
{
ch = fgetc(list);
printf("%c", ch);
}
while (ch != EOF);
You should use:
int ch;
/* ... */
while ((ch = fgetc(list)) != EOF)
{
printf("%c", ch);
}
Or:
int ch;
/* ... */
ch = fgetc(list);
while (ch != EOF)
{
printf("%c", ch);
ch = fgetc(list);
}
In c code. I have an input file (called in) that is a mad-lib in the format, "I have really < adjective> eyes" (no spaces inside the <>) and I want to write a bool function that uses scanf to read every word and return true if the word begins with '<' (also called a token) How would I go about doing that? and yes I have to use scanf. Here is what I have right now but I do not think that it is completely right, so another question is, how do I know if my function is properly working.
/* istoken = returns true if word is a token */
bool istoken(char word[]) {
char first;
int firstindex;
while (1) {
scanf("%s", word);
first = word[MAX_LEN];
firstindex = (int)strlen(word);
if (first == '<') {
printf("The token is: %s\n", first);
return true; }
else {
return false; }
}
}
In the caller, word must be sufficienly sized to hold the largest word in your text (+3 chars, 2 for <,> and the nul-termanting character. You should pass the maximum length for word as a parameter to istoken, but since you are using scanf, you must hard-code the field width modifier to protect your array bounds. (that is one of the reasons fgets is recommended over scanf -- but you must use scanf). Don't skimp on buffer size for word in the caller. Something like the following should suffice in the caller (probably main() for you):
#define MAXC 1024
...
char word[MAXC] = "";
There is no need for first or firstindex. To check the first character in a string, all you need do is dereference the pointer. With that, it is simply a matter of:
/* istoken = returns true if word is a token */
bool istoken (char *word) {
while (scanf("%1023s", word) == 1) /* did a valid read take place? */
if (*word == '<') /* is 1st char '<' ? */
return true; /* return true */
return false; /* out of words, return false */
}
(note: simply returning the token in word via the pointer parameter while returning bool, seems a bit of an awkward factoring of your code -- but it is doable. Also, if the token exceeds 1024 chars, including the nul-terminating char -- you will not have a complete token in word on function return)
Look things over and let me know if you have further questions.
A Short Example Reading stdin
#include <stdio.h>
#include <stdbool.h>
#define MAXC 1024
/* istoken = returns true if word is a token */
bool istoken (char *word) {
while (scanf("%1023s", word) == 1) /* did a valid read take place? */
if (*word == '<') /* is 1st char '<' ? */
return true; /* return true */
return false; /* out of words, return false */
}
int main (void) {
char word[MAXC] = "";
if (istoken (word))
printf ("found token: '%s'\n", word);
else
fprintf (stderr, "error: no token found.\n");
return 0;
}
Example Use/Output
$ echo "my dog has <too> many fleas." | ./bin/scanftoken
found token: '<too>'
Last note: while you, as you propose in the comment below, can output the token from within intoken, e.g.
bool istoken(char word[]) {
while (scanf("%100s", word) == 1) {
if (word[0] == '<') {
printf("the token is: %s\n", word);
return true;
}
}
return false;
}
That is generally something you want to avoid. Within your program design you want (as a goal) to separate your implementation (what your program does, computes, etc..) from Input/Output. That makes your code usable when called by more than one function that wants to output printf("the token is: %s\n", word);
While a bit uncommon, your istoken function that locates a token and returns true/false makes more sense if the caller then uses that return to determine what to do with the token in word. If you are just going to print it from inside intoken if a token is found, and then do nothing with the return in the caller, then why declare it as bool anyway -- you may as well just declare it as void if you are not using the return.
Like I said this is (a goal). You can factor your code any way you like as long as it is valid code. The use of printf within istoken is perfectly valid for temporary debugging purposes as well. (in fact that is one of the most helpful debugging tools you have, just sprinkle temporary printf statements throughout the logic path in your program to find out where you code works as intended and where the "train-falls-off-the-track" so to speak.
Example with File I/O
OK, we are finally getting to 'Z' with this 'XY' problem. Since, as I now understand, you have your text in a file (I have used "myfile.txt" for the input) and you want to read your inputfile in istoken and return word and true/false to main() and if true then write the token to your output file (I used "tokenfile.txt" before for my output file), then what you need to do is open both your input file and output file using fopen in main() similar to the following:
FILE *ifp = fopen ("myfile.txt", "r"), /* infile pointer */
*ofp = fopen ("tokenfile.txt", "w"); /* outfile pointer */
(I'm not that creative, I just use ifp for the input file pointer and ofp for the output file pointer)
Whenever you open a file, before you attempt to read or write to the file, you must validate that the file is actually open for reading or writing (e.g. fopen succeeded). For example:
if (ifp == NULL) { /* validate input open for reading */
perror ("fopen-myfile.txt");
return 1;
}
if (ofp == NULL) { /* validate output open for writing */
perror ("fopen-tokenfile.txt");
return 1;
}
Now with both files open, you can call istoken and read from ifp. However, this takes modifying istoken to take a FILE * parameter for use with fscanf instead of using scanf. For example:
/* istoken = returns true if word is a token */
bool istoken (FILE *ifp, char *word) {
while (fscanf(ifp, "%1023s", word) == 1) /* valid read take place? */
if (*word == '<') /* is 1st char '<' ? */
return true; /* return true */
return false; /* out of words */
}
After the return of istoken, you can write to stdout to let the user know if a token was found and also write to ofp to store token in your output file, e.g..
if (istoken (ifp, word)) { /* call istoken passing open ifp */
printf ("found token: '%s'\n", word); /* output token */
fprintf (ofp, "%s\n", word); /* write token to outfile */
}
else
fprintf (stderr, "error: no token found.\n");
Lastly, you must fclose the files you have open. But there is a twist for files you write to. You should validate the fclose to insure a stream-error did not occur on ofp that may not have been otherwise caught. e.g.
fclose (ifp); /* close infile pointer */
if (fclose(ofp) == EOF) /* validate "close-after-write" */
perror ("stream error on outfile stream close");
Putting it altogether, you can do something like the following:
#include <stdio.h>
#include <stdbool.h>
#define MAXC 1024
/* istoken = returns true if word is a token */
bool istoken (FILE *ifp, char *word) {
while (fscanf(ifp, "%1023s", word) == 1) /* valid read take place? */
if (*word == '<') /* is 1st char '<' ? */
return true; /* return true */
return false; /* out of words */
}
int main (void) {
char word[MAXC] = "";
FILE *ifp = fopen ("myfile.txt", "r"), /* infile pointer */
*ofp = fopen ("tokenfile.txt", "w"); /* outfile pointer */
if (ifp == NULL) { /* validate input open for reading */
perror ("fopen-myfile.txt");
return 1;
}
if (ofp == NULL) { /* validate output open for writing */
perror ("fopen-tokenfile.txt");
return 1;
}
if (istoken (ifp, word)) { /* call istoken passing open ifp */
printf ("found token: '%s'\n", word); /* output token */
fprintf (ofp, "%s\n", word); /* write token to outfile */
}
else
fprintf (stderr, "error: no token found.\n");
fclose (ifp); /* close infile pointer */
if (fclose(ofp) == EOF) /* validate "close-after-write" */
perror ("stream error on outfile stream close");
return 0;
}
Example Input File
$ cat myfile.txt
my dog has <too> many fleas.
Example Use/Output
$ ./bin/scanftoken
found token: '<too>'
$ cat tokenfile.txt
<too>
The best advice I can give you on learning C is to simply slow down. There is a lot to learn, and in fact given 30 years, I have barely scratched the surface (that and they keep revising the standard every so often). Just take it a step at a time. Loop up the man page for each function you use, find out what the proper parameter are and most critically what it returns and what form of error reporting is has (e.g. does it set errno so you can use perror to report the error or do you need to use fprintf (stderr, ....)?
Always enable compiler warnings and read and understand the warning and do not accept code until it compiles without warning. You can learn a lot of C just by listening to what your compiler is telling you. And if all else fails... talk to the duck. How to debug small programs, really, it helps :)
If you mind using some powerful lexical analyzer, I suggest you using flex which can help you lot for tokenization.
As you can see, Flex let you write token pattern and generate a C parser which does all the work.
Here is a program which compresses multiple blanks and tabs down to a single blank, and throws away whitespace found at the end of a line:
%%
[ \t]+ putchar( ' ' );
[ \t]+$ /* ignore this token */
You can find more at http://alumni.cs.ucr.edu/~lgao/teaching/flex.html.
I've written a program to reverse a .txt file, line-by-line, and return the output to a new file. It is part of the requirements for the command line arguments to NOT include the .txt extension, so I add in at the top of the program before fopen is called on the file. This works perfectly fine in macOSx terminal, as you can see here: https://imgur.com/a/HqUFd
However, when I upload this to my school's server, I get the following output: https://imgur.com/a/rCdaI
Relevant code:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define NUMARG 3
#define INFILEARG 1
#define OUTFILEARG 2
int countWords(const char *sentence);
int main(int argc, char *argv[]){
/* File pointers */
FILE *finp;
FILE *foutp;
/* Three levels of strings: word < line < text.
Both token variables to use with strtok splitting the text. */
char **holdWords = NULL, *holdLine = NULL, *holdText = NULL,
*lineToken = NULL, *wordToken = NULL;
int stringSize = 0, totalStrings, i;
size_t size = 0;
/* Add .txt extension */
int inFileNameSize = sizeof(argv[INFILEARG]);
int outFileNameSize = sizeof(argv[OUTFILEARG]);
char inFileName[inFileNameSize+4]; //add 4 to have room for ".txt"
char outFileName[outFileNameSize+4];
strcat(inFileName, argv[INFILEARG]);
strcat(inFileName, ".txt");
strcat(outFileName, argv[OUTFILEARG]);
strcat(outFileName, ".txt");
/* Check for errors in argument number and opening files. */
if(argc != NUMARG){
printf("You have to put the input and output files after the program name.\n"); fflush(stdout);
return(1);
}
if( (finp = fopen(inFileName, "r")) == NULL ){
printf("Couldn't open %s for reading.\n", inFileName); fflush(stdout);
return(1);
}
if( (foutp = fopen(outFileName, "w")) == NULL){
printf("Couldn't open %s for writing.\n", outFileName); fflush(stdout);
return(1);
}
Can anyone help me figure out what's going on here? Thank you.
EDIT TO EXPLAIN WHY DIFFERENT THAN LINKED QUESTION: While it's helpful to know why a pointer to an array can't be sizeof'd, my question is about getting the size of a string (one pointer, not a pointer to a pointer). I get an error when using strlen on my mac, yet it works on unix. I get an error when using sizeof of the unix, yet sizeof works on my mac.
It appears your have a buffer overflow, you can see this by the fact the file names printed appear corrupted.
If you look at the lines:
/* Add .txt extension */
int inFileNameSize = sizeof(argv[INFILEARG]);
int outFileNameSize = sizeof(argv[OUTFILEARG]);
Your error lies in the use of the sizeof operator. This returns the sizeof the type not the length of the string. Hence your in/outFileNameSize variable is too short for the actual string you are copying into it.
My program iterates through a single directory (non-recursively) and stores the names of all the files in that directory inside an array. Then, it uses that array in the second part of my program and returns some information about each file. I can iterate through the directory, and I can process a single file, but I'm having trouble combining the two parts of the program. Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int getArraySize(char* arr[]);
int getArraySize(char* arr[]) {
return sizeof(&arr);
}
char *filesArray[200];
int main (int argc, char* argv[])
{
DIR *dir;
struct dirent *ent;
int filesCtr = 0;
if ((dir = opendir ("/home/dshah/Documents/CECS 420/Project 3")) != NULL) {
while ((ent = readdir (dir)) != NULL) { /* print all the files and directories within directory */
if (strcmp(ent->d_name, ".") == 0) {
continue;
} else if (strcmp(ent->d_name, "..") == 0) {
continue;
} else if (ent->d_type == 4) { // if a directory
continue;
} else {
filesArray[filesCtr] = ent->d_name;
printf("%s\n", filesArray[filesCtr]);
filesCtr++;
}
}
closedir (dir);
} else { /* could not open directory */
perror ("Could not open directory");
}
int i;
for (i = 0; i < getArraySize(filesArray); i++) {
char* filename = filesArray[i];
FILE *file = fopen (filename, "r");
if (file != NULL) {
char line [128]; /* or other suitable maximum line size */
int ctr = 1;
while (fgets(line, sizeof line, file) != NULL) { /* read a line */
if (strstr(line, "is") != NULL) {
printf("%s:%d:%s", filename, ctr, line);
}
ctr++;
}
fclose (file);
} else {
perror (filename); /* why didn't the file open? */
}
}
return 0;
}
The line I am having trouble with is:
char* filename = filesArray[i];
Is this line of code correct? It works when I set filename to a string like "file.txt", so shouldn't this also work when I do printf("n %s\n", filesArray[i]);? Is filesArray[i] in this line of code a string?
EDIT:
Thanks, that fixed the problem. One more quick question: I'm trying to append the full path on
FILE *file = fopen (filename, "r");`
line by changing it to
FILE *file = fopen (strcat("/home/dshah/Documents/CECS 420/Project 3/", filename), "r");
but it gives me a segmentation fault. Shouldn't this work cause I'm just specifying the path?
When you pass an array to a function, it decays to a pointer, so when you do e.g. &arr you actually get a pointer to that pointer, and the size of a pointer is most likely not the size of the original array. If (and I mean really if) the array is actually a string, you can use strlen to get the length of the string (not including the string terminator character).
In your case, you don't actually need the getArraySize function, as you already have a counter telling you how many strings there is in the filesArray array: The filesCtr variable.
Also, when using a function such as readdir the d_name field of the returned entry may actually be pointing to a static array so you can't really just copy the pointer, you have to copy the complete string. This is done with the strdup function:
filesArray[filesCtr] = strdup(ent->d_name);
Remember that when done you have to free this string.
Oh, and avoid using "magic numbers" in your code, for example when checking if the directory entry is a sub-directory (ent->d_type == 4). Use the macros available to use (end->d_type == DT_DIR).
And a final thing, the d_name field of the readdir entry only contains the actual filename, not the full path. So if you want the full path you have to append the path and the filename.
I'm trying to write a program to swap a character that I would specify on the command line (a command line argument) with a character in the input text file. The first command line argument is the character I want to change, the second argument is character that I want to replace the old character with, and the third argument is the input file.
When I do this, my program should generate an output file named: "translation.txt". I know that the problem with my program is in the "if" statements/the fprintf statements, but I'm not sure how to fix this. I was thinking of reading each character in the input file separately, and from there, I wanted to use "if" statements to determine whether or not to replace the character.
void replace_character(int arg_list, char *arguments[])
{
FILE *input, *output;
input = fopen(arguments[3], "r");
output = fopen("translation.txt", "w");
if (input == NULL)
{
perror("Error: file cannot be opened\n");
}
for (int i = 0; i != EOF; i++)
{
if (input[i] == arguments[1])
{
fprintf(output, "%c\n", arguments[2]);
}
else
{
fprintf(output, "%c\n", arguments[1]);
}
}
}
int main(int argc, char *argv[])
{
if (argc < 5)
{
perror("Error!\n");
}
replace_character(argc, argv);
}
Okay I think this can help:
#include <stdio.h>
int main(int argc, char** argv)
{
if (argc < 4) return -1; /* quit if argument list not there */
FILE* handle = fopen(argv[3], "r+"); /* open the file for reading and updating */
if (handle == NULL) return -1; /* if file not found quit */
char current_char = 0;
char to_replace = argv[1][0]; /* get the character to be replaced */
char replacement = argv[2][0]; /* get the replacing character */
while ((current_char = fgetc(handle)) != EOF) /* while it's not the end-of-file */
{ /* read a character at a time */
if (current_char == to_replace) /* if we've found our character */
{
fseek(handle, ftell(handle) - 1, SEEK_SET); /* set the position of the stream
one character back, this is done by
getting the current position using
ftell, subtracting one from it and
using fseek to set a new position */
fprintf(handle, "%c", replacement); /* write the new character at the new position */
}
}
fclose(handle); /* it's important to close the file_handle
when you're done with it to avoid memory leaks */
return 0;
}
Given an input specified as the first argument, it will seek a character to replace and then replace it with what is stored in replacement. Give it a try and let me know if it doesn't work. I run it like this:
./a.out l a input_trans.txt
My file has just the string 'Hello, World!'. After running this it's changed to 'Heaao, Worad!'.
Read up on ftell and fseek, as they're key here for what you need to do.
EDIT: Forgot to add an fclose statement that closes the file handle at the end of the program. Fixed!