19

In advice about how to design good CLI commands I read:

If your command is expecting to have something piped to it and stdin is an interactive terminal, display help immediately and quit. This means it doesn’t just hang, like cat.

This seems like a really good call to me. The default behavior of cat is to appear to hang, and to reduplicate anything the user types in an attempt to interact with it. It is such a bad default that I assume it must have been intended for some other context where this behavior would make more sense.

What was that context? Why would I ever want this command to read from standard input? Is there some non-interactive context where this is useful?

I only ever use cat to view (short) files in the terminal, but I gather (from the name) that its original purpose was once concatenation of files. What sense does it make to concatenate standard input, with no further argument?

2
  • Comments have been moved to chat; please do not continue the discussion here. Before posting a comment below this one, please review the purposes of comments. Comments that do not request clarification or suggest improvements usually belong as an answer, on Retrocomputing Meta, or in Retrocomputing Chat. Comments continuing discussion may be removed.
    – Chenmunka
    Mar 16, 2023 at 18:07
  • I remember there was a utility which emitted an error message like "people don't usually type in their log files on the terminal, but what do I care?" (to standard error, natch) when it sensed that standard input was a terminal. I can't remember which tool it was. Probably some Perl log analyzer?
    – tripleee
    Mar 27, 2023 at 15:13

10 Answers 10

17

TL;DR: In UNIX version 1, the easiest way to get unfiltered, human-typed input onto the standard output was with an argument-less cat. A few versions later, echo would provide another simple means to get human-typed input onto the standard output.


It is such a bad default that I assume it must have been intended for some other context where this behavior would make more sense.

cat was introduced in 1971, UNIX version 1, for the PDP series of minicomputers. Computer I/O at that time used fancy typewriters called teletypes for both input and output(*):

Popular minicomputers of the late 1960s and early ’70s, such as the PDP-8, PDP-11, and the Data General Nova, supported ASCII encoding, making the Model 33 [teletype] an ideal low-cost (relatively speaking) input/output (I/O) terminal for them. In particular, the PDP series by DEC were influential machines, and if you look up historical photos of them, you’ll almost always see a Teletype Model 33 in use beside them.
...
When you used a teletype with a mainframe [sic] computer like these, you’d see your own local input on paper as you typed, and then you’d receive a response from the computer printed below it as the teletype printed to a continuous feed of rolled paper stored within the unit.
...
In 1970, Dennis Ritchie and Ken Thompson developed the UNIX operating system on a PDP-11 using Model 33 teletypes as interfaces, and some of the teletype-related design choices that they made are still with us today.

Where the "bad default" of today made more sense back then was as a way to pass human-typed text to the output teletype, without having to go through a named file -- remember this was before McIlroy had conceptualized pipes and the only way to get data into a named file was to write a program (on tape or hand-entered line by line) that created a named file in the filesystem (there was no echo command until version 6, written by McIlroy no less):

cat
--- Here is Today's Weather Report ---
^D
cat report

In this example, the user invokes cat and types a line of text that acts as a header indicating that the day's weather report is coming, ending that input with an end-of-file sequence. Then the user invokes cat again to output a file called report.

Of course, the commands the user types will appear on their own console (de facto because a teletype is just an ink-on-paper typewriter), but importantly the output of these cat commands will go to standard out, which may be a different teletype than the one on which the commands are entered.

For example, in this picture the console teletype giving the commands (middle of photo) is routing output to the large line printer (left of photo):

> Cecelia Larsen [is] sitting at the console typewriter and Bob Kuhn [is] sitting at the CRT. I do not recall the name of the person standing at the Analex line printer.

In this picture, we see the Self Service I/O Area at Columbia University, circa early 1980. Notice that this multi-user environment was also multi-room, with input terminals a good distance away from output terminals.

The Columbia University Self Service Input/Output Area, circa early 1980s.

