We ❤️ Open Source

A community education resource

5 min read

A Linux and Unix experiment – Part 3: Writing several C programs

Here's what writing C programs was like using Unix from fifty years ago.

This article is a part of a series called “A throwback experiment with Linux and Unix.” Read the Introduction and set-up, part 1, and part 2 of the series.

For my “throwback” experiment, I wanted to demonstrate that you can still do real work on Linux, using just a terminal and the command line, just like the original Unix.

As a proof of concept, I worked for a week in Linux like it was Unix from fifty years ago. During this experiment, I assigned myself three tasks. The third task was to write several C programs. This is my experience.

For reference, I’ve included a list of all user commands in Unix 4th edition.


Unix 4th edition commands reference guide


Write several C programs

Having prepared a document using nroff, I need to print it. Unix 4th Edition provided a program called type that would print a document on a TeleType Model 37 terminal, as though it had been typed on a typewriter. The type command would wait for the operator to load a US Letter page into the terminal and press Enter. Then type printed 66 lines (at ten characters per inch and six lines per inch, a US Letter page was 85 characters wide and 66 lines long) then waited for the operator to load another page into the terminal and press Enter before continuing.

This type command doesn’t exist on modern Linux systems. Instead, type is an internal Bash command that tells you about a command. For example, type cp might tell you it’s an external command, and type for should indicate it’s a Bash keyword:

$ type cp
cp is /bin/cp

$ type for
for is a shell keyword

I decided to write my own type command, to mimic the original from Unix 4th Edition. With the assumption that nroff will not produce lines that are too long for the terminal, the typefile() function only needs to count new lines, and pause after every 66 lines. I wrote this using ed:

#include <stdio.h>

void pause(void)
{
 while (getchar() != '\n');
}

void typefile(FILE *in, FILE *out)
{
 int linenum = 0;
 int ch;

 while ((ch = fgetc(in)) != EOF) {
   fputc(ch, out);

   if (ch == '\n') {
     linenum += 1;

     if (linenum == 66) {
       pause();
       linenum = 0;
     }
   }
 }
}

int main(int argc, char **argv)
{
 int i;
 FILE *pfile;

 for (i = 1; i < argc; i++) {
   pfile = fopen(argv[i], "r");

   if (pfile != NULL) {
     pause();                  /* wait for operator to load page */
     typefile(pfile, stdout);
     fclose(pfile);
   }
   /* else: silently ignore */
 }

 return 0;
}

You might notice the pause() function uses getchar() to read single characters from the standard input, looking for the Enter key. While this is naive programming today, reading keyboard input like this was not unusual on terminals from fifty years ago.

I tried to write this in original “K&R” C style, which didn’t include variable types inside function definitions, and assumed int for the return type. Newer versions of the GNU C Compiler reject this older syntax, so I had to write my program using a more modern style. If you’re interested in the history, my functions would instead have been defined this way in original C in from fifty years ago:

#include <stdio.h>

pause()
{
 ...
}

FILE *in; FILE *out;
typefile(in, out)
{
...
}

int argc; char **argv;
main(arg, argv)
{
 ...
}

Now that I had a version of the type program, I needed to test it before running it against my file. One way to verify the program works correctly is to read a file with 67 lines, to see if it pauses after 66 lines.

That’s fairly easy to do with the seq command, which creates a sequence of numbers. Unfortunately, Unix 4th Edition didn’t include the seq command, so I needed to write my own instead:

#include <stdio.h>
#include <stdlib.h>             /* atoi */

int main(int argc, char **argv)
{
 int start = 1, stop = 10, step = 1;
 int i;

 /* usage:
    seq stop
    seq start stop
    seq start stop step
  */

 if (argc == 2) {
   stop = atoi(argv[1]);
 } else if (argc > 2) {
   start = atoi(argv[1]);
   stop = atoi(argv[2]);

   if (argc == 4) {
     step = atoi(argv[3]);
   }
 }

 /* bug: assumes step>0 (cannot count down) */

 for (i = start; i <= stop; i += step) {
   printf("%d\n", i);
 }

 return 0;
}

If you are a C programmer, you might notice my else if is “cuddled” on line 17; that was the style in Kernighan and Ritchie’s C programming book, and reflected the style in use at Bell Labs.

My seq command is similar to the modern version of seq except I’ve borrowed the sequence definition from FORTRAN. Where modern seq might use seq 1 3 4 to count from 1 to 4 by every third number (1 then 4), my version uses ./seq 1 4 3 instead. Here are a few examples:

$ ./seq 3
1
2
3

$ ./seq 3 6
3
4
5
6

$ ./seq 3 9 2
3
5
7
9

With this new seq command, I could create a test file with 67 lines, and use that with my type command to verify that it waits for the Enter key, prints 66 lines, waits for the Enter key again, then prints the rest of the file.


More from We Love Open Source

About the Author

Jim Hall is an open source software advocate and developer, best known for usability testing in GNOME and as the founder + project coordinator of FreeDOS. At work, Jim is CEO of Hallmentum, an IT executive consulting company that provides hands-on IT Leadership training, workshops, and coaching.

Read Jim's Full Bio

The opinions expressed on this website are those of each author, not of the author's employer or All Things Open/We Love Open Source.

Register for All Things Open 2024

Join thousands of open source friends October 27-29 in downtown Raleigh for ATO 2024!

Upcoming Events

We do more than just All Things Open and Open Source 101. See all upcoming events here.

Open Source Meetups

We host some of the most active open source meetups in the U.S. Get more info and RSVP to an upcoming event.