I need to write a program that is copying the content of a file to another file and reverses it.
I found an example and read it through to understand what is going on.
The problem is that my program has to use two functions:
void reverse(char line[]){
int i;
int length;
char tmp;
..
..
..
return;
}
(no further paramters or local variables)
The second function does the rest of the work(opens files, copies files, closes files)
The main program only reads the name of the files and calls the copy function.
#include<stdio.h>
#include<string.h>
void reverse(char line[])
{
int i;
int length;
char temp;
if (line == NULL)
return;
length = strlen(line);
for (i = 0 ; i < length / 2 + length % 2 ; ++i)
{
if (line[i] == line[length - i - 1])
continue;
temp = line[i];
line[i] = line[length - i - 1];
line[length - i - 1] = temp;
}
return;
}
int main()
{
FILE *src_fh, *dst_fh;
char src_fn[256+1], dst_fn[256+1];
printf("Enter Source File Name:\n");
fgets(src_fn, sizeof(src_fn), stdin); reverse(src_fn);
if( (src_fh = fopen(src_fn, "r")) == NULL )
{
printf("ERROR: Source File %s Failed To Open...\n",src_fn);
return(-1);
}
printf("Enter Destination File Name:\n");
fgets(dst_fn, sizeof(dst_fn), stdin); reverse(dst_fn);
if( (dst_fh = fopen(dst_fn, "w+")) == NULL )
{
fclose(src_fh);
printf("ERROR: Destination File %s Failed To Open...\n",dst_fn);
return(-2);
}
int ch;
while( (ch = fgetc(src_fh)) != EOF )
{
fputc(ch, dst_fh);
}
fclose(src_fh);
fclose(dst_fh);
return 0;
}
You only need to swap the first character with the last, the second with the pre-last, and so on.
You actually don't need the int temp variable, but since it seems to be required, here it is
void reverse(char line[])
{
int i;
int length;
char temp;
if (line == NULL)
return;
length = strlen(line);
for (i = 0 ; i < length / 2 + length % 2 ; ++i)
{
if (line[i] == line[length - i - 1])
continue;
temp = line[i];
line[i] = line[length - i - 1];
line[length - i - 1] = temp;
}
return;
}
This is an improved version, without int temp, instead we store the result of length / 2 + length % 2 so it's not recalculated on each iteration
void reverse(char line[])
{
int i;
int length;
int half;
if (line == NULL)
return;
length = strlen(line);
half = length / 2 + length % 2;
for (i = 0 ; i < half ; ++i)
{
if (line[i] == line[length - i - 1])
continue;
line[length] = line[i];
line[i] = line[length - i - 1];
line[length - i - 1] = line[length];
}
line[length] = '\0';
return;
}
just use the location of the terminating '\0' byte as the temp when swapping.
For the second function, read each line using fgets and write it to the file with fprintf, just remember to remove the newline character from the read strings, you can use the chomp function y posted for that, if you don't remove the newline, the reversed lines will have the newline at the beginning of the line.
The prameter name line in the prototype void reverse(char line[]) seems to give a hint, how the given exercise might be intended to be solved.
split the file in lines
reverse every line
reverse the order of the lines
Nevertheless you should watch out following this strategy, as there is still a really nasty gotcha involved, if your file may contain any data.
In this case you'll get in big trouble finding the end of line[] as '\0' termination might get confused with a literal '\0' in the line.
As a workaround you might try to replace any literal occurance of '/0' by the sequence '\0' 'x' and mark the end of your line by the sequence '\0' '-' or whatever before passing it to reverse() and reaversing the substitution after writing the reversed line to the file.
Unfortunately this attempt doesn't look too elegant, but maybe reversing a file the way it is meant to be done in the exercise isn't really elegant anyays.
the following code
1) incorporates proper error checking
2) outputs each input line, reversed, to the output file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* chomp(char* p)
{
int len;
if(!p) return(p);
if( (len=strlen(p))<=0 ) return(p);
if( p[len-1] == '\n' ) { p[--len] = '\0'; }
if( p[len-1] == '\r' ) { p[--len] = '\0'; }
return(p);
} // end function: chomp
int main()
{
/* Create Usable Variables */
FILE *src_fh = NULL;
FILE *dst_fh = NULL;
char src_fn[256+1] = {'\0'};
char dst_fn[256+1] = {'\0'};
char line[2048] = {'\0'};
/* Retrieve Source File Name From User */
printf("Enter Source File Name:\n");
if( NULL == (fgets(src_fn, sizeof(src_fn), stdin) ) )
{ // fgets failed
perror("fgets for input file name failed" );
exit(EXIT_FAILURE);
}
// implied else, fgets successful
chomp(src_fn); // remove trailing newline characters
/* Attempt Opening Source File For Reading */
if( (src_fh = fopen(src_fn, "r")) == NULL )
{
perror( "fopen failed" );
printf("ERROR: Source File %s Failed To Open...\n",src_fn);
return(-1);
}
// implied else, fopen source file successful
/* Retrieve Destination File Name From User */
printf("Enter Destination File Name:\n");
if( NULL == (fgets(dst_fn, sizeof(dst_fn), stdin) ) )
{ // then fgets failed
perror( "fgets for output file name failed" );
fclose(src_fh); // cleanup
exit( EXIT_FAILURE );
}
// implied else, fgets for output file name successful
chomp(dst_fn); // remove trailing newline characters
/* Attempt Opening Destination File For Writing */
if( NULL == (dst_fh = fopen(dst_fn, "w")) )
{
perror( "fopen for output file failed" );
fclose(src_fh); // cleanup
printf("ERROR: Destination File %s Failed To Open...\n",dst_fn);
return(-2);
}
// implied else, fopen for output file successful
int index;
/* Copy Source File Contents (reversed, line by line) to destination file */
while( NULL != (fgets(line, sizeof(line), src_fh) ) )
{
chomp(line); // remove trailing newline characters
index = strlen(line) - 1; // -1 because arrays start with offset 0
// and strlen returns offset to '\0'
// output reversed line to file
while( index >= 0 )
{
fputc( line[index], dst_fh );
index--;
} // end while
fputc( '\n', dst_fh );
} // end while
/* Close Files On Success */
fclose(src_fh);
fclose(dst_fh);
return 0;
} // end function: main
Related
I want to read a list of words from a file, which has one word per line.
The words should have up to 4 characters each. How can I produce an error if one of the lines is longer than that?
I tried reading the words using fgets
char buf[5];
fgets(buf, 5, stdin);
and with scanf
char buf[5];
scanf("%4s", &buf);
but in both cases it splits long lines into smaller lines. For example qwerasdf is read as two words, qwer and asdf. Is there a way to detect that it tried to read a long line with more than 4 characters and give an error instead?
The only alternative I can think of is reading the input character-by-character and taking care of everything by myself. But is there a simpler solution using functions from the standard library?
You could check for the length of the read string and since fgets also reads the newline character, you could explicitly check for '\n' as the last input character.
char buf[6];
while (fgets(buf, sizeof(buf), stdin)) {
if (strlen(buf) > 5
|| (strlen(buf) == 5 && buf[strlen(buf) - 1] != '\n')) {
fprintf(stderr, "line too long\n");
exit(EXIT_FAILURE);
}
}
The buffer must consist of at least six characters: 4 input characters + 1 newline character + the string terminating NUL byte.
You are making an excellent choice reading with fgets(), the only rule-of-thumb you are breaking is don't skimp on buffer size. But, even if you do, you can handle things properly with fgets().
When you read a line from a file, fgets() (or POSIX getline()) read and include the '\n' as part of the buffer they fill (if there is room). If you are expecting up to 4-characters, then a buffer size of 5 is too-short-by-one to accommodate all your characters, the nul-terminating character, and the '\n'. Your circumstance attempting to read a 4-character line ("cats") with a 5-character buffer with fgets() would result in buf holding:
+---+---+---+---+---+
| c | a | t | s | \0| --> '\n' remains unread
+---+---+---+---+---+
You can gracefully handle that as well (but better not to skimp on buffer size) To gracefully handle the issue you need to check:
if '\n' is the last char in the buffer, complete line read, trim '\n' by overwriting with nul-terminating character;
otherwise, read next char;
if next char is '\n', then OK, you read all chars and there wasn't room for the '\n' which you just read and checked -- continue reading the next line;
else if next char is EOF, then you read all characters in the last line in a file with a non-POSIX end-of-file (no '\n' after the final line of data), break read loop you found EOF;
else additional character remain unread in the line, read and discard characters until the next '\n' or EOF is found
Putting that logic together, you could do:
#include <stdio.h>
#include <string.h>
int main (void) {
char buf[5];
while (fgets (buf, 5, stdin)) { /* read each line */
if (strchr (buf, '\n')) /* if '\n' found - line read */
buf[strcspn (buf, "\n")] = 0; /* nul-termiante at '\n' */
else { /* otherwise */
int c = getchar(); /* read next chars */
if (c == '\n') /* if '\n', OK read next line */
continue;
else if (c == EOF) /* if EOF, OK, non-POSIX eof */
break;
fputs ("error: line too long - discarding remainder.\n", stderr);
for (; c != '\n' && c != EOF; c = getchar()) {}
}
}
}
Look things over and let me know if you have further questions.
Here I made this function to read the file
char by char and returns only one line per call
so now you can read your file line by line, the
type Line has an array of chars value where we store
the line and an int hasNextLine 1 or 0 (bool)
that tell you if the file has another line or no,
this is handy when you loop over the file line by line.
#include <stdlib.h>
#include <stdio.h>
typedef struct {
char *value;
int hasNextLine;
} Line;
Line * getLine(FILE *file) {
Line *line = (Line *)malloc(sizeof(Line));
if(line == NULL) {
return NULL;
}
line->value = NULL;
line->hasNextLine = 1;
int n = 0, c;
while(1) {
c = getc(file);
char *tmpStr = (char *)realloc(line->value, n + 2);
if(tmpStr == NULL) {
line->hasNextLine = -1;
return line;
}
line->value = tmpStr;
if(c == EOF) {
line->hasNextLine = 0;
line->value[n] = '\0';
return line;
}
if(c == '\n') {
line->value[n] = '\0';
return line;
}
line->value[n] = c;
n++;
}
return line;
}
Usage:
// example reading one line
int main() {
FILE *f = fopen("your_file.txt", "r");
if(f == NULL) {
printf("File not found!");
return 1;
}
Line *l = getLine(f);
if(l != NULL) {
printf("%s\n", l->hasNextLine != -1 ? l->value :
"Error: while getting the line");
free(l->value);
free(l);
}
fclose(f);
return 0;
}
// example reading the whole file
int main() {
FILE *f = fopen("your_file.txt", "r");
if(f == NULL) {
printf("File not found!");
return 1;
}
Line *l;
int hasNextLine;
while(1) {
l = getLine(f);
if(l != NULL) {
printf("%s\n", l->hasNextLine != -1 ? l->value :
"Error: while getting the line");
free(l->value);
hasNextLine = l->hasNextLine;
free(l);
}
if(hasNextLine <= 0) {
break;
}
}
fclose(f);
return 0;
}
you can make a custom function for user input
char * sgetLine(char *msg) {
printf("%s", msg);
Line *l = getLine(stdin);
char *strLine = NULL;
if(l == NULL) {
return NULL;
}else {
if(l->hasNextLine == -1) {
free(l->value);
free(l);
return NULL;
}
strLine = l->value;
free(l);
return strLine;
}
}
so now you can use one function call to print
the question and to get the answer (char array)
int main() {
char *l = sgetLine("What is your name? ");
if(l != NULL) {
printf("%s\n", l);
}
free(l);
return 0;
}
I want to ignore/skip the comments in a text file when I use fgets.
The problem is that I only can skip a comment if the first character in a line starts is #. Comments starts with # in my text file. But there are some # in my file.txt that are not the first character of a line, like so;
#Paths
A B #Path between A and B.
D C #Path between C and D.
A is my first node, B is my second node and when # comes I want to ignore the rest of text until the next line. My new node should be D and C etc. I can only use "r" in fopen function.
I have tried fgets but it reads line by line and fgetc doesn't help either.
bool ignore_comments(const char *s)
{
int i = 0;
while (s[i] && isspace(s[i])) i++;
return (i >= 0 && s[i] == '#');
}
FILE *file;
char ch[BUFSIZE];
file = fopen("e.txt", "r");
if (file == NULL) {
printf("Error\n");
fprintf(stderr, "ERROR: No file input\n");
exit(EXIT_FAILURE);
}
while(fgets(ch, BUFSIZE, file) != NULL)
{
if (line_is_comment(ch)) {
// Ignore comment lines.
continue;
printf("%c",*ch);
}
fscanf(file, "%40[0-9a-zA-Z]s", ch);
....
}
the following proposed code:
performs the desired functionality
cleanly compiles
properly checks for errors
this answer uses a state machine, based on: 'InComment'
and now, the proposed code:
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int InComment = 0;
FILE *fp = fopen( "file.txt", "r" );
if( !fp )
{
perror( "fopen to read -file.txt- failed" );
exit( EXIT_FAILURE );
}
int ch;
while( (ch = fgetc(fp)) != EOF )
{
if( ch == '#' )
{
InComment = 1;
}
else if( ch == '\n' )
{
InComment = 0;
fputc( ch, stdout );
}
else if( !InComment )
{
fputc( ch, stdout );
}
}
fclose( fp );
}
Also method names are different, but am I right with this version ?
Ignore my dirty method line_is_comment - from first version unless you want to play with ;-)
Extended test input:
#Paths
A B #Path between A and B.
D C #Path between C and D.
E F
G H
Output:
rest of line read
AB rest of line read
DC rest of line read
EF rest of line read
GH rest of line read
#include <stdio.h>
bool line_is_comment(const char *s)
{
char *commentPos = const_cast<char*>(strchr(s, '#'));
if(commentPos != NULL) {
*commentPos = 0; // cut-off chars after comment
//return true; // or false then to accept the line
return commentPos == s;
}
return false;
}
#define BUFSIZE 50
int main()
{
FILE *file;
char ch[BUFSIZE];
file = fopen("e.txt", "r");
if (file == NULL) {
printf("Error\n");
fprintf(stderr, "ERROR: No file input\n");
exit(EXIT_FAILURE);
}
int x;
while(!feof(file)) {
x = fscanf(file, "%40[0-9a-zA-Z]s", ch);
if(x == 0) {
ch[0] = fgetc(file);
if(ch[0] == '#' || ch[0] == '\n') {
if(ch[0] != '\n') fgets(ch, BUFSIZE, file);
printf(" rest of line read\n");
}
} else if(x<0) break;
else {
printf("%c",*ch); // continue with ... undisclosed part here
}
}
return 0;
}
You can also make use of strcspn to trim all comments (and if not present, trim the line-endings from your buffer) in a single simple call. Where you would normally trim the line-ending from the buffer read by fgets() with:
ch[strcspn (ch, "\r\n")] = 0; /* trim line-ending */
You can simply add the "#" character to your reject list and nul-terminate there if a comment is present. That would reduce the complete task of removing comments beginning with '#' and outputting the newly formatted line to:
while (fgets (ch, BUFSIZE, fp)) { /* read every line */
ch[strcspn (ch, "#\r\n")] = 0; /* trim comment or line-ending */
puts (ch); /* output line w/o comment */
}
A short example taking the file to read as the first argument to the program (or reading from stdin by default if no argument is given), you could do:
#include <stdio.h>
#include <string.h>
#define BUFSIZE 1024 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
char ch[BUFSIZE];
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (ch, BUFSIZE, fp)) { /* read every line */
ch[strcspn (ch, "#\r\n")] = 0; /* trim comment or line-ending */
puts (ch); /* output line w/o comment */
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
return 0;
}
Example Input File
Borrowing Tom's example file :)
$ cat dat/comments_file.txt
#Paths
A B #Path between A and B.
D C #Path between C and D.
E F
G H
Example Use/Output
$ ./bin/comments_remove <dat/comments_file.txt
A B
D C
E F
G H
Look things over and let me know if you have further questions.
I read some data from a file, and send it through a pipe. When I read the data from the pipe, sometimes there's extra characters inside. The extra characters are also inconsistent, but normally is an extra "R" at the end.
The data I read from the file is correct, as it is always as it should be. It's only after reading it from the pipe that I encounter problems.
Could you help me find the error? I've been staring at this for ages and I can't find it.
This is the part of my code that is giving me trouble.
Thanks for your help.
int main (int argc, char **argv) {
int nClients;
int file_name_HTML[2];
create_pipes(file_name_HTML, server_access_request);
init_free_pipes();
nClients = getHTMLFilesIntoPipe(file_name_HTML);
int clients[nClients];
for(int i=0; i < nClients; i++)
{
if((clients[i] = fork()) == 0)
{
clientFunction(file_name_HTML, server_access_request);
}
}
.....
}
int getHTMLFilesIntoPipe(int *file_name_HTML)
{
int i, n = 0;
char (*lines)[MAXCHAR] = NULL;
FILE *fp;
fp = fopen("./data/listado_html.txt", "r");
if (!fp) { /* valdiate file open for reading */
err_exit("error: file open failed.\n");
}
if (!(lines = malloc (MAXLINES * sizeof *lines))) {
err_exit("error: virtual memory exhausted 'lines'.\n");
}
while (n < MAXLINES && fgets (lines[n], MAXCHAR, fp)) /* read each line */
{
char *p = lines[n]; /* assign pointer */
for (; *p && *p != '\n'; p++) {} /* find 1st '\n' */
if (*p != '\n') /* check line read */
{
int c;
while ((c = fgetc (fp)) != '\n' && c != EOF) {} /* discard remainder of line with getchar */
}
*p = 0, n++; /* nul-termiante */
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (int i = 0; i < n; i++)
{
write(file_name_HTML[WRITE], lines[i], strlen(lines[i]));
}
free(lines);
return n;
}
void clientFunction(int *file_name_HTML, int *server_access_request)
{
char fileName[MAXCHAR];
close(file_name_HTML[WRITE]);
//Read HTML file name
read(file_name_HTML[READ], fileName, MAXCHAR - 1);
printf("%s\n", fileName);
.......
}
Expected output:
abcd1.html
abcd2.html
abcd3.html
abcd4.html
abcd5.html
Current output:
abcd1.htmlR
abcd2.htmlR
abcd3.htmlR
abcd4.htmlR
abcd5.htmlR
It is because your string is not null(\0) terminated.
As you write to the pipe excluding null(\0) terminator.
write(file_name_HTML[WRITE], lines[i], strlen(lines[i])+1);
^--- +1 to include null character.
strlen returns the length excluding null terminator.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
typedef int bool;
#define true 1
#define false 0
#define A 65
#define Z 90
#define a 97
#define z 122
#define NEWLINE 10
int main(int argc, char* argv[])
{
int noArgReverse();
int argReverse(int i, char* c[]);
if (argc == 1){
if (noArgReverse() == 0)
return 0;
else
return 1;
}
if (argc > 1){
if (argReverse(argc, argv) == 0)
return 0;
else
return 1;
}
else{
fprintf(stderr, "unknown error detected.\n");
return 1;
}
}
int noArgReverse()
{
char charInput[10000];
int pointerArray[5000];
int pointerCount = 0;
bool wordStart = false;
int indexer;
int lineLength;
int parser;
char currInput;
pointerArray[0] = 0; // first word would start at 0 be default
while (currInput != EOF){
lineLength = 0;
indexer = 0;
pointerCount = 0;
while ((currInput = getc(stdin)) != NEWLINE){
/*
* I am implementing a 10,000 char limit, as this seems an
* unreasonable length.
*/
if (lineLength == 9999){
fprintf(stderr, "Line length exceeded 10,000 chars. "
"This line and, if in the middle of a word,"
"will be split.\n");
break;
}
if (!wordStart){
if ((currInput >= A && currInput <= Z) || (currInput >= a && currInput <= z)){
wordStart = true;
}
}
while (wordStart){
charInput[lineLength++] = currInput;
currInput = getc(stdin);
//if the word has ended
if ((currInput < A || currInput > Z) && (currInput < a || currInput > z)){
wordStart = false;
charInput[lineLength++] = '\0';
if (pointerCount != 0){ // at least one word has been added
++indexer;
pointerArray[indexer] = pointerCount;
pointerCount = lineLength;
}
else //first word of the line to be added
pointerCount = lineLength;
}
}
}
while (indexer >= 0){
parser = pointerArray[indexer--];
while (charInput[parser] != '\0')
fprintf (stdout, "%c", charInput[parser++]);
fprintf (stdout, " ");
}
fprintf (stdout, "\r\n");
if (lineLength == 0){
currInput = EOF;
}
}
return 0;
}
int argReverse (int argc, char* argv[])
{
char charInput[10000];
int pointerArray[5000];
int pointerCount = 0;
bool wordStart = false;
int indexer;
int lineLength;
int parser;
char currInput;
FILE *currentFile;
while (argc > 0){
currentFile = fopen(argv[argc--], "r");
while ((currInput = getc(currentFile)) != EOF){
lineLength = 0;
indexer = 0;
pointerCount = 0;
while (currInput != NEWLINE){
/*
* I am implementing a 10,000 char limit, as this seems an
* unreasonable length for a single line.
*/
if (lineLength == 9999){
fprintf(stderr, "Line length exceeded 10,000 chars. "
"This line and, if in the middle of a word, the word, "
"will be split.\n");
break;
}
if (!wordStart){
if ((currInput >= A && currInput <= Z) || (currInput >= a && currInput <= z)){
wordStart = true;
}
}
while (wordStart){
charInput[lineLength++] = currInput;
currInput = getc(currentFile);
//if the word has ended
if ((currInput < A || currInput > Z) && (currInput < a || currInput > z)){
wordStart = false;
charInput[lineLength++] = '\0';
if (pointerCount != 0){ // at least one word has been added
++indexer;
pointerArray[indexer] = pointerCount;
pointerCount = lineLength;
}
else //first word of the line to be added
pointerCount = lineLength;
}
}
}
}
fclose(currentFile);
}
return 0;
}
So for my first function, I'm getting an error I can't seem to get to the bottom to while debugging, or, rather, I'm not sure how to solve. The function should take input from stdin, and print the words in reverse order (the chars should remain in order, so "This is a sentence" should be "sentence a is This"). Simple enough. However, when I give sample input, the output I get is all wrong.
input:
This is sample
input for testing
output:
testing for input sample is This
This
The input has one return, but the output has an extra return between the lines, and does not split the lines.
So, it's not printing the newline when it should, and it's printing the first inputted word again when it ends.
The second issue I am having is in the second set of code, the argReverse function. After the file is opened, in this case I use test.txt, which is a simple text file with a couple lines of phrases and empty lines, the first use of getc returns a segmentation fault. I read this is a permission or failed file opening, but I'm not sure what to do to fix this. I'm trying to open the last file first and work down from there, obviously, and this should be able to handle multiple files, but I can't even open one. I'm not sure what to do to fix this. I've tried moving the getc outside of the while loop, same problem. I'm guessing I'm doing something wrong with opening the file, but I don't know what it is.
Notes on style:
The bool type, and true and false are defined in <stdbool.h>.
Use character constants like 'A' 'Z' 'a' 'z' '\n' instead of hard-coded numbers, and/or use the character classification functions like isalpha from <ctype.h>.
The "reverse" functions just return 0 when they end, so there's no point in returning anything. They should be declared as returning void. If they did return something useful, I would return that value from main (eliminating the if statements). For example,
if ( argc == 1 )
return noArgReverse();
Putting large arrays on the stack is generally a bad idea. (Large is subjective, but I use 2K bytes as a rule of thumb.) For a non-reentrant function, you can declare the arrays as static to get them off the stack. For a reentrant function, you can malloc the arrays, and free them at the end.
Notes on design:
The fgets function will read a line and put it into a buffer. No need to read a character at a time.
When processing command line arguments, the canonical loop is
int main( int argc, char *argv[] )
{
for ( int i = 1; i < argc; i++ )
printf( "argv[%d] is \"%s\"\n", i, argv[i] );
}
The reason for your seg-fault is that you are using argv[argc], which the C specification guarantees to be NULL. So you are passing NULL to fopen. Furthermore, you should always check the return value from fopen, because fopen will return NULL if it is unable to open the file.
By far the biggest design issue in the code is repetition. You have two almost-identical functions, which is a nightmare to debug and maintain, since every change needs to be made twice, and tested twice. The solution is to define a reverse function that takes a file pointer as input. The main function should take care of opening/closing the files, or can pass stdin when there aren't any args.
Sample code:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXL 10000
#define MAXW 5000
void reverse( FILE *fp );
int main( int argc, char *argv[] )
{
if ( argc < 2 )
{
reverse( stdin );
return 0;
}
FILE *fp;
for ( int i = 1; i < argc; i++ )
{
printf( "----- %s -----\n", argv[i] );
if ( (fp = fopen( argv[i], "r" )) == NULL )
{
printf( "***Error: unable to open file\n" );
}
else
{
reverse( fp );
fclose( fp );
}
}
return 0;
}
void reverse( FILE *fp )
{
static char line[MAXL]; // buffer for the input line
static char *word[MAXW]; // array of pointers to the words on the line
while ( fgets( line, MAXL, fp ) != NULL )
{
int i = -1;
int count = 0; // count of words on the line
for (;;)
{
// skip any non-alpha characters
for ( i++; line[i]; i++ )
if ( isalpha( line[i] ) )
break;
// check if we've reached the end of the line
if ( !line[i] )
break;
// add the pointer to the word list
word[count++] = &line[i];
// scan till we reach the end of the word
for ( i++; line[i]; i++ )
if ( !isalpha( line[i] ) )
break;
// check if we've reached the end of the line
if ( !line[i] )
break;
// terminate the word
line[i] = '\0';
}
// output the words in reverse order
for ( i = count - 1; i >= 0; i-- )
printf( "%s ", word[i] );
printf( "\n" );
}
}
How to write a ANSI C user-defined function that returns a specific line from a text file?
char * ReadFromFile(const char * fileName, int line)
{
//..........
}
This should do the trick:
char * ReadFromFile(const char * fileName, int line)
{
FILE *fp;
char c;
char *buffer = malloc( 100 * sizeof(char) ); // change 100 to a suitable value;
int buffer_length = 100; // eg. max length of line in your file
int num = 0;
if(line < 0) // check for negative line numbers
{
printf("Line number must be 0 or above\n");
return(NULL);
}
if( ( fp = fopen(fileName,"r") ) == NULL )
{
printf("File not found");
return(NULL);
}
while(num < line) // line numbers start from 0
{
c = getc(fp);
if(c == '\n')
num++;
}
c = getc(fp);
if(c == EOF)
{
printf("Line not found\n");
fclose(fp);
return(NULL);
}
else
{
ungetc(c,fp); //push the read character back onto the stream
fgets(buffer,buffer_length,fp);
fclose(fp);
return(buffer);
}
}
Edit: The boundary conditions suggested by caf & lorenzog in the comments have been included. Never thought error-proofing could be so tedious! (Still doesn't check for cases where line number is more than int can safely hold. This is left as an exercise to OP :)