The uniformity of treating all devices as files combined with the elegance of all programs having standard input and output channels, allowed UNIX to adapt to many environments and configurations without having to reprogram for every scenario. As reprogramming was expensive and specialized, only the most versatile programs endured: cat, now more than 50 years old and unchanged in its essential operation, is proof.


In search of direct, authoritative historical evidence, I turned to my copy of "UNIX: A History and Memoir" by Brian Kernighan. I can find no explicit statement that the interactive console for standard input was an explicit design choice, but there are a few choice paragraphs on this subject.

It’s interesting to speculate about how differently things might have turned out if Unix had been developed in a world with punch cards instead of Teletypes.
-- Kernighan, Brian. UNIX: A History and a Memoir (p. 182). Kindle Edition.

Ken (seated) and Dennis at the PDP-11, ~1972 (Wikipedia)

Ken is typing at a Model 33 Teletype, a sturdy but slow and noisy device, basically a computer-controlled electro-mechanical typewriter that could only print in upper case, at 10 characters per second. The Model 33 dates from 1963 but earlier versions had been in widespread use since the early 1930s. Teletype Corporation was a part of AT&T, and Teletypes were widely used throughout the Bell System and elsewhere for sending messages, and later for connecting to computers. Whatever was typed on the Teletype keyboard was sent to the computer, and responses were printed (in upper case) on a long roll of paper; the tops of the paper rolls are just visible in the picture. Arguably, one reason why many command names on Unix are short is that it took considerable physical force to type on a Model 33, and printing was slow. Someone even built an experimental “portable” Model 33. The keyboard and printer were shoehorned into a suitcase-like container that in theory could be carried around, though at 55 pounds (25 kg) you wouldn’t carry it far. (It had no wheels either.) It was connected to a remote computer through a dial-up phone connection and a built-in acoustic coupler: plug a telephone handset into a couple of rubber sockets and the coupler converted data into sound and back again, rather like a fax machine.
-- Kernighan, Brian. UNIX: A History and a Memoir (p. 57-58). Kindle Edition.

It's interesting that in 1978, 6 years after cat was written, McIlroy summarized "[T]he Unix philosophy, a style of programming, of how to approach a computing task" with this point:

Expect the output of every program to become the input to another, as yet unknown, program. Don’t clutter output with extraneous information. Avoid stringently columnar or binary input formats. Don’t insist on interactive input.
-- Kernighan, Brian. UNIX: A History and a Memoir (p. 183). Kindle Edition.

Perhaps had pipes and shell redirection been a part of UNIX version 1, then a dedicated program like read would have been the only way to read bytes from the interactive console and the absence of a standard input stream to a program would have immediately exited.


Addenda

The PDP-1 Input-Output Systems Manual is a great reference for how the PDP series worked at a line level -- signals, bits, and wires -- and provides a

(*) Ben Gurley (designer of the PDP-1) had written a subroutine for displaying characters on the PDP-1 scope (barely a character display), but (a) that wasn't universally installed and (b) it still treated the display as a teletype.

10
  • 1
    I don't quite get it. Why would they need "a way to echo human typed text to the teletype" when a teletype normally allowed you to "see your own local input on paper as you typed"? Mar 15, 2023 at 23:04
  • @JohnSkilesSkinner Great question, which I skipped over originally. I've expanded on this point with my recent edit.
    – bishop
    Mar 16, 2023 at 1:54
  • 1
    re a mainframe computer like these 'These' meaning PDP-8 and PDP-11, uh-huh. I wonder what 'Benj Edwards' thinks 'mainframe' means.
    – dave
    Mar 16, 2023 at 2:39
  • @another-dave Try asking him on Twitter; his contact is in the byline. Smells like an editorial oversight to me and no reason to impugn the author or article.
    – bishop
    Mar 16, 2023 at 3:10
  • 1
    From a historical point of view, DEC operating systems (and later CP/M) had a command called PIP (Peripheral Interchange Program) which functioned similarly to cat. It took input from one peripheral and sent it to another. Unix generalized this into stdin/stdout/stderr and pipes.
    – mannaggia
    Mar 28, 2023 at 15:10
