Unix

Using the Classic Unix Spreadsheet (sc)

Published 7 Nov 2026. By Jakob Kastelic.

Here’s some notes on how to use the 1980s classic spreadsheet program sc, and why. We’ll start with the usage basics, then discuss the limitations and comment on the user experience.

Basic Usage

Install it from your favorite package manager (sudo apt install sc) or build the latest version yourself:

git clone https://github.com/n-t-roff/sc.git
cd sc
./configure
make
sudo make install

Launch sc and you’ll have a spreadsheet in milliseconds—likely faster than your finger can leave the Enter key.

Navigation. To move around, use the arrow cells or the hjkl keys familiar from Vim and similar programs. To quit, press q. To save, press P.

Text Entry. Free-form text can be inserted in the current cell using <` for left-aligned text, `> for right aligned, and \ for centered. To change alignment subsequently, navigate to the cell and press { or } to left or right align, and | to center.

Annotations. Instead of adding hidden notes to cells, in sc one adds links to other cells. (I tend to place these notes in the rightmost column, so they do not clutter the main data.) To add a link, press *a, navigate to the target cell, and press Enter twice. Press ** to jump from the original cell to the linked target cell. Simple!

Numerical Values. Press = and enter a number or a formula. Special functions start with @, for example entering @now will enter the current Unix time. To enter a specific date, use @dts(2026,11,30).

Cell Format. Press F to set the format string for the current cell. The manual details the format, but in practice I use this most commonly to make one cell (or a few cells) format a date/time value. I use the format string ^D%m/%d/%y, where ^D is the literal Ctrl-D character (ASCII 0x04, “end of transmission”).

Column Format. Press f and use the arrow keys or hjkl: left/right (or h/l) to make the column narrower or wider; up/down (or k/j) to increase or decrease the number of decimal places.

For more flexibility, press f followed by a digit: f0 is the default, f1 formats numbers in scientific notation like 1.00E+04, f2 is the same in engineering notation (10.00e+03 such that the exponential is a multiple of three), f3 and f4 format dates as 5 Nov 26 and 5 Nov 2026, resp. To define a custom numerical format, open spreadsheet in the text editor and enter a line like

format 5 = "^D%m/%d/%y"

Careful: if you want the date formatting to work correctly, the ^D must be the literal end-of-transmission character, not a caret followed by uppercase D.

Rows/Cols. To insert new rows or columns, type ir or ic respectively. To delete rows or columns, type dr and dc. As with most commands, you can precede it with a number; for example, 5ir inserts five rows. Careful when deleting multiple rows, there is no undo! Save the sheet first, before making large deletions.

Ranges. To operate on several cells at once, use the range commands that start with r. There are several range commands but I only use four:

Copy/Cut/Paste. Besides the range copy and move commands explained just above, it is also possible to operate on a single cell, or one or more rows or columns, as follows:

ObjectCopyDeletePaste
Cellyyxpp
Rowyrdrpr
Columnycdcpc

Marks and Jumping. When working on two parts of the spreadsheet that are far apart, for example when copying and pasting, or moving things around, it helps to give cells a temporary “mark”. To mark a cell, press m followed by any other lowercase letter, for example ma. To jump to a cell previously marked in this way, press backtick followed by the same lowercase letter.

To copy the marked cell, mark it, navigate somewhere else, and press c followed by the letter. Even though one can copy and paste a single cell using yy and pp as explained above, I got used to copying with ma and ca instead (Y is far away from fingers on QWERTY keyboards).

Type Ctrl-A to jump to cell A0.

Scrolling. To scroll the sheet down, press Ctrl-e followed by Ctrl-e (again). To scroll up, press Ctrl-y. To scroll such that the current cell becomes the top row, press z followed by Enter. To center the current cell, press zc.

Searching. To search for a particular string (case sensitive), type g" followed by the desired string. To repeat the search without re-typing the string, press n. To repeat the search in the backwards direction, type N.

