If "programs ought to do one thing and do it well", then why aren't they just library functions in a scripting language? Scripting languages can probably do the same things (if given minor syntax improvements, see Xonsh) in one line as well, and scale to have more complicated logic if needed. I still don't see the beauty.
Why would having them just be library functions make it better? (I don’t disagree, just curious what you’re thinking.) The actual differences between pipeable programs and library functions might be hard to pin down concretely. Btw do you use unix & pipes a lot, or not much?
Here are a few thoughts, maybe helpful or maybe not. Part of the beauty of pipes and simple lego-brick programs is the complete integration with the operating system, and more specifically the file system. Being able not just to write your state to a file, but furthermore having that be the default mode of operation is pretty powerful, especially combined with special files. Writing to a file in a scripting language that’s not a shell is usually easy, but not the default, and would be cumbersome to do at every step. Part of do one thing well is to keep the shell DRY - don’t add sorting or searching or filtering functionality to another program if you can already use grep or sort or sed. Done right, this helps people stay fluent with a small number of tools. Another implicit benefit of the unix philosophy that wasn’t mentioned here, but does go hand-in-hand with pipes and small-good tools, is the idea to process and work with human readable text files.
One way to look at unix beauty is to do some command line data analysis. I like the general philosophy of data exploring with piped commands for one-off and small projects. Sometimes more focused tools are needed, but most of the time perhaps you can get away with unix tools. https://en.wikibooks.org/wiki/Ad_Hoc_Data_Analysis_From_The_...
That's because this is the way they thought back then. You can trivially pass json data between processes and make a coherent structured system. Nobody does it though.
Sure, but I think the bigger problem is complete systems. Look at a usual Linux box. It has a bunch of files, all with their custom syntax, and you parse them with error-prone shell commands. If everything used json, everyone's lives would've been so much better.
Independent programs using json is a step in the right direction though.
> …then why aren't they just library functions in a scripting language?
I’d say they basically are. What’s the difference between Python’s sort and (let’s say) Bash’s sort? In Python I call sort to order a data structure like a list. In Bash I do the same, but on a different data structure. The crux of the matter is probably buried in semantics.
> I still don't see the beauty.
What I like about shell and pipelines is that they allow me to easily edit and play with text. I don’t need to think about opening nor closing files, I don’t need to think how to best represent the data I’m working with. In minutes I can get the information I need and I can move on.
> If "programs ought to do one thing and do it well", then why aren't they just library functions in a scripting language?
In many cases, the command line tool is a wrapper for an awesome library, and the awesome library has bindings for multiple scripting languages. So in many cases, there's a command, and a library and yeah, you can import the library into your favorite scripting language (or often compiled language, too). In the end, you could argue that all languages are just an abstraction for system calls anyway.
So why the love for the Unix shell? One line is faster for the user than 23 lines. It's the same reason we use compiled languages instead of assembly most of the time. The same applies to using python of javascript instead of C or rust. Less lines of code. Bigger abstractions. Problem is solved and the work is done faster.
> I still don't see the beauty.
The whole "collection of small programs that do one thing well" thing is just one way to do it, and there are plenty of examples of programs that are super successful that don't heed that advice in any way at all. In the end, Unix has held up for a remarkably long time... seems like there should be a better way to do it, but it's hard to argue with Unix success in the wild.
You can think of them as library functions, but they all have the same signature: a pair of (byte[]) streams for input and output (plus another for errors), and then `args: string[]` and `options: map<string, string>`. If you're a human that's only really interested in consuming text, this isn't a completely inappropriate interface, and there is some beauty in just how expressive it can be.
Or perhaps, why not both? Imagine a monorepo where the same source code that produces the "ls" output also has a library function that returns the same as structs. Perhaps ls is a bad example, but it would be quite neat if the command line and programming context could act on semantically similar procedures.
The Rust effort to implement coreutils anew could possibly pick this up and make something really useful here.
Actually, shell/unix pipes bring to the table a bunch of features: a convenient syntax to connect processes, I/O buffering (usually done by the programs themselves but still), and concurrency.
Languages are an (often) unique combination of (often) non-unique features. That's why shell scripting is a thing.