43

This is not specific to cat. It's a general rule in Unix that, if there are no arguments, the program reads from standard input. This is also true of grep, od, sort, split, uniq, wc, and so on. This lets you pipe in the input instead of needing a file, but it will take input from the terminal as well.

Pipes were introduced in V3 Unix, but isatty() didn't appear until V7 Unix. I couldn't tell you if the behavior would be any different if isatty() had been available back then, but now it's just the way Unix works.


As an addendum, it seems that Plan 9 utilities also have this behavior of waiting for input when invoked without arguments, even though it otherwise departs from historical Unix behavior in many ways. So, I'd have to conclude that the designers of Unix actually would not have changed this if they were making it today, and it all comes down to a difference in opinion about what the "correct" behavior is.

2
38

'cat' fits the pipeline model, which says that for a program with one input and one output, it should read from stdin and write to stdout, at least by default in the case that no files are specified.

Rather than asking why cat reads stdin, I'd ask what benefit you'd gain by not having it do that. I don't accept that tailoring a utility command like cat to people who don't know what it does is a good tradeoff.

This is essentially an argument from consistency, but there's also an argument from simplicity. If you look at the source code for the primeval Unix utilities, they were pretty minimalist. Why write 'isatty' code (assuming you even have such a system call) when cat works fine without it?

Design decisions made 50 years ago might have used different tradeoffs!

To put you in the right frame of mind, I quote from the 1971 Unix Programmer's Manual page for 'cat':

DIAGNOSTICS: none; if a file cannot be found it is ignored.

Postscript: I gather (from the name) that its original purpose was once concatenation of files. Well, sort of. But it demonstrates a certain parsimony of concept that I like. You don't need separate 'type out this file' and 'append these files to make a new file', as many other systems have, when one tool can be designed to do both jobs, and simply, too.

1
8

I’ve frequently used cat > somefile.txt to create a file quickly, and most other long-time UNIX users have as well. It was quicker and more convenient than vi over a slow connection, and much more so than a line editor (as long as I didn’t make any typoes).

2
  • 3
    This is the answer I was going to give. copy con somefile.txt does the same thing on dos/windows and I've used both for decades. If you want to add a couple of lines to the end of a '.gitignore', would you rather fire up an editor, navigate to the bottom, type in your two lines, save the file, and exit the editor, or would you just type cat >> .gitignore, type in your lines, and hit CTRL+D? Mar 16, 2023 at 20:41
  • @JasonGoemaat Ctrl-Z on Windows!
    – Davislor
    Mar 16, 2023 at 20:59
4

I use cat without arguments all the time, for exactly the purpose it accomplishes.

For example, I edit some unrelated file with joe editor (put your other favorite editor here, this is mine) and I need to paste some stuff in it which I want to be included verbatim.

If I just pasted text I've selected elsewhere (via shift-ins in xterm), all kinds of unwanted things would happen (it would auto-wrap text at right margin, it might auto indent stuff after newlines, it might do semantic things with spaces after colons, it might interpret ASCII sequences with codes less than 32 as control characters which would invoke editor commands instead of being preserved in editor window, etc)

If I however say ctrl-k r !cat (meaning: "run program cat and read its output into current editor buffer"), I can then paste anything without worrying, terminate with EOF (via ctrl-d) and be safe in knowing data is imported into editor buffer verbatim.


Regarding the historical uses, all those tools were made by default to read from stdin pipe and write to stdout pipe. To produce the warning when stdin refers to the terminal, you need to know that -- and isatty(3) libc call needed to do that. And I'm not sure if isatty was standardized before SUSv3, or even existing before Version 7. But some historian should check exact dates...

