C
cs
Hi,
I'm new to C and would appreciate any feedback on the following
program, asplit, which splits a file into 2 new files, putting a
certain number of lines in the first file, and all the rest in the
second file.
Any comments as to non-portability, stylistic infelicities, outright
bugs or anything else would be very much appreciated.
It compiles fine using gcc 4.1.2, and --std=c99 -pedantic -Wall, with
no warnings, and seems to work.
There are two files, a header and a source file.
## Begin asplit.h ##
#ifndef ASPLIT_H_
#define ASPLIT_H_
enum e_status { SUCCESS, ERROR_FILE_INPUT, ERROR_FILE_OUTPUT_A,
ERROR_FILE_OUTPUT_B };
typedef enum e_status status;
typedef struct {
int status;
long int num_rem_lines;
} asplit_result;
/**
* Asymmetrically splits file named in_file into 2 files, out_file_a,
and
* out_file_b, putting num_lines (must be non-negative) lines in the
first
* file, and the rest in the second file.
*
* Any of the filenames may be "-", indicating stdin in the case of
in_file,
* or stdout in the case of out_file_a or out_file_b.
*
* Returns a pointer to an asplit_result struct (unless unable to
allocate,
* in which case it returns NULL), which shows success or failure and
the
* number of lines that were included in 2nd file if successful.
* Possible status values are given by the e_status enumeration in
this
* header file. Success is indicated by SUCCESS, and each of the 3
different
* failure states is indicated by one of the enum values beginning
with
* ERROR_, indicating which file was unable to be opened.
*
* If the status is SUCCESS, then the num_rem_lines value indicates
* the number of lines remaining after the first file was filled,
* all of which were entered into the second file.
*
* The client is responsible for memory management of the result
struct.
*
*/
asplit_result * asplit(const char *in_file, const char *out_file_a,
const char *out_file_b,
const long int num_lines);
#endif /*ASPLIT_H_*/
## End asplit.h ##
## Begin asplit.c ##
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "asplit.h"
char *copy_string(const char *const s);
int close_file(FILE * f);
FILE * open_infile(const char * fname);
FILE * open_outfile(const char *fname);
/* Name under which program was invoked. */
char *program_name;
void usage(int status) {
if (status != EXIT_SUCCESS)
fprintf(stderr, "Try `%s -h' for more information.\n",
program_name);
else {
printf(
"\n\
Usage: %s [OPTION] -i IN_FILE -a OUT_FILE_A -b OUT_FILE_B -n NUM_LINES
\n\n\
",
program_name);
puts("\
Asymmetrically split IN_FILE into 2 files, putting the first NUM_LINES
\n\
into OUT_FILE_A and the rest into OUT_FILE_B.\n\
\n\
-i input file, IN_FILE\n\
-a name of first output file, OUT_FILE_A\n\
-b name of second output file, OUT_FILE_B\n\
-n the number of lines to put in OUT_FILE_A\n\
\n\
-h display this help and exit\n\
-v verbose output\n\
");
puts("\
\n\
When IN_FILE is -, use standard input; when OUT_FILE_A or OUTFILE_B is
-,\n\
use standard output.\n\
");
printf(
"\
\n\
Examples:\n\n\
%s -n 100 -i - -a output1 -b output2\n\
Read from standard input, writing first 100 lines to output1
file and\n\
all following lines to output2.\n\n\
%s -n 1000 -i infile.txt -a output1.txt -b -\n\
Read from infile.txt, writing the first 1000 lines to
output1.txt and\n\
all following lines to standard output.\n\n\
%s -n 1 -i - -a - -b -\n\
Copy standard input to standard output.\n\n\
\n",
program_name, program_name,
program_name);
puts("\nReturns 0 on success, and 1 on failure.\n");
printf("\nReport bugs to (e-mail address removed).
\n\n");
}
exit(status);
}
asplit_result * asplit(const char *in_file, const char *out_file_a,
const char *out_file_b,
const long int num_lines) {
long int curr_line_num;
int curr_chr;
FILE *in_f, *out_f;
asplit_result *result;
size_t size = sizeof(asplit_result);
result = malloc(size);
if (result == NULL) {
return NULL;
} else {
result->num_rem_lines = 0;
}
/* Open in_file if not "-", returning unsuccessfully if unable
to open. */
if ((in_f = open_infile(in_file)) == NULL) {
result->status = ERROR_FILE_INPUT;
return result;
}
/* If num_lines is 0, then nothing goes into the first file.*/
if (num_lines == 0) {
/*
* So open second file if not "-", returning
unsuccessfully
* if unable to open.
*/
if ((out_f = open_outfile(out_file_b)) == NULL) {
close_file(in_f);
result->status = ERROR_FILE_OUTPUT_B;
return result;
}
} else {
/*
* If num_lines is greater than 0, then open first
file, returning
* unsuccessfully if unable to open.
*/
if ((out_f = open_outfile(out_file_a)) == NULL) {
close_file(in_f);
result->status = ERROR_FILE_OUTPUT_A;
return result;
}
}
/* Initial line number is 0, and increments after each newline
is read. */
curr_line_num = 0;
/* While input file still has characters: */
while ((curr_chr = getc(in_f)) != EOF) {
/* Output char to current output file. */
putc(curr_chr, out_f);
/*
* If we read a newline, and if we've just hit the
maximum
* allowed in the first file, we must close the first
file, and
* open the second file, returning unsuccessfully if
unable to open.
*/
if (curr_chr == '\n'&& ++curr_line_num == num_lines) {
close_file(out_f);
if ((out_f = open_outfile(out_file_b)) ==
NULL) {
close_file(in_f);
result->status = ERROR_FILE_OUTPUT_B;
return result;
}
}
}
/* Close input and output files if necessary. */
close_file(in_f);
close_file(out_f);
/* Return the number of lines output to the second file. */
result->status = 0;
result->num_rem_lines = (curr_line_num < num_lines) ? 0 :
(curr_line_num - num_lines);
return result;
}
int main(int argc, char **argv) {
program_name = *argv;
char *input_file = NULL;
char *output_file_a = NULL;
char *output_file_b = NULL;
int num_lines = -1;
int verbose = 0;
asplit_result *result = NULL;
int ich;
while ((ich = getopt(argc, argv, "hvi:a:b:n:")) != EOF) {
switch (ich) {
case 'h':
usage(0);
break;
case 'i':
input_file = (char *) copy_string(optarg);
break;
case 'a':
output_file_a = (char *) copy_string(optarg);
break;
case 'b':
output_file_b = (char *) copy_string(optarg);
break;
case 'n':
num_lines = atoi(optarg);
if (num_lines < 0)
printf("-n arg must be at least 0, not
%d.\n", num_lines);
break;
case 'v':
verbose = 1;
break;
default:
usage(1);
break;
}
}
/* Verify that all required arguments were submitted. */
if (input_file == NULL|| output_file_a == NULL|| output_file_b
== NULL
|| num_lines < 0)
usage(1);
/* Do the actual split. */
result = asplit(input_file, output_file_a, output_file_b,
num_lines);
if (result == NULL) {
fprintf(stderr, "Error: asplit returned NULL result.");
exit(1);
}
/* Determine whether it terminated successfully, and handle
accordingly. */
switch (result->status) {
case SUCCESS:
if (verbose)
printf("Output %li lines to 2nd file.\n",
result->num_rem_lines);
exit(0);
case ERROR_FILE_INPUT:
fprintf(stderr, "Unable to open input file: %s\n",
input_file);
exit(1);
case ERROR_FILE_OUTPUT_A:
fprintf(stderr, "Unable to open output file: %s\n",
output_file_a);
exit(1);
case ERROR_FILE_OUTPUT_B:
fprintf(stderr, "Unable to open output file: %s\n",
output_file_b);
exit(1);
default:
fprintf(stderr, "Error: unexpected return value %d\n", result-
}
}
/**
* Create a copy of the string referenced by the given pointer,
returning
* a pointer to the newly created copy, or NULL if unable to malloc
memory
* for the copy or original pointer was null or the string was empty.
*
* The client is responsible for memory management of the new string.
*/
char *copy_string(const char *str_orig) {
char *str_copy = NULL;
if (str_orig != NULL && *str_orig != 0) {
size_t size = strlen(str_orig) + 1;
str_copy = malloc(size);
if (str_copy != NULL)
memcpy(str_copy, str_orig, size);
}
return str_copy;
}
/**
* Close the file referenced by the given file pointer if it is not
one of
* the standard system files.
*
* Returns 0 if the file was actually closed, and 1 if not closed
*/
int close_file(FILE * f) {
if (f != stdin&& f != stdout&& f != stderr) {
fclose(f);
return 0;
}
return 1;
}
/**
* Open the input file (mode 'r') referenced by the given pointer,
* returning a pointer to the FILE. If fname is "-", return stdin.
*/
FILE * open_infile(const char *const fname) {
return strcmp(fname, "-") == 0 ? stdin : fopen(fname, "r");
}
/**
* Open the output file (mode 'w') referenced by the given pointer,
* returning a pointer to the FILE. If fname is "-", return stdout.
*/
FILE * open_outfile(const char *const fname) {
return strcmp(fname, "-") == 0 ? stdout : fopen(fname, "w");
}
## End asplit.c ##
I did browse the FAQ already and found a couple of things to improve,
which I already did, but any additional feedback would be appreciated.
Cheers,
calvin
I'm new to C and would appreciate any feedback on the following
program, asplit, which splits a file into 2 new files, putting a
certain number of lines in the first file, and all the rest in the
second file.
Any comments as to non-portability, stylistic infelicities, outright
bugs or anything else would be very much appreciated.
It compiles fine using gcc 4.1.2, and --std=c99 -pedantic -Wall, with
no warnings, and seems to work.
There are two files, a header and a source file.
## Begin asplit.h ##
#ifndef ASPLIT_H_
#define ASPLIT_H_
enum e_status { SUCCESS, ERROR_FILE_INPUT, ERROR_FILE_OUTPUT_A,
ERROR_FILE_OUTPUT_B };
typedef enum e_status status;
typedef struct {
int status;
long int num_rem_lines;
} asplit_result;
/**
* Asymmetrically splits file named in_file into 2 files, out_file_a,
and
* out_file_b, putting num_lines (must be non-negative) lines in the
first
* file, and the rest in the second file.
*
* Any of the filenames may be "-", indicating stdin in the case of
in_file,
* or stdout in the case of out_file_a or out_file_b.
*
* Returns a pointer to an asplit_result struct (unless unable to
allocate,
* in which case it returns NULL), which shows success or failure and
the
* number of lines that were included in 2nd file if successful.
* Possible status values are given by the e_status enumeration in
this
* header file. Success is indicated by SUCCESS, and each of the 3
different
* failure states is indicated by one of the enum values beginning
with
* ERROR_, indicating which file was unable to be opened.
*
* If the status is SUCCESS, then the num_rem_lines value indicates
* the number of lines remaining after the first file was filled,
* all of which were entered into the second file.
*
* The client is responsible for memory management of the result
struct.
*
*/
asplit_result * asplit(const char *in_file, const char *out_file_a,
const char *out_file_b,
const long int num_lines);
#endif /*ASPLIT_H_*/
## End asplit.h ##
## Begin asplit.c ##
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "asplit.h"
char *copy_string(const char *const s);
int close_file(FILE * f);
FILE * open_infile(const char * fname);
FILE * open_outfile(const char *fname);
/* Name under which program was invoked. */
char *program_name;
void usage(int status) {
if (status != EXIT_SUCCESS)
fprintf(stderr, "Try `%s -h' for more information.\n",
program_name);
else {
printf(
"\n\
Usage: %s [OPTION] -i IN_FILE -a OUT_FILE_A -b OUT_FILE_B -n NUM_LINES
\n\n\
",
program_name);
puts("\
Asymmetrically split IN_FILE into 2 files, putting the first NUM_LINES
\n\
into OUT_FILE_A and the rest into OUT_FILE_B.\n\
\n\
-i input file, IN_FILE\n\
-a name of first output file, OUT_FILE_A\n\
-b name of second output file, OUT_FILE_B\n\
-n the number of lines to put in OUT_FILE_A\n\
\n\
-h display this help and exit\n\
-v verbose output\n\
");
puts("\
\n\
When IN_FILE is -, use standard input; when OUT_FILE_A or OUTFILE_B is
-,\n\
use standard output.\n\
");
printf(
"\
\n\
Examples:\n\n\
%s -n 100 -i - -a output1 -b output2\n\
Read from standard input, writing first 100 lines to output1
file and\n\
all following lines to output2.\n\n\
%s -n 1000 -i infile.txt -a output1.txt -b -\n\
Read from infile.txt, writing the first 1000 lines to
output1.txt and\n\
all following lines to standard output.\n\n\
%s -n 1 -i - -a - -b -\n\
Copy standard input to standard output.\n\n\
\n",
program_name, program_name,
program_name);
puts("\nReturns 0 on success, and 1 on failure.\n");
printf("\nReport bugs to (e-mail address removed).
\n\n");
}
exit(status);
}
asplit_result * asplit(const char *in_file, const char *out_file_a,
const char *out_file_b,
const long int num_lines) {
long int curr_line_num;
int curr_chr;
FILE *in_f, *out_f;
asplit_result *result;
size_t size = sizeof(asplit_result);
result = malloc(size);
if (result == NULL) {
return NULL;
} else {
result->num_rem_lines = 0;
}
/* Open in_file if not "-", returning unsuccessfully if unable
to open. */
if ((in_f = open_infile(in_file)) == NULL) {
result->status = ERROR_FILE_INPUT;
return result;
}
/* If num_lines is 0, then nothing goes into the first file.*/
if (num_lines == 0) {
/*
* So open second file if not "-", returning
unsuccessfully
* if unable to open.
*/
if ((out_f = open_outfile(out_file_b)) == NULL) {
close_file(in_f);
result->status = ERROR_FILE_OUTPUT_B;
return result;
}
} else {
/*
* If num_lines is greater than 0, then open first
file, returning
* unsuccessfully if unable to open.
*/
if ((out_f = open_outfile(out_file_a)) == NULL) {
close_file(in_f);
result->status = ERROR_FILE_OUTPUT_A;
return result;
}
}
/* Initial line number is 0, and increments after each newline
is read. */
curr_line_num = 0;
/* While input file still has characters: */
while ((curr_chr = getc(in_f)) != EOF) {
/* Output char to current output file. */
putc(curr_chr, out_f);
/*
* If we read a newline, and if we've just hit the
maximum
* allowed in the first file, we must close the first
file, and
* open the second file, returning unsuccessfully if
unable to open.
*/
if (curr_chr == '\n'&& ++curr_line_num == num_lines) {
close_file(out_f);
if ((out_f = open_outfile(out_file_b)) ==
NULL) {
close_file(in_f);
result->status = ERROR_FILE_OUTPUT_B;
return result;
}
}
}
/* Close input and output files if necessary. */
close_file(in_f);
close_file(out_f);
/* Return the number of lines output to the second file. */
result->status = 0;
result->num_rem_lines = (curr_line_num < num_lines) ? 0 :
(curr_line_num - num_lines);
return result;
}
int main(int argc, char **argv) {
program_name = *argv;
char *input_file = NULL;
char *output_file_a = NULL;
char *output_file_b = NULL;
int num_lines = -1;
int verbose = 0;
asplit_result *result = NULL;
int ich;
while ((ich = getopt(argc, argv, "hvi:a:b:n:")) != EOF) {
switch (ich) {
case 'h':
usage(0);
break;
case 'i':
input_file = (char *) copy_string(optarg);
break;
case 'a':
output_file_a = (char *) copy_string(optarg);
break;
case 'b':
output_file_b = (char *) copy_string(optarg);
break;
case 'n':
num_lines = atoi(optarg);
if (num_lines < 0)
printf("-n arg must be at least 0, not
%d.\n", num_lines);
break;
case 'v':
verbose = 1;
break;
default:
usage(1);
break;
}
}
/* Verify that all required arguments were submitted. */
if (input_file == NULL|| output_file_a == NULL|| output_file_b
== NULL
|| num_lines < 0)
usage(1);
/* Do the actual split. */
result = asplit(input_file, output_file_a, output_file_b,
num_lines);
if (result == NULL) {
fprintf(stderr, "Error: asplit returned NULL result.");
exit(1);
}
/* Determine whether it terminated successfully, and handle
accordingly. */
switch (result->status) {
case SUCCESS:
if (verbose)
printf("Output %li lines to 2nd file.\n",
result->num_rem_lines);
exit(0);
case ERROR_FILE_INPUT:
fprintf(stderr, "Unable to open input file: %s\n",
input_file);
exit(1);
case ERROR_FILE_OUTPUT_A:
fprintf(stderr, "Unable to open output file: %s\n",
output_file_a);
exit(1);
case ERROR_FILE_OUTPUT_B:
fprintf(stderr, "Unable to open output file: %s\n",
output_file_b);
exit(1);
default:
fprintf(stderr, "Error: unexpected return value %d\n", result-
exit(1);status);
}
}
/**
* Create a copy of the string referenced by the given pointer,
returning
* a pointer to the newly created copy, or NULL if unable to malloc
memory
* for the copy or original pointer was null or the string was empty.
*
* The client is responsible for memory management of the new string.
*/
char *copy_string(const char *str_orig) {
char *str_copy = NULL;
if (str_orig != NULL && *str_orig != 0) {
size_t size = strlen(str_orig) + 1;
str_copy = malloc(size);
if (str_copy != NULL)
memcpy(str_copy, str_orig, size);
}
return str_copy;
}
/**
* Close the file referenced by the given file pointer if it is not
one of
* the standard system files.
*
* Returns 0 if the file was actually closed, and 1 if not closed
*/
int close_file(FILE * f) {
if (f != stdin&& f != stdout&& f != stderr) {
fclose(f);
return 0;
}
return 1;
}
/**
* Open the input file (mode 'r') referenced by the given pointer,
* returning a pointer to the FILE. If fname is "-", return stdin.
*/
FILE * open_infile(const char *const fname) {
return strcmp(fname, "-") == 0 ? stdin : fopen(fname, "r");
}
/**
* Open the output file (mode 'w') referenced by the given pointer,
* returning a pointer to the FILE. If fname is "-", return stdout.
*/
FILE * open_outfile(const char *const fname) {
return strcmp(fname, "-") == 0 ? stdout : fopen(fname, "w");
}
## End asplit.c ##
I did browse the FAQ already and found a couple of things to improve,
which I already did, but any additional feedback would be appreciated.
Cheers,
calvin