Favourite 'Nixisms

Introduction

I started this list in 2018 as a reminder of the Unix ideas that kept surviving changes in distributions, editors, languages, and hardware. The specific tools change; the useful part is usually the underlying constraint or convention.

Dotfiles

small files, large consequences.

Dotfiles are ordinary files whose leading period makes them hidden by convention in many Unix tools. They are commonly used for per-user configuration.

The durable lesson is not a particular dotfile manager. It is that configuration should be reproducible, reviewable, and easy to move between machines without silently mixing secrets into a public repository.

GNU Stow remains a useful example because it treats configuration as a set of independent trees and assembles them with symbolic links. Other tools can solve the same problem with different tradeoffs.

My own preference has moved toward project-scoped environments and explicit configuration rather than assuming every tool belongs globally in $HOME.

Version and Environment Management

keep projects stable without freezing the whole machine.

Language and tool versions frequently differ between projects. Version managers and environment shims put a small dispatch layer in front of the normal command path so a project can select the version it expects.

Tools such as pyenv, rbenv, and goenv popularized that pattern. These days I generally prefer a single project-oriented tool such as mise when it can replace several language-specific managers.

The principle matters more than the implementation: the system package set, user toolchain, and project toolchain are different scopes. Conflating them makes upgrades and reproducibility harder.

Linux Namespaces and Containers

isolate views of the kernel, then compose the boundaries.

Linux namespaces isolate particular classes of kernel resources: processes, mounts, networks, users, hostnames, and related views. They are more precise than saying that namespaces isolate “everything.”

A chroot changes the apparent filesystem root for a process. A container runtime typically combines several mechanisms—namespaces, cgroups, capabilities, filesystem isolation, and often seccomp or other policy—to build a stronger execution boundary.

That distinction matters for security. Containers share the host kernel; they are not simply lightweight virtual machines, and their isolation properties come from the combined configuration rather than from one primitive.

Namespaces have been especially useful to me for repeatable build environments, cross-platform testing, and self-hosted services. They also helped normalize the idea that infrastructure can be assembled from small, replaceable execution units.

One historical example in the original version of this article was RancherOS, which attempted to run much of the host as containers. RancherOS 1.x is no longer actively maintained, so it is better treated as an interesting design experiment than a current recommendation.

Vim and Modal Editing

navigation is an interface, not just cursor movement.

I still like the Vim model because editing modes make navigation, selection, and transformation composable. Neovim is my usual implementation, but the durable idea is modal editing rather than any specific plugin list.

Plugin recommendations age quickly. A better long-term investment is learning the motions, text objects, registers, macros, and how the editor interacts with language tooling. Once those are comfortable, changing completion or fuzzy-find plugins is relatively cheap.

Shells, Pipes, and Multiplexing

small tools become a system when their boundaries compose.

The Unix shell is valuable because it exposes a common composition model: processes consume streams, produce streams, return status codes, and can be connected with pipes and redirection.

Commands such as cut, cat, sed, awk, sort, uniq, find, xargs, curl, and jq remain useful because they do one bounded job and can be combined without needing a new application for every workflow.

Job control adds another layer:

  1. suspend a foreground process with Ctrl-Z
  2. continue it in the background when appropriate
  3. return with fg or manage multiple jobs explicitly

Terminal multiplexers such as tmux or zellij extend the same idea to entire shell sessions. Detaching a session from one terminal and reattaching elsewhere is particularly useful over SSH or other intermittent remote connections.

This is also the design rule I increasingly want from my own tools: small CLI programs, useful standard streams, predictable exit codes, and documentation that does not require a GUI to understand the contract.

Files, File Descriptors, and IPC

a lot of Unix becomes simpler when interfaces look alike.

“Everything is a file” is a useful slogan, but not literally true. A more accurate version is that Unix exposes many resources through filesystem paths or file descriptors, which lets the same small set of operations compose across files, pipes, sockets, devices, and other kernel objects.

/proc and /sys expose process, kernel, and device information through virtual filesystems. FIFOs provide named pipe endpoints. Sockets and shared-memory objects use different semantics but still participate in a small, regular set of system interfaces.

The power is not that every object is identical. It is that interfaces are regular enough to reuse tools and mental models.

Open Source, C, and Security

open source makes inspection possible; it does not make bugs disappear.

Open source, C, and Linux shaped how I learned software. That history still matters to me, but my security view has changed since the first version of this article.

C provides direct control over memory and remains appropriate in important systems domains. That same control also creates a class of memory-safety risks that cannot be dismissed as a matter of programmer skill alone. Buffer boundaries, lifetime errors, integer mistakes, unsafe dependencies, and complex control flow interact in ways that make prevention difficult at scale.

Modern secure-development guidance increasingly recommends memory-safe languages for new code where feasible, while using compiler hardening, isolation, fuzzing, sanitizers, and staged migration for existing C and C++ systems.

That does not make C “bad.” It changes the engineering burden. Choosing a memory-unsafe language means accepting additional verification and containment work rather than assuming expertise removes the risk.

Downsides and Tradeoffs

Unix-like systems are powerful partly because they permit choice, and choice has costs:

  • Distribution and packaging fragmentation: Flatpak, Snap, containers, and language package managers reduce some problems, but they do not eliminate the differences between distributions or deployment targets.
  • Configuration fragmentation: highly personalized environments can be productive for one person and opaque to everyone else. Reproducible config, project-local tooling, and documented defaults help.
  • Container complexity: containers isolate and package workloads; they do not automatically solve configuration, security, persistence, networking, or operations.
  • Filesystem conventions: the traditional hierarchy is powerful but can be unintuitive. Alternative layouts such as GoboLinux and declarative systems such as NixOS show that other models are possible.
  • Learning curve: shells, modal editors, permissions, services, networking, and composable CLI tools reward depth, but the initial cost is real.

What Still Holds Up

The tools in my setup have changed substantially since 2018. The parts I keep returning to have not:

  • prefer small interfaces that compose
  • separate configuration and state by scope
  • make environments reproducible
  • treat process and privilege boundaries explicitly
  • keep tooling inspectable from the command line
  • automate repeated work without hiding how it operates

Those are the ’nixisms worth preserving even when the particular command, distribution, editor, or container runtime changes.

References