So on the "Why didn't it throw an error when stdin is a terminal from the beginning" - because:

  • functionality to determine that might not have existed when they were written
  • it requires extra code, and extra features do not drop from the sky, but need someone to invest effort and time to implement them
  • it is extra work and extra resources just to make tool be less capable and more bloated. Relatedly, in those days, there were no expectations that computer users need to be nannied. In fact, it was expected that user should be given enough rope to hang themselves (or shoot themselves in the foot), especially if that saved on resources.
  • after that, it's compatibility. Even if nowadays there are expectations that machines must be made idiot-proof so even users with absolutely no knowledge what they are doing should be protected from themselves; trying to break that compatibility for existing tools (especially ones as popular as cat(1)!) is a recipe for disaster.
1
3

@user24811 already covered the background, so just an addendum - displaying help and quitting breaks the pipeline model. I do not want the remains of a "helpful message" on file after it has been squeezed through my pipeline, awk'ed, digested and all.

stdin is perfect the way it is, and your modern funky terminal, after all, is just a file descriptor if viewed from "the other end". Treating it any different would rob it of that very design foundation.

cat is just a way to initiate i/o. When 300bd terminals (or, modem lines) were state of the art, the local line editing capabilities of the terminal were so much faster than the tedious redraw routines taking place while you watched the cursor toggle through your line, that cat was quite often used as the input editor of choice for small files.

And of course, there's an xkcd...

