Bringing the Unix philosophy to the 21st century (2019)
blog.kellybrazil.com
blog.kellybrazil.com
Having JSON as an intermediate representation to work on instead is not going to save anyone - what we'd really need is for all tool versions/variants output on all platforms (all GNU/Linux distros, all the BSDs, all embedded Linux variants, all commercial UNICES, etc.) to be the same, all the time. That's not going to happen, so shell scripting is going to stay messy.
Also, for my INTERACTIVE shell, anyone can try to pry free-form, text-based and semi-structured output from my cold, dead hands. JSON or YAML output might be an acceptable compromise between being easy to parse and bearable for human consumption, but for my daily work, I would rather have my tools make it easy for me, the human part in the whole equation, and not some parsing logic that might not even (need to) exist. Shell scripting provides most of its value from the fact that since I'm in its repl-of-sorts all the time, I can translate that familiarity to scripts and executables effortlessly, and I would not want that going away. But I am rather certain it would, if we had JSON (or another form of more structured data interchange syntax) adopted as the "universal" interface between UNIX tools.
If I had to decide on a tool to replace shell script I would vote for tcl, since it maintains many of the advantages of the shell but provides a better handling of the programming aspect. Unfortunately these days it seems that you either use shell or some full-scale language like python.
Disclaimer: I am the author of the article and JC.
It's like I get it, the old way is ugly and not always easy to decipher but at least it's shorter and your chances of understanding it are better.
I've had both -- the classic piped chain of UNIX commands and various JSON-producing tools piped to `jq`. The former were still easier to work with.
https://ilya-sher.org/2018/09/10/jq-is-a-symptom/
My bias: I'm the author of Next Generation Shell.
It's syntax isn't 100% POSIX compatible so there is some new stuff to learn but it works with all the existing POSIX tools and is more readable than AWK and Perl but while also being terse enough to write one liners.
I considered python but Ruby’s easier chaining with map/filter/etc made it easier for me to use when writing just one line with it to transform some json.
https://github.com/kellyjonbrazil/jello
Other languages are superior in their handling of maps/arrays, but Python is just so damned popular now I thought it was a good choice to democratize JSON handling.
https://blog.kellybrazil.com/2020/03/25/jello-the-jq-alterna...
What I like to do is comments like:
/*
* Collates
* [
* {
* id: 4
* dept: 'oncology',
* name: 'Joe S.'
* }
* .
* .
* .
* ]
*
* into
*
* {
* 4: { // id
* 'Joe. S': { // name
* dept: oncology
* }
* .
* .
* .
* }
*/
( Then insert horrible one-liner that does the transformation. ) /*
* Collates
* [
...
Removes all the unneeded vertical space created by inserting the extra newlines and makes the entire thing more readable. Put two spaces in front of each line that will be part of the code block and then any extra needed spaces. Some text editors can format text in this manner automatically for you.The Unix command line is the Candy Crush of interfaces, giving us a little dopamine hit every time we solve a problem that didn't need to be solved.
More long term, I have high hopes of having an environment above the kernel that basically does what /proc and /sys and others do currently. Programs equivalent to those in /bin /usr/bin etc. would be just dynamically loadable modules or built into the basic tool set. No shell scripts anymore, no random filesystems with custom formats for everything, just a JITed VM that has a strong set of tools but anybody can extend it with either stuff written in some Clojure-like language, or something compiling to the VM or something native that has some kind of interface (FFI?) to be usable from the programs running on the VM (e.g. for cryptographic stuff or stuff that needs to be as efficient as possible). We would also need something like SSH but for structured data that would support a SHELL/ REPL-like workflow as a byproduct but really be meant for more or less high performance, efficient communication (e.g. useable even for large file copy operations and such). In the end, parts of this system could connect to a in kernel VM (BPF?) and execute there but we would interact with them using the nice, structured REPL.
This would be a huge undertaking but I can't really see, how we can radically improve the efficiency of work with the current systems. It seems, we are mostly just patching old approaches to do new tricks and to me, it seems to be falling apart. The complexity we impose upon us is crushing and I don't think all of it is necessary.
Whereas it's not nearly as easy to exhaust memory with line-based data processing nor to crash utilities that read line-by-line, e.g., sed. If lines are too long, I can chop them down to a reasonable size on some sentinel.
IMO, JSON, like Javascript, is web/browser centric. For someone who rarely uses a browser or Javascript and is comfortable with UNIX, e.g., yours truly, JSON is not particularly advantageous. For large data, line-based is more robust (and memory-efficient) than JSON, IME.
Better than JSON is netstrings or bencode.
No? The design of most JSON libraries assumes that, but there are perfectly good incremental JSON parsers out there[1–3]. It’s just that people don’t seem to have figured out a good API for not-completely-incremental parsing (please prove me wrong here!), but this applies equally to any structured data format as soon as you want to pull out pieces of data that are nested more than one level down.
The lack of length prefixes in JSON does indeed make a solid parser somewhat more difficult, but you get the ability to author and validate it manually instead. All in all a draw and not because of the incremental parsing thing.
(Tabular or otherwise homogeneous data is indeed reprsented wastefully, but unless the individual records are huge json+gzip is a perfectly serviceable “worse-is-better” solution, and its self-describing nature along with support for structured cell values can at times make for a better experience than TSV. And at other times not.)
[1] https://github.com/ICRAR/ijson
It comes with an example program called json_reformat which I have experimented with in the past.
However the "reformatters" I write using only shell utilities work just as well. YMMV.
I doubt your JSON reformatter was entirely correct (hey, we all must parse [X]HTML using regex from time to time), as you either have to pretend you can make sense of JSON using regular expressions (you can’t, no language or format supporting unbounded nesting can be regular) or write what’s essentially a standard JSON parser in shell with all the associated inefficiencies. My own needs are usually adequately served by the likes of jq -r ... | while IFS=$"\t" read -r ..., which feels much less hackish.
(Even correctly handling CSV with quoting, escaping, and embedded newlines in a UNIX pipeline without a purpose-built utility is surprisingly difficult—although not impossible, as CSV is a regular language.)
Not everyone suffers from that fetish, fortunately. With excessively complex JSON, I prefer to extract the specific data I want. The speed of json_reformat demonstrates how slow reformatting is when done "correctly".
Every user's needs are different. jq fails to meet mine.
It doesn't. I have personally written JSON parsing code that runs on embedded systems with less RAM than the size of the file.
I have too. I used shell utilities. :)
YAJL will also work.
It also was unbearably slow in a modern computer, to the point that it made debugging difficult.
Unix shells, for all their idiosynchronacies, tend to focus on small bits of functionality directly composed from common shell or system utilities. The complexities lie in outdated design, not in the language or form.
We could definitely do with an upgrade to the underlying tooling, I don't find anything otherwise lacking in shell scripting otherwise. I don't agree with the OP that we need a web readable data format - perhaps the jq syntax could be useful but the best part of shell scripting is it is approachable by humans - if it is not you are doing too much and should be writing your missile control program in python or c, or similar. I dont think it should be easier to produce functioning junk nor should it be a requirement to master a data syntax in order to print a formated date. Maybe you need a different date program if you need to do something special.
Maybe to you pluggable means an output object from a utility being the same input object as another utility? In this case, Unix isn't pluggable at all.
It is the only shell that ships by default on modern OSes that builds up on Xerox PARC ideas.
It is not only .NET, rather anything on the OS.
It also handles COM, DLLs, OLE Automation, WMI, pluggable filesystems.
With Powershell you can easily automate something like use the currently selected cell on an Excel document, and use it as input for something else, including another active application.
Something like that would be possible on UNIX shells with DBus like protocols, and ability to load shared objects into the shell, but on typical UNIX fashion everyone does their own thing and thus the whole experience remains fossilized.
Or Perl. That's what Perl was originally created for.
Or sed. If awk is too mainstream for you.
Or Python.
Or just write the ugly pipeline in your script, and promptly forget how it actually works. I've done that a lot.
After learning how to use ed in scripts, I've found it's actually easier to use it compared to sed because it effectively has random access through the input rather than going from beginning to end.
But both awk and Perl have the advantage of storing values in variables over sed or ed.
Being able to do something like
/some string/-2 d
Which deletes a line 2 lines above where some string matched is something that's trivial to do in ed, but takes a bit of work in sed.What i would like is an cli awk ide. (Rolls of the tongue right? )
Ideally i could write ... | awk-ide "scripts/thing.awk" | ...
If it exists, run it. Otherwise have it open an editor session that runs an awk program as i type it on some buffered data, shows the result, and have some hints on what syntax/variables are available.
while inotifywait --event modify,move_self,delete_self script.awk; do awk -f script.awk input.txt; done
in a tmux pane on the right with vim/emacs/whatever on the left?Note: You need to watch the move_self and delete_self events, since editors might use some file move/deletion dance instead of a direct modify to help prevent data loss in the event of a crash.
The industry has approximated this state of affairs by approximating a GNU+Linux monoculture. Doesn’t matter that BSD tar behaves differently if ~nobody uses it.
Plus there are a couple of POSIX like OSes for embedded deployment.
Unix Text Processing, Dougherty and O'Reilly, 1987 https://www.oreilly.com/openbook/utp/
Cherry pick chapters like those on the Shell and AWK, and avoid those on troff, macros etc unless specifically interested.
I disagree. Parsing "loose coupled" text and converting it to the format that a different tool expects is a rather non-trivial problem, and one that's often poorly specified to begin with; converting structured outputs is generally straightforward in comparison.
For any serious data retrieving or scripting the light object wrapping will be superior. For quick one off use maybe the plain text option will be quicker to reason about.
I've tried writing a language around this but it's... rusty. You kind of want to pipe Get-AzDnsRecordSet and Get-DnsServerRrsourceRecord to the same interface structure and then build another transform to some other structure from the interface... The program wires together the transforms. So the programming paradigm becomes rather unconventional, you only write transformations of “events”, you address other shell commands by listening to outputs of their “eventspace,” etc. ... You get a sort of strange logic programming language where the pipe operator has to be slightly specialized every time because it always needs to transform a little as it pipes
Mandating tab separated columns with a consistent quoting for embedded spaces would be a net benefit. And it would match today's tools well.
While there will always be a need to keep Bash around for comparability, there are a plethora of other tools out there that solve many of the shortcomings of POSIX.
I don’t use it on servers, I stick with Bash for that. But I do use it as my primary local shell.
As for why murex, that’s a combination of personal preference and simply not being aware of other shells until after I’d already started using murex.
What I like about it is it’s typed and does a lot of the boring stuff automatically in the pipeline based off what type is (JSON, YAML, CSVs etc are all “types”). So working with a JSON file is as natural as working with a flat text file. But also it’s trivial to bypass the clever stuff and use it like a dumb bash shell too. Which is where Powershell and its ilk fall down.
They also have the benefit of properly handling numbers. Some might look at the absence of maps as a negative, but I think alists are preferable anyway due to their constant ordering.
JSON is nice in that it has just enough structure to do a good number of tasks in one obvious way. The biggest omission is probably some kind of time and/or date type (but ISO8601 in a string is the obvious solution there).
It’s not a coincidence that JSON was reverse-engineered from a language with convenient literals for dictionaries and arrays, and most languages provide those two collection types because they cover most use cases, so JSON fits most languages fairly well.
It’s just handy having both arrays and dictionaries available, rather than stretching one data structure to cover both, whatever Lua or Lisp might say.
I’m saying that arrays and maps give good bang for the buck, so you don’t really need to define anything beyond those. And if you accept that, having special syntax for arrays and maps is more convenient and readable than S-exprs.
S-expressions are arrays, and maps are really just degenerate unsorted arrays of key-value pairs. Taking a look at https://json.org/example.html, I think this is easily more readable:
(menu
(id file)
(value File)
(popup
(item (value New) (onclick "CreateNewDoc()"))
(item (value Open) (onclick "OpenDoc"))
(item (value Close) (onclick "CloseDoc()"))))
than: {"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}A big part of the difference is that all the JSON keys are quoted, which I agree is ugly (I like JSON5 myself).
You’ve also omitted the “menuitem” from the S-expr version. That could have been omitted from the JSON too but I assume it’s meant to be there for some good reason.
I think the first reason is that the distinct characters provide a kind of visual checksum which makes me (slightly) more confident when initially matching a closing character to the correct starting character.
The second reason is that each character has a conventional meaning which makes it possible to form an initial guess as to its purpose.
Having said this, I freely admit this opinion may be colored by my only (paid) experience writing Lisp which was for a sprawling 20 year-old AI codebase mostly written by professors and graduate students who were often learning Lisp as they went.
'But folks won't use it!' Well, they might not. But if one gives folks a choice between the capability they need using an unfamiliar technology and not having the capability at all, they will learn the unfamiliar tech.
To put it in 2021 terms, Jupiter Netbooks in 1980's instead of PDP-11 green phosphor terminals.
However my university library had a very good section on all kinds of OSes and programming languages since the dawn of computing, thus I could see how the future might have been and there was definitely a much better path, specially when coupled with my own experience across Amiga, Windows and Mac OS.
It is like the bulb paradox, just applied to OSes.
Rivest's proposal, with source code: http://people.csail.mit.edu/rivest/sexp.html
McCarthy's Common Business Communication Language: http://jmc.stanford.edu/articles/cbcl.html
Some forgotten wisdom right there.
Or do we now have incompatible json shell tools and streaming text tools as a permanent fixture?
I'm excited about structured output ideas, but json? I'd much rather have streams of whitespace separated words than json. That's in that "No type system is better than a bad type system" metaphorical area.
I'll take grepping with a theoretically brittle regex over this jq[1] any day of the week.
[1] jq -nc --stream 'inputs | select(length==2) | select( [.[0][0,2,4]] == ["results", "data", "row"]) | [ .[0][6], .[1]] '
That being said, I too will take a `sed` or `awk` one-liner over some of the `jq` monstrosities that I've seen.
> people tend to forget this
There are people with strong opinions about this that don't even understand why you need something like JSONL in the first place and I'm surprised by that. If they never use unix pipelines I don't get why they feel we want to hear their hot take about how they should be redesigned.
For the second point, whitespace sensitivity is the one mistake that greatly pisses me off with Unix. I should be able to pass arguments and filenames with as many spaces as I want. We are in the 21st century and occasionally do use spaces in filenames.
That being said, the goal is that command line tools that output useful data for scripts should have a structured output option like JSON while still keeping the human text output option so something like JC doesn’t even need to exist.
$ jc -p date
jc: Error - date parser could not parse the input data. Did you use the correct parser?
For details use the -d or -dd option. $ echo $LANG
en_DK.UTF-8
$ date
2021-08-23T08:07:02 CESTFor best results set the LANG locale environment variable to `C` or `en_US.UTF-8`. For example, either by setting directly on the command-line:
$ LANG=C date | jc --date
It is possible to add support for more locales and you can always override the built in parsers with your own plugin to support it.> But, um, seems slightly kludgy to be obvious. What if the `ifconfig` format changes slightly.
Lots of people have asked for this, but the argument has always been that it can't be done, it's too hard. So I created JC to help open minds and change behavior. As I've said many times: the goal of JC is for JC to never have to exist. Hopefully it will persuade people that there is a better way and we should expect better from our existing tools without having to completely change the way we do things today.
JC supports over 70 programs and file-types today. I slowly, incrementally added more and more parsers over the last two years. Now it's to the point that it's hard to find popular apps that don't have coverage. lately, I get requests to add parsers for apps that already provide JSON output, which I don't do. :)
And those old apps like `ifconfig`? Many of those haven't been touched in a decade. There's not a huge risk of the output changing any time soon. (Believe me, I know - I went through the source code in several of these utilities to be able to figure out what to call some of the undocumented fields)
What I'm trying to say is that there are 100 reasons to say it won't work until you actually try it and you find that it actually works pretty well and opens up possibilities you hadn't thought of before.
But in 2013 a certain data format called JSON was standardized as ECMA-404..."
"Had JSON been around when I was born in the 1970’s Ken Thompson and Dennis Ritchie may very well have embraced it as a recommended output format to help programs “do one thing well” in a pipeline."
This whole post hinges on the theory that JSON is a revolutionary technology that no one had created something like before, and no one even considered creating before.
But that seems completely wrong, right?
"The Xerox Network Systems Courier technology in the early 1980s influenced the first widely adopted standard. Sun Microsystems published the External Data Representation (XDR) in 1987. XDR is an open format, and standardized as STD 67 (RFC 4506)"
That said, you know that every potential variation of every possible approach to accomplish what I presume your objectives are has already been exhaustively explored, documented, implemented, and finally abandoned by organizations with functionally limitless resources (aka open ended government contracts) - 50 years ago? Have you considered leveraging some of that work? I don't think many people know about the massive amount of work already done - that was just abandoned for a variety of reasons: reasons that often no longer apply, and very rarely have anything to do with the technology's utility. For example: everybody here knows about the OSI model and how sparsely filled out it is - but did you know that there is a layer set aside to do exactly what you are talking about, and that it just isn't being used? Thats right, #6, the presentation layer - specifically the virtual terminal protocol: which is where the designers wanted the object exchange and structured data to go... not in a mess of json one layer up. The VTP was outlined in several papers going back to at least '72. You could also lean on the Airforce's work for your data model, they ran that to ground pretty thoroughly with IDEF. You've also got a huge amount of free work from IBM when it comes to structured documents, and architecture that lends itself to semantic reasoning.
Anyway, my larger point is that instead of exasperating the issue of wastefully bloated software teetering on increasingly high layers of abstractions (do a stack trace and consider the insanity of it), maybe the way to really improve our circumstances has already been discovered and then lost for a time. It would be no more difficult than trying to make kornshell-json a thing.
A Survey of Terminal Protocols (1979) DOI 10.1016/0376-5075(79)90001-1
Computer Network Architectures and Protocols (1983) DOI 10.1007/978-1-4615-6698-4
IDEF: https://en.wikipedia.org/wiki/IDEF
IBM's various journals (pre-'00) are also worth reading.
This goes against the philosophy explicitly mentioned in OP's article. E.g. avoid tabular formats.
This is systemd against init again. A powerful, but overreaching shell that becomes unreplaceable and bloated with concerns. In constrast, traditional *nix/GNU programs work well, and interact well, in every shell.
I think that the shell paradigm could be so much better. If there's a momentum to make a change from that, I'll jump on the bandwagon. Any change is better than the current state of the shell.
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git...
So I have to bring in jsmn.h to parse protocols? Sorry no. Been there, done that. We are pushing too much unnecessary JSON around already.
Unix is also about KISS. Structured data are lines and paragraphs.
what it replaces (unstructured text) is much less secure so I don't think that counts against json
Which format to use a as representation of data structure might be debatable, but I think JSON is a reasonable choice. (Formatted) JSON is readable by human and could be easily parsed by programs.
I want some sort of non-destructive way for "the shell" to take a best guess at what's going on when I make a typo, or when the data's not formatted quite right, like a live linter that's there all the time.
12 Principles for a Diverging Desktop Future [dd]
which is the vision for the Arcan project.[dd]: https://www.divergent-desktop.org/blog/2020/08/10/principles...
Second, this does not handle interprogram piping (e.g. 'find . | xargs dosomething'). We have filtering to console but little piping (we can map the json output to a single column and pipe that, using the textual interface with all its deficiencies).
To be clear, the author has no choice (rewriting all userland is a daunting concept), but a proper approach for 21th century Unix would be for all utilities to output and receive json (or whatever format is chosen) natively, and that the console would then be able to mange that into a textual format for the user.
Xerox PARC workstations and Lisp Machines already solved the problem via their REPLs.
data = ``jc PROGRAM ARGS ...``
The double-backtick syntax runs the external program, `jc` in our case, and parses the output. It means that the "integration" is not `jc` specific.`data` is now structured data that comes from the parsed JSON.
Example (run from your shell):
ngs -pl '``jc ifconfig``.filter({"name": /docker/}).ipv4_addr'
Will print IPs of all docker interfaces, one IP per lineEDIT: well, some discussion about this: https://github.com/chriskiehl/Gooey/issues/296
[0]: https://octopus.com/
If I could design it all from scratch I'd have a convention for getting a program to tell me:
- info, user prompts, debug -> stderr
- foo data, bar data -> stdout
That way I could use those defaults sometimes, but other times specify per-execution preferences that would route the data differently.
> A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see Bash Builtins) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced, assigned to, unset, or has its attributes modified (other than using or changing the nameref attribute itself), the operation is actually performed on the variable specified by the nameref variable’s value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to the function.
So something like below:
set_var() {
local -n vname=$1 # use nameref for indirection
# some processing and assign value
vname="abcd"
}
use_var() {
local output
set_var output # call function to populate the variable
echo "output=$output"
unset -n output
}
use_var
I guess this is pretty close to "clean". Unfortunately, it is only available in bash 4.3, if I'm not wrong, so I don't have a choice to use it (our hosts use 4.2.x).[0]: https://www.gnu.org/software/bash/manual/html_node/Shell-Par...
[1] Unix has weird ad-hoc mechanisms too like environment variables or your PATH containing modified versions of programs, and it has programs that do a thousand and one things, but I claim those are mostly violations of the Unix philosophy.
Cut for field 1 (`cut -d/` -f1) is a cheesy way of saying we want to delimit fields with '/' too. So add that to awk via script variable or via -F command-line option.
[0]https://stackoverflow.com/questions/23934425/parse-ifconfig-...
> jq filters run on a stream of JSON data. The input to jq is parsed as a sequence of whitespace-separated JSON values which are passed through the provided filter one at a time. The output(s) of the filter are written to standard out, again as a sequence of whitespace-separated JSON data.