Colors. There are a number of commands to set the color of cells. I do not remember what these are. Instead, I open the spreadsheet file in a text editor, and simply add the following self-explanatory lines:

set color
color 1 = @white;@black
color 2 = @black;@green
color A1:C4 2

Frames. Set a frame if you want one part of the spreadsheet to scroll independently of another. I use this to have the table header with column names always visible, even when scrolling through many pages of data rows. There are many commands for manipulating frames and as with colors, I do not remember any of them. Instead, open the spreadsheet in a text editor and add a line like

frame A0:N1000 B3:N1000

This sets the first three lines to be the fixed header, while the rest scrolls freely.

Function keys. To use shortcuts, the function keys (F1 through F12) can be given custom actions, such as motion commands or calling macros. For example:

fkey 4 = "endright"
fkey 11 = "merge \"|~/projects/config/scmacros/copy_above.sh\""

Macros. To convert a complex sequence of keystroke into a single keypress, define macros. These are simple shell scripts such as the copy_above.sh that I have mapped to F11: it moves one cell up and one cell right, copies the cell, then moves one cell down to paste it. To create your own, take a look at my macros, or, better yet, point your favorite AI at the cloned sc repo and tell it to write it for you.

Technically sc distinguishes “simple” macros, which are interpreted by sc itself, and advanced macros, which are shell filters. However, the advanced macros can do anything the simple ones do, so I never used the simple ones.

Config file. Common settings that you want applied by default to all spreadsheets can be placed in the ~/.scrc file. Rather than reading this file in addition to the spreadsheet file, sc will place the content of scrc into all newly created spreadsheets. In my scrc, you will find only the format 5 definition and a couple function key shortcuts.

Limitations

A key part of the sc experience lies in the features that are not present. Of course, you could add any of these with AI, or look at projects like sc-im but in this instance I choose to “embrace the constraint” instead, to see what I can learn from it.

No Sync. Instead of every change being reflected on the cloud, I sync the sheets I want to sync by adding them to a git repo. Works great since the file format is text-based and very simple.

No Multiple Sheets. I was used to grouping several related sheets in one file, but after sc-ing for a while, this makes no more sense to me than bundling several source code files in one. The file system works great for organizing files—why does it seem like people are suddenly afraid of using files? Why do Apple and Google go to such lengths to hide it from users?

Limited Conditional Formatting. I love making sheets automatically get colored based on data values but in retrospect it’s a huge distraction. Why not make the data speak for itself instead? (There are some commands that can change cell color, but I do not find it useful enough to learn.)

UI is not Discoverable. You cannot figure out how to do things, or even if something is possible, except by reading the manual and/or source code. The learning code feels steep but for a program I use every day, it totally does not matter. I have it all in finger memory and I’m glad there’s not “beginner friendly” menus, popups, AI chats slowing me down.

Limited External Copy/Paste. It’s nice to copy a table from a web page seamlessly into a spreadsheet but here you have to import it with a text editor, or Python script, or Claude. No CSV in/out either, without external tools. In other words, use the right tool for the right job. Does one program really have to do everything? For me, the key insight of Unix is the partitioning of tasks into separate small programs, even if sometimes at the cost of slight inconvenience.

No Undo/Redo. Instead, I keep history with git, and make sure to save before and after making edits. If I make a mistake, just quit and reopen the program: qn, Up, Enter. Four keystrokes and it’s undone. (Usually Ctrl-Z in Google Sheets takes longer to execute, amazingly.)

No Unicode. I live in ASCII.

Discussion

In light of these limitations, why bother with sc? Isn’t old software obsolete, by definition of “old”? I disagree. If a program worked well on ancient computers a long time ago, then on modern machines it will work even better. The sc user experience is just so fast it blew me away. There is simply no waiting; everything happens instantaneously: opening/closing the program, entering data, making edits.