4
  • 3
    Presumably the 'helpful message' would be on stderr, not stdout - I believe this is why stderr was invented. Anyone know when stderr (i.e., fd #2) appeared? I suppose soon after pipes!
    – dave
    Mar 15, 2023 at 12:42
  • perfectly true, unfortunately there are way too many tools that don't bother differentiating between stderr and stdout, they'll just blurb something ("echo oops") and exit. Mar 15, 2023 at 12:46
  • 1
    Even if cat (or any other program) complained about its stdin being connected a terminal, it would still work when reading input from a pipe.
    – ilkkachu
    Mar 15, 2023 at 19:26
  • By design, a terminal is a file descriptor. There really is no point in complaining about a terminal being used as starting point for a pipe. We did make use of that bak then, and since the invention of cutbuffers, are doing so even more. Mar 19, 2023 at 16:22
3

There are use-cases where it makes sense.

  • Imagine you want to type or cut&paste text and save it to a file, without starting an editor. A simple cat > file.txt can run in a terminal and you are free to type or cut&paste stuff in it.
  • Imagine you have a shell script which uses cat deeply within some function or in some non-trivial logic; maybe it supports especially the use-case of being run with either a file or STDIN, and this behaviour (of the script) may be implemented by having a cat $FILE step somewhere, with $FILE being allowed to be empty. If cat were to change behaviour depending on whether $FILE is there or not, this would be confusing or rather very error-prone.

But most importantly, without really counting, the majority of the low-level, small Unix'ish CLI tools I use all day, every day, behaves this way. Very few of them, if at all, change their behaviour when started in an interactive shell, at all. Most commands you'd regularly use in a pipeline, be it cut, sort, wc, sed, awk or whatever it may be, behave exactly like cat - they seemingly "hang", waiting for user input, with no prompt and no information that they are doing it.

2

Unix was designed on systems very different from the Unix systems of today. On a typical system today, it's possible to have multiple processes running simultaneously, and even when time slicing is required, switching among processes takes a small fraction of a millisecond. On the systems for which Unix was designed, however, every task switch would take a substantial fraction of a second as current process memory would have to be written out to disk, and memory for the next process would need to be read from disk. This lead to two design goals which would be nearly relevant today, but were critical in the 1970s:

  1. The number of task switches must be minimized.

  2. The number of occasions when a program's output would be visibly affected by the receipt of new input from the user must be minimized.

On a system which, when waiting for user input, would generally able to react to it within 100ms, it is very useful to have a user interface which is based around prompts. If Unix were intended as a single-user system, having it indicate when input is expected, and of what type, would have made things much more convenient and less confusing. Unix was not, however, a single-user system, and if multiple users simultaneously did something that would take 200ms to complete, that could cause even a simple action that should generate an instant response to take over a second. If completing a line of input would usually cause an immediate re-prompt, but would sometimes not do anything for a second or two, that would greatly impede users' input to use the system "fluidly".

Many aspects of Unix that are treated as sacred were designed to satisfy constraints that haven't been relevant for decades. In many cases, it's not really possible to understand why things are as they are without understanding the constraints, but understanding the constraints will make the reasons for things obvious.

6
  • One constraint was that terminal access was often over a 1200 baud modem or even something slower. You did not want any extra output since that took time to display. Hence the lack of any prompt in ed.
    – doneal24
    Mar 28, 2023 at 14:59
  • @doneal24: Prompts of 1 or 2 bytes would not present any kind of performance issue even at 300 baud if they indicated when the system was ready for more data. The issue is that while Unix systems that received a line of input would immediately be ready to buffer another line of input, it would take them awhile to get ready to show the next line.
    – supercat
    Mar 28, 2023 at 17:52
  • Would this change if you were on a system that echoed characters as they were typed?
    – doneal24
    Mar 28, 2023 at 17:59
  • @doneal24: BTW, I've long thought it unfortunate that ASCII didn't define codes to indicate readiness/unreadiness to receive data which, rather than starting/stopping the tape reader, would raise/lower a visible flag (or, for later units, turn a light on and off), along with a code to ask the far end to report readiness status. Having a terminal controller send such a code, and having a terminal indicate when it was received, would allow someone who was entering data to receive confirmation that the far end buffer hadn't gotten full.
    – supercat
    Mar 28, 2023 at 18:02
  • @doneal24: At 110 baud, it might be noticeable. At 300 baud or faster, unless one is continuously typing 30 characters per second, a two-character prompt would increase the time to process a return keystroke from 66ms to 133ms. Readily observable, but not objectionable. At 1200 baud or faster, the time would fall to "barely observable".
    – supercat
    Mar 28, 2023 at 18:06
1

Another approach often used in scripts is variable pipelining, e.g.

someTool --args | $filter > "$outfile"

here the filter=cat allows to save a plaintext file, and a filter="gzip -9" allows to compress it well.

1
  • 4
    That would still work even if cat (or gzip) complained about its stdin being connected to a terminal.
    – ilkkachu
    Mar 15, 2023 at 19:27
1

There are lots of use-cases where stdin is a TTY but stdout isn't, as a way to get keyboard or copy/paste input into other things, like cat >> file, as other answers mention. I've also seen cat > /dev/null used as a way to get typed text onto the screen to show another human sitting nearby something that involves special characters and syntax which would be hard to describe in words.

If you were going to put training wheels on cat, you might consider if (argc <= 1 && isatty(0) && isatty(1)) { warn(); exit(1); } (maybe without the exit part), with the warning on stderr including the fact that control-C will get out as always. (Unlike vi, the standard means of quitting work just fine.)

Perhaps also check that stdin and stdout are the same TTY (same device major/minor), to avoid breaking a complicated redirect that reads keyboard input from one terminal and outputs it in another. Obviously much simpler to just not do this, especially since cat historically didn't. The worst-case scenario is that someone takes a minute to realize they should hit control-C, so the danger level is pretty low.


I have used cat with stdin and stdout both connected to the terminal to see how control-D works to "submit" a line from line editing on a "cooked" TTY without pressing enter, i.e. when a read(2) system call returns. (It's even more useful to use strace cat > /dev/null for that, though.)

This is relevant when messing around with toy programs in assembly or C that use read directly, since cat is a known-good program that also just uses read without caring about newlines, and makes a write system call after every read without extra output buffering to some block or buffer size. (At least the GNU implementation works that way.) e.g. to demonstrate that it's not fully safe to assume the last character of a read is a newline, even with a large buffer size and assuming you're reading from an interactive terminal.

Of course, if cat on its own didn't work, I'd just have run cat - or cat /dev/stdin which I assume would still work perfectly fine; this obscure use-case doesn't need no-args support. (Or strace wc.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .