We ❤️ Open Source
A community education resource
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
- Read: A throwback experiment with Linux and Unix: Introduction and set-up
- Read part one of this tutorial series: Writing a FORTRAN 66 program
- Read part two of this tutorial series: Prepare a document using
nroff
- Read: Technology history: Where Unix came from
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.