Conversely, if a piece of software—indeed any technical project–is worth implementing, it’s worth doing it right so it can stand the test of time. Sure, some tech is necessarily disposable, if it interfaces with some frequently changing protocols, for example. But most problem domains allow decomposition into small tools, where the tool remains essentially unchanged over decades.

The user experience deserves particular focus in the earliest stages of design, to ensure that it does not irritate or slow down the advanced user. If a tool is supposed to be used frequently by most of its users, then most of its users will become “advanced users” and will not tolerate convoluted keystrokes or, worse, time-consuming mouse or touch navigation. And certainly there is no excuse for slow user interfaces—that ship sailed back when processor clock speeds exceeded a megahertz.

Android is a particularly nasty counterexample: in a misguided attempt to appease the “beginner”, everything is hidden behind seven layers of tiny hamburger menu icons. Essentially all web apps take several orders of magnitude too many clock cycles for any actions. The problem has become so bad and so pervasive that no one even notices anymore—everything is slow, slow slow. But programs such as sc can wake us up to realize: not everything has to suck.

I’m no longer convinced anybody knows anything about user interface. It’s clear some things are easier to use and some things are harder to use. That’s okay. But it’s also clear that people learn a lot.[1]


  1. From an interview with Peter J. Weinberger (Murray Hill, 8 September 1989), with Michael S. Mahoney interviewing. ↩
Incoherent Thoughts

The Minimum Working Example

Published 6 Nov 2026. By Jakob Kastelic.

When the rules of a game are simple, don’t keep inventing new rules to complicate things. In particular, at all levels an engineering project needs to be as simple as possible while still functioning as expected. Beyond that, leave all rules behind.

Indeed, there is only one rule: the thing has to work. Besides, the less time and effort spent in the implementation, the more there is left for interesting applications. In sum, always only make the minimum working example.

Electronics

Phase Noise in a 10 MHz Amplifier

Published 5 Nov 2026. By Jakob Kastelic.

These are some notes I’ve taken when creating a circuit that takes a 10 MHz sine-wave input and outputs several copies of the same, with a minimum of added phase noise or distortion. First, select a topology and simulate it. Then, build the circuit and measure the key specs: noise, crosstalk, distortion.

Basic Idea

The goal is simple, 10 MHz in, 10 MHz out. Many approaches have been explored:

Of these, I have chosen the version that I find easiest to explain to myself: an inverting buffer drives several non-inverting output drivers. This is shown in the image below.

A straightforward transient LTspice simulation (.tran 0 1.5u 0.5u 25p) shows that the output amplitude is 1Vrms. In other words, the circuit should work as shown.

In the simulation I used the spice model for the LMH6609 op-amp provided by Texas Instruments (original model by National Semiconductor). It works just fine in LTspice except for commenting out a comment. Apparently PSPICE supports the / symbol as comment in addition to the * used by LTspice.

Crosstalk and Pulling

A key spec on any multi-channel instrument is crosstalk: if a signal is injected in one output, does it show up in the other output?

Notice the V4 signal source in the LTspice schematic shown above. If we disable the main signal source V3 to AC 0 and enable V4 as AC 1, then an AC simulation (.ac dec 200 1k 1G) shows about 10μV of crosstalk at out1. Including a rough estimate for layout-dependent capacitive coupling between two outputs directly (e.g. 10fF at C7), this rises to 17μV, or about -95 dB.

Assuming the op-amp model is accurate, we can use this result to estimate the phase pulling between the two outputs. Assume one of the outputs is connected to an unterminated cable such that the full signal strength—all 1Vrms or +13dBm of it—comes back in quadrature. The peak amplitude is 2.82 × 17 μV = 48μV.

Adding a small phasor a at angle φ to a carrier A gives a resultant phase shift Δφ ≈ (a/A)·sin φ. The angle φ is set by the cable’s round-trip delay plus the coupling path’s fixed phase—and since the user can present any length, the worst case is φ = 90° (quadrature), where the interferer is pure phase: Δφ = 48 µV / 1.38 V = 35 µrad.

