TL;DR: A short essay on the elegance and practicality of composable interfaces. AKA 'why I prefer Houdini to Maya'.

The other day I was watching the latest video by Entagma, titled Nerd Rant: Are we switching to Linux?.
At some point Manuel - in a very tranchant way - mentions that the way terminals work is mostly due to leftovers from the past, and that (compared to GUIs) text based interfaces are not ideal from a design standpoint.

I found that particularly surprising, especially since Entagma has historically done a lot of great Houdini content, and Houdini has deep roots in the UNIX philosophy, which has been a fundamental drive in the design of most terminal applications we use today.

At some point, I used to share a similar opinion to Manuel's regarding command line tools and terminals. However, with time I radically changed my perspective, so I here I have collected my thoughts on how that happened, and why now I believe that for certain tasks, terminal-based workflows are terribly elegant and efficient.

Chapter 1: Houdini was inspired by UNIX

First of all, let's quickly get some important context out of the way. The way Houdini networks are organized is very much inspired by the UNIX philosophy (since being a "certified UNIX system" requires a license, people tend to refer to UNIX-like systems, aka *nix systems).

A node path like /obj/my_geo/some_node is effectively identical to the way in which Linux (and most *nix systems) represent a file on your filesystem.

If you ever needed to refer to a node using syntax like ../some_parent_node, that's identical to the conventions used in Unix, where .. represents the idea of going to a parent directory.

On top of that, there's another important inspiration from the Unix philosophy: focusing on designing single tools that perform a single task well, and are meant to be composed together. This is the philosophy that underpins the design of all good command line tools.

In Houdini, those tools are nodes, which are further divided into contexts depending on the kind of problems they are more suited to solve (geometry problems? Surface OPerators. 2d problems? Compositing OPerators. Animation problems? CHannel OPerators. et cetera..).

Nodes have a standard way to send data to other nodes thanks to ther inputs and outputs they expose. This protocol makes it possible to design nodes in isolation, only focusing on the type of input that they can work with, and on the output they generate.

An example node network in Houdini.

Let's also not forget that Houdini still ships a dedicated HScript command called unix (at this point I'd say it's more of an homage than anything else!) : https://www.sidefx.com/docs/houdini/commands/unix.

Chapter 2: GUIs are not superior in all cases

Just like Houdini nodes, Command Line tools also have an agreed protocol on how they should send and receive data. This protocol involves mostly:

  • Reading data from stdin (file descriptor 0)
  • Writing results to stdout (file descriptor 1)
  • Writing logging and errors to stderr (file descriptor 2)

This means that well-written CLI applications are much more portable and flexible than well-written GUI applications.

Let's make a simple example.

Suppose that I want to find a Markdown file that I edited just a few moments ago. I vaguely remember that the file lives somewhere in my ~/Documents directory.

