A Version of the Standard Linux cat CommandΒΆ

This version of the cat command fixes the two bugs in mycat_2bugs.cpp.

// mycat_fixed.cpp

//////////////////////////////////////////////////////////////////////////////////////////
//
// This is a simple version of the standard Linux command "cat", that is used
// for displaying the contents of files at the command-line.
//
// You can use cat like this:
//
//    $ cat makefile
//   CPPFLAGS = -std=c++14 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g
//
// cat has a few options, such as:
//
// - -E puts a $ character at the end of each line
//
// - -n numbers the lines
//
// - -T shows tab characters as ^I
//
// The version of cat we create here won't any flags as iput, but will have
// all of these options turned on by default.
//
// Note that for printing line numbers the C function printf is used. This is
// for simplicity, since it is easier to create a right-justified number using
// printf than using cout. You can certainly do it with cout, but it is more
// complicated and requires more code.
//
// printf requires C-style strings as input, and so when want to use C++-style
// strings (i.e. from #include stirng>) we call the s.c_str().
//
//////////////////////////////////////////////////////////////////////////////////////////

#include "cmpt_error.h"
#include <iostream>     // cout
#include <fstream>      // ifstream
#include <cstdio>       // printf
#include <string>

using namespace std;

//
// useful constants
//
const bool DEBUG_ON = false;

const string TAB_PRINT = "^I";
const string NEWLINE_PRINT = "$";
const string NUM_FORMAT = "%6d\t"; // right-justified in a 6 char wide field
                                   // ending with a tab character (\t)

//
// We're reading arguments from the command-line, and so we use this
// old-fashioned C-style way of dealing with strings and arrays.
//
//   argc: the argument count
//
//   argv: an array of C-style strings contain all the arguments;
//         the first argument is the program name
int main(int argc, char *argv[]) {
    if (DEBUG_ON) {
        // print all arguments
        cout << argc << " arguments:\n";
        for(int i = 0; i < argc; i++) {
            cout << "   arg[" << i << "] = \"" << argv[i] << "\"\n";
        }
    }

    //
    // make sure a file argument has been provided
    //
    // first argument to mycat will be the file we want to display
    if (argc < 2) {
        cout << "Error: not enough arguments\n";
        exit(-1);  // -1 could be used by programs that call this program as an
    }              // error code

    //
    // try opening the file
    //
    ifstream infile(argv[1]);  // not argv[0] (which is the name of the program)
    if (infile.fail()) {
        cout << "Error: unable to open file " << argv[1] << "\n";
        exit(-1);
    }

    //
    // read the input file one character at a time
    //
    int line_num = 0;
    char c;
    infile.get(c);

    // if the file is empty then we're done`
    if (infile.eof()) return 0;

    // first line of the file is handled as a special case
    line_num++;
    printf(NUM_FORMAT.c_str(), line_num);
    cout << c;
    infile.get(c);

    while (!infile.eof()) {
        switch (c) {
            case '\n': // newline character
                cout << NEWLINE_PRINT << "\n";
                if (infile.peek() != EOF) { // peek returns next character without consuming it
                    line_num++;
                    printf(NUM_FORMAT.c_str(), line_num);
                }
            break;
            case '\t': // tab character
                cout << TAB_PRINT;
            break;
            default:
                cout << c;
        } // switch
        infile.get(c);
    } // while

} // main