At 10MHz one period is 100ns spanning 2π rad, so Δt = Δφ/(2πf) = 34e-6 / (6.28e7) ≈ 0.55 ps. The reflection on Ch-2 will cause Ch-1 to move in phase by less than a picosecond. Pretty good!

Additive Phase Noise

There are many mechanisms that contribute to the phase noise at our outputs. Let us immediately ignore the noise of the 10 MHz source and instead focus on what adds to the source’s phase noise.

At large offsets away from carrier, the noise floor is set by op-amp voltage noise, resistor Johnson noise, and current noise. The same LTspice model from above (remember to disable V4 and re-enable V3!) can be used to calculate the voltage noise density at the output, by means of a .noise V(out1) V3 lin 401 9.9Meg 10.1Meg simulation directive. At 10 MHz, it reads 9.7 nV/rtHz. Relative to the 1 Vrms output amplitude, this is a phase noise floor of

L = 20 log10(9.7e-9 / 1.00) − 3.01 = −163 dBc/Hz.

Note the 3 dB subtracted, due to the fact that the noise decomposes into components in phase and in quadrature with the carrier phasor. The in-phase part wiggles the amplitude; the quadrature part wiggles the phase. For additive noise these are equal in power, so exactly half the noise power becomes phase modulation.

Convective Air Currents

Any air moving across the board, whether forced or natural convection, can cause the temperature of PCB components to fluctuate. Ferrite inductors, such as the 1.5 μH seen in our tank circuit, might have as much as 500 ppm/K of temperature coefficient. We can step the inductance value in the LTspice simulation to find about 0.45° of phase change due to 1% change in inductance.

Putting the numbers together, the phase sensitivity is 3.9e-4 rad/K, or, in time units, about 6.3 ps/K. If we estimate the inductors temperature noise at 1 Hz to be about 1.5 mK/rtHz, then the phase noise would be

L = 20 log10(3.9e-4 * 1.5e-3) - 3.01 = -127 dBc @ 1 Hz offset.

That’s a significant amount of phase noise, but easily fixed by reducing the air currents or increasing thermal insulation. The inductor temperature changes are heavily low-pass filtered by means of the part’s thermal inertia. In other words, above the thermal pole the response falls as f^{-2}, so lowering the corner buys 20 dB per decade of shift.

Microphonics

Any sound or mechanical vibration modulates the electrical characteristics of the circuit. Non-NP0 capacitors are particularly affected, so it’s important to verify there’s none in the critical signal path.

Inductors are also prone to microphonics, via magnetostrictive modulation of inductance. The datasheet will most likely not specify it, so instead we take a wild guess, say 1.5e-4 inductance change per “g” of acceleration.

Reusing the 0.45° per 1% coefficient from the previous section, the phase sensitivity is 1.2e-4 rad/g, or about 1.9 ps/g. A fan blade-pass tone at 120 Hz depositing 0.01 g peak gives a peak phase deviation of 1.2 μrad, and a sinusoidal modulation of peak deviation Δφ produces sidebands at

20 log10(1.2e-6 / 2) = -124 dBc.
Theology

Seeing God and the Pretentiousness of Purity

Published 5 Nov 2026. By Jakob Kastelic.

Disclaimer: I’m not a theologian, proceed at your own risk.

An astute reader of the Notes on Cassian noticed a wild claim of mine, that “being dead to the wold” means afterlife, when examining Cassian’s thoughts on divine contemplation and “seeing God”, in Conf. 1, Ch. XV. Let’s take a closer look.

I don’t think you can “see God”, no matter how “pure” or “dead to the world” you are. To be fair, Cassian only makes the converse claim: if, any carnal affections survive in you, then you cannot lastingly contemplate God. Am I going too far by assuming Cassian implied the reverse claim: you could lastingly contemplate God if only no carnal affections survive in you?