I can open up a terminal, and using the find command, formalize those 2 requirements:

  • I modified the file recently, say "less than 10 minutes ago"
    • This becomes the -mmin -10 flag, where the first m stands for modified
  • The file is a Markdown file
    • This becomes the -name '*.md' flag
      • The * stands for "anything" (it's called a "wildcard"), so it will search any file ending with .md

The generic syntax for find is the following: find <some-directory> <some-flags>.

So in this case we'll start with this:

$ find ~/Documents -mmin -10 -iname '*.md'
~/Documents/blog/valerioviperino.me/content/blog/2026-05-02-a-small-essay-on-terminal-interfaces.md

The $ I added before find is just a convention that people use to represent commands that you type into a shell (some folks will use > instead, but I find > more confusing since > is also a valid terminal command!). You don't need to actually type the $. It just makes it easier to distinguish which commands you should run, and which outputs they generated.

As you can see, find found one file that matches my query, and it printed it out the full path:

~/Documents/blog/valerioviperino.me/content/blog/2026-05-02-a-small-essay-on-terminal-interfaces.md

Now, let's say I just want to read the first 10 lines to see if it's really the article that I think it is.

find doesn't necessarily offer functonality for that, but it doesn't matter. After all, the design of find is to do one thing: searching the filesystem for specific files. As long as I can find some command that lets me read the content of files, I can probably use the output from find for my own needs.

One command that allows you to print the first lines of a file is the head command. To read ten lines, I would write something like head <some-file> -n 10.

So now I need to find a way to pass <some-file> from being the output of find to being one of the flags of head. One first, easy way is to do it via xargs. xargs allows me to read the output of a command and execute another command for each line in that output.

The syntax for doing that is the following:

xargs -I <some-placeholder> <your-command>

For example, if I wrote xargs -I {} head {} -n 10, we could mentally split it like this:

  • xargs -I {} this is the part where I tell xargs to look for the string {}
    • -I is what we call "a flag". The flag name is -I and the value is {}.
    • Here I decided that {} will be my placeholder. Note that there's nothing special about {}, I could have used other text too. However, using {} is a bit of convention and it's shorter to type!
  • head {} -n 10 is the command I want to run

Where xargs will see the {} in the command I gave it, it will replace it with the content of the line that we read.

For example, say that we have a file called my_file.txt with just 2 lines:

Hello
World

Let's also meet the cat command, a very simple command that reads the content of a file and displays it, e.g.:

$ cat my-file.txt
Hello
World

If we wanted to add a prefix and a suffix to each line in this file, we would run something like this:

$ cat my-file.txt | xargs -I {} echo aaa-{}-bbb
aaa-hello-bbb
aaa-world-bbb

This reads as: For each line that you'll receive in input, run the echo aaa-{}-bbb command and pass the current line from the input anywhere we used the {} placeholder. echo is another simple command that just prints to stdout whatever you give to it as input!

Okay, so hopefully this command (xargs -I {} head {} -n 10) makes more sense now.

We're only missing one last detail now: how to send the output of one command to be the input of another command. We only need to type one character, the pipe: |.

The syntax is like this:

<some-command> | <some-other-command>

So, putting everything together, we have this command:

$ find ~/Documents -mmin -10 -iname '*.md' | xargs -I {} head {} -n 10
+++
title = "A short essay on UNIX, command lines and text interfaces"
path = "/a-short-essay-on-UNIX-command-lines-and-text-interfaces"
date = "2026-05-02 10:00:00"

[taxonomies]
tags = ["reflections", "shell", "terminal", "gnu/linux"]
+++

As expected, it printed the first 10 lines of this blog post.

For more advanced users, find also has a built-in way to execute commands for each find that it finds, and it's via the -exec command. So you could have written the previous command also like this:

find . -mmin -10 -iname '*.md' -exec head {} -n 10 \;

The syntax is similar, where {} is the placeholder represent the current file being processed. Note though that find requires you to manually close each command using the ; (end of command), which requires you to write \; (let's not worry about this for now, but you need to "escape" the ; character using \: this basically tells your shell that ; is meant to be used by find when running their command, not by your shell when running the find command. You can read more about this in the manual page: man find).

For beginners, I think xargs generally tends to be a bit more intuitive to understand. But -exec can be faster and more powerful once you start reading the man page of find in detail.

Now let's say we wanted to see how many words my article has so far. Generally speaking, I can use the wc (word count) command and then pass the -w flag.

The full syntax would be:

wc -w <some-file>

Again, we just need to compose the commands in the right order, et voilá:

$ find ~/dev/blog -mmin -10 -iname '*.md' | xargs -I {} wc -w {}
2817 ~/Documents/blog/valerioviperino.me/content/blog/2026-05-02-a-small-essay-on-terminal-interfaces.md
# Alternatively, using `-exec`:
$ find ~/Documents -mmin -10 -iname '*.md' -exec wc -w {} \;
2817 ~/Documents/blog/valerioviperino.me/content/blog/2026-05-02-a-small-essay-on-terminal-interfaces.md

Neither find, nor wc or xargs were written in mind specifically to work with each other's output. Instead, they all speak the same lingua franca of the terminal, which means they can be assembled together just like legos (or IKEA modules, depending on your interests!).

And of course, you can keep piping the output of one command into another command as long as you want.

For example, here I'm looking in the /usr/share/dict/linux.words file for words starting in craft (the ^ character means: match at the beginning of the word), then I'm sorting them alphabetically and getting the first 10.

$ grep -E '^craft' /usr/share/dict/linux.words | sort | head -n 5
craft
crafted
crafter
craftier
craftiest

What about words that end in 'craft' ? I can use the $ sign to ask for that instead. Here you go:

$ grep 'craft$' /usr/share/dict/linux.words | sort | head -n 5
adcraft
aerocraft
aircraft
anti-aircraft
antiaircraft

TIP: If you ever find a command online that seems "archaic", try using https://explainshell.com to get you to explain it!

Now try to imagine how you would have achieved the same results using a GUI application..

Maybe you will find a specialized app that lets you search files with a lot of options. But then what if you need to count the words of those files and that app doesn't have that feature? GUI apps are nice for vertical integrations on a specific domain, but they're terribly inflexible as horizontal tools because there aren't many standardized mechanisms for them to exchange data needed by the user.

Chapter 3: Having different goals doesn't make a tool 'archaic'

While not immediately simple to grasp, I'm a strong believer that terminal applications operated via text commands allow us (even in 2026!) to achieve a few important design goals that GUIs generally fail to achieve: composability, flexibility and consistency.

CLIs are not just relics of the past made by people that didn't read "good design" books, they just use mental models that optimize for different design goals compared to GUIs.

Authors of command line applications don't need to worry about doing ad-hoc integrations with specific other apps, they just need to implement support for (at least) 3 important features:

  • Reading from standard in (stdin)
  • Writing to standard out (stdout)
  • Writing to standard error (stderr)

Users of command line applications also only need to understand how to operate with regard to these 3 features.

Of course, complex applications should also support reading from/writing to files (and many other kinds of resources, including ones not made of text), but the fundamental mental model and the underlying protocol used will not be altered by adding this extra functionality on top.

There's a lot Command Line Interface Guidelines available on: https://clig.dev. While not being really "official" in any way, they offer good suggestions for CLI authors.

Why text?

One interesting side effect of using text commands as a primary design medium for software is that it lends itself very well to two other goals:

  • Sharing efficiency
  • Ease of reproducibility

Sharing Efficiency

Most online software services allow you to send text to other people (while more complex media types might not always be supported).

Assuming shared trust, if you need to debug an issue with somebody, you can send them a short list of commands to run without having the need to send extra screen recordings or to have screen-sharing session to (synchronously) look at the exact state of things on their computer. In certain cases, a single (well written) one-liner (a command made of only one line) is much more efficient (in terms of time and disk space!) than having to create multiple screenshots to document how to perform the same change. In my life as a software engineer working in the VFX and CG industry I had many occasions where just being able to probe the result of 2/3 commands saved myself (and the person on the other side) much longer debugging sessions, with the extra bonus of being a fully asynchronous flow (mostly because I can just ssh on a target system and run commands in the background without the need to take control over the pointer and the windows of the current user). In my experience, software that was designed to be debuggable via command line tends to be easier to mantain in general. Making and proving hypothesis on the state of the system tends to be a quicker operation. On the other side, software that can only be debugged by clicking specific buttons tends to be more tedious and laborious to support and debug.

Ease of Reproducibility

In terms of reproducibility, I feel text is a great resource to allow software to keep the instructions required to operate it as deterministic and as "scientific" as possible. At the end of the day, a terminal (and a shell) are one of the most direct ways to interface with the OS without the need to be a fully fledged programmer. Due to their simpler architecture, the amount of prerequisites required by terminal applications is considerably lower than GUI applications. To me, writing a good, well tested command means focusing on what's essential to the task at hand, a little bit like writing an equation that accurately models the essence of some real world behaviour. This is why a good command should list all prerequisites (and assumptions) needed for its execution.

While it is possible to do the same prep work for screenshots and screen recordings too, people tend to get lazy when it comes to writing this kind of precise documentation required to operate GUIs. So while writing 10 lines of explicit code is generally not a lot of work (just merely speaking of time involved in the preparation), creating even 4 or 5 different annotated screenshots tends to be a bit more cumbersome. Not to mention that, to cater to the most wide audience possible, one should create screenshots for each different OS and ideally localize them too. You can see how the number of combinations grows quite quickly..

Epilogue

Now, I'm not saying that we should forego GUI apps altogether and go back to only write and use CLIs.. but I do think there's some clever design techniques that we can learn from CLIs instead of just treating them as the product of some barbaric pre-civilization culture that didn't yet have the right tools to express themselves properly. For me, writing down a well documented (and composable) command means cutting down the extra "fluff" of User Interfaces (important for usability, but not important to capture the essence of a problem) and improving the signal to noise ratio by a good chunk.

On a practical term, CLIs can reach a very wide audience with little effort. Both macOS and Linux have shells that support a shared set of commands. And now with the Linux subsystem in Windows, one can run identical commands in recent versions of Windows too. This means that being capable of writing a single (POSIX-compatible) command that does the trick well might end up saving you quite a lot of repetitive documentation work..