A parallel set of claims arises from his scriptural quote. Exodus states that if you see God, then you cannot live. Cassian elaborates: you cannot live to “this world and to earthly affection” (mundo huic affectibusque terrenis). The implied reverse statement in this case is, if you’re “dead to the world”, then you may be able to see God. Again, did Cassian intend this implication or am I reading into it too far?

There’s only two options, either he did mean it or he did not. If he did not mean it, then we could discuss whether carnal affections (carnalis affectus) prevent you from contemplating God “lastingly” (perpetuo), or from seeing God. Since I’m not at this point interested in studying perpetuity, i.e., eternity or afterlife, so let’s pass on to the other option: Cassian did mean the “reversed claim”: purify your heart and you’ll be able to see God now and forever.

Agents

Same Input, Different Outputs

Published 4 Nov 2026. By Jakob Kastelic.

Some time ago, models used to take a widely variable time to complete a task, ususally between ten seconds and ten minutes. This kept me glued to the screen, mostly just waiting for it to be done. Then I got the idea to tell Claude that when it’s done, it should print a big “DONE” banner so I can see it from the other side of the room. I ended up collecting several different versions of this banner as they came out of the model.

██████   ██████  ███    ██ ███████
██   ██ ██    ██ ████   ██ ██
██   ██ ██    ██ ██ ██  ██ █████
██   ██ ██    ██ ██  ██ ██ ██
██████   ██████  ██   ████ ███████
█████   ██████  ███    ██ ███████
██   ██ ██    ██ ██    ██ ██
██   ██ ██    ██ ██ ██  ██ █████
██   ██ ██    ██ ██  ██ ██ ██
█████   ██████  ██   ████ ███████
█████   ██████  ███    ██ ███████
█   ██ ██    ██ ██    ██ ██
█   ██ ██    ██ ██ ██  ██ █████
█   ██ ██    ██ ██  ██ ██ ██
████   ██████  ██   ████ ███████
█████   ██████  ███    ██ ███████
██   ██ ██    ██ ███    ██ ██
██   ██ ██    ██ ██ ██  ██ █████
██   ██ ██    ██ ██  ██ ██ ██
█████   ██████  ██   ████ ███████
█████   ██████  ███    ██ ███████
██   ██ ██    ██ ██    ██ ██
██   ██ ██    ██ ██ ██  ██ █████
██   ██ ██    ██ ██  ██ ██ ██
█████   ██████  ██   ████ ███████
█████    ██████   █    █   █████
█    █  █      █  ██   █   █
█    █  █      █  █ █  █   █████
█    █  █      █  █ █  █   █████
█    █  █      █  █  █ █   █
█████    ██████   █    █   █████
█████    ██████   █    █   █████
█    █  █      █  ██   █   █
█    █  █      █  █ █  █   █████
█    █  █      █  █  █ █   █
█    █  █      █  █   ██   █
█████    ██████   █    █   █████
█████   █████  █   █ █████
█   █  █   █  ██  █ █
█   █  █   █  █ █ █ ████
█   █  █   █  █  ██ █
█████   █████  █   █ █████
█████   ████  █   █ █████
█   █  █    █ ██  █ █
█   █  █    █ █ █ █ ███
█   █  █    █ █  ██ █
█████   ████  █   █ █████
████  ████  █   █ █████
█   █ █   █ ██  █ █
█   █ █   █ █ █ █ ███
█   █ █   █ █  ██ █
████  ████  █   █ █████
████  ████  █   █ █████
█  █  █  █  ██  █ █
█  █  █  █  █ █ █ ███
█  █  █  █  █  ██ █
████  ████  █   █ █████
███  ███  ██ █ █ ████
█ █  █ █  █ █ ██ █
█ █  █ █  █ █ ██ ███
█ █  █ █  █ █ █ █ █
███  ███  █ █ █ █ ████