I have had great success creating DSLs using ipython, where you simply define a set of classes and functions on them, then use ipython.embed() (Before it was terminal.embed, so you can see where the experience is dated).
I've used click [1] a lot to build Python tooling scripts the past few years. Click usage is "sort of" similar to the author's proposed solution. There's also a small section here [2] that describes some of the issues covered in the article (in context of argparse).
Certainly factor/retroforth, Forth is too low level to be useful, but with support for quotations and streams, factor/(retro?) might be good, dealing with series a la rebol is also a good idea. Unfortunately all of those languages are kind of dead.
The interesting thing is that pipes behave in an rpn like fashion, but standard behavior is prefix based, So a rebol like language should be the normal operation, and then it changes to rpn when you pipe, the only languages I see implementing this is rye, which is highly experimental.
Powershell is also a really good option, at least for what OP wants, I like terseness. I can read bash code fine enough, until someone starts golfing, but that's true of all languages. python also has xonsh.
The whole point of autotools is to make sure that your project can run on the largest possible set of unix-like operating systems.
`sh` is actually the very best compilation target for that, looking back in time, whatever other language that could have been chosen (most likely perl) would have been a huge mistake.
I don't think you understand the role of autoconf... The whole point is that you _don't know_ yet what compiler is available, which version/flavor of C is supported, which API or syscall are available, etc.
Autoconf was litterally made so that you can write C code that is somewhat portable. Suggesting that it should have been written in C and built locally is non-sensical.
Well first because autoconf/automake were built in the early 90s. It's not like there were a ton of choices back then.
Basically if you wanted something that 1) is available on all Unix-like flavors and 2) is strictly well defined and standardized, you could count them on a single hand fingers.
I guess the candidates were sh, awk, make, m4 and perl.
sh had the advantage of being ubiquitous, somewhat well understood by everyone, strictly defined (an sh-compatible shell at /bin/sh was mandated by Posix/SUS, which had (and still have) quite good traction even for non-strictly unix/posix Oses)
Note that even by today's standards, "sh" is probably the only thing you can be 100% sure is available whatever the Unix flavor, even on the most minimal build imaginable.
I cannot say I can think of anything as well defined and supported even today.
Python? Ok but which specific version?
Most distribs struggled to migrate to Python 3 for the very reason that system-wide python 2 usage was pervasive in base utilities (yum, etc). If Python 2 would have been chosen as autoconf target, imagine the nightmare we would be in today.
Perl? I guess it could have been the strongest candidate after sh. Present me is happy that it was not chosen though, as we seldomly see any Perl nowadays, while sh is still quite well used and understood.
sh is also quite well suited IMHO for the kind of things you do with autoconf/automake/make.
Some examples that come to my mind:
- Cleanup some directories / intermediary files
- grep / find / sed some configuration from config files to .h/.pc
- Lookup for ad-hoc libraries on the filesystem
- Archive / build your dists
- etc
Now dont get me wrong, I won't say autotools are a brease to work with. Especially I have a profound hate of m4.
Pragmatically though, once you tame the beast, it does the job it was made to do pretty darn well.
If I recall correctly, the original compilation target is not even /bin/bash but the /bin/sh. Originally, the ./configure script generated by autoconf must run on several incompatible flavors of Unix at that time, including AIX, SunOS, and BSD, powered by several different CPU architectures, such as x86, Alpha, SPARC, and MIPS, so shipping binaries or using the Bash syntax was not acceptable. It must strictly use the original sh syntax. Though I suspect lots of Bashism has lurked in over the years since nobody still tests it anymore.
I felt like the author did realize that, and the "written by the devil" comment was a direct reference to autoconf (which certainly qualifies as a devil). Who knows, though.
zetalyrae also correctly pointed out that much of the text is "handwritten but transcluded". Most of the code snippets that appeared in autoconf was originally written by hand and stored in reusable modules, including 100+ tests in autoconf. So overall the volume of code is still huge.
I guess it at least demonstrated the power of modularity. Almost nobody can understand it, but it still works...
A very hard thing to do though is the requirement of generics.
If everything is typed, you may have functions that take an argument and produce another value of the same type for instance. And generics are really, really hard to implement correctly.
Oil is first thing that came to mind reading article. Interestingly the submitted article and the one you linked open in similar way with two bullet points saying shell is made of two things.
I've looked into Oil a bit, and I think Andy has the right idea for how to go about updating the legacy scripts. I've written a few local scripts in Oil and it's a definite upgrade from Bash.
you could also have the console on our own machine with 1+GHz that passes code to the embedded box to execute, or one of a million other variations on this theme. but nooo we gotta LARP about how well we support legacy systems which is why are systems are so broken after doing this for 50 years. oh yeah and embedded is dead, it's just high end boxes with ubuntu on them now. and last but not least i hope to god i never have unix on something that actually needs to be embedded for all of performance, stability and security reasons.
A number of command-line programs are actually just wrappers around a more strongly-typed library API. For example, curl and libcurl, or ffmpeg and a whole collection of audio/video libraries. [1]
Of course, in the POSIX world, "library API" means "C API", so if you want to write nice-looking Python code like in the article, there is still some more work to do.
Where does the requirement for it being a generic library come from? The point is that a lot of things that are available as command-line utilities for shell scripts already have a C API available as well.
And libav* is used by a lot more than /usr/bin/ffmpeg
I was just thinking the same. There's not really any reason a shell can't have terse syntax for quick command things and explicit, careful syntax for things that require it. And if python had a pipe operator, you could basically do
open('file.txt') |> grep(?,"lasagna") |> set |> sorted
Which is really pretty good. It's not at all hard to imagine a Python that was designed to be good in the shell. They just didn't try.
I’ve written about this problem previously and it is a problem I’m attempting to solve with my own shell (along with the added problem that most shells still have a UI from the 80s).
I do think it is possible to find a sane middle ground however it’s not an easy task negotiating the right compromises to make. Plus any shell that does achieve this wouldn’t be POSIX compliant, which then automatically breaks support with sh/Bash etc.
Story time: I've been a diehard bash/zsh fan for the better part of the last two decades (I was a regular user of it since 1999 so that's 24 years now). I daily-drove some form of Linux since 1999 up until ~2010 when I switched to MacOS (zsh). I only switched to using Windows as my daily driver for ~2 1/2 years ago.
Around 2 years ago, I had a script in python that I wrote to process my directory of media files based on the directory name and looked for a subtitle file and pulled that down. For fun, it was a simple enough script that I re-wrote it in bash (WSL2). Six months later, I needed to make a change and was surprised at how much I couldn't read it. I just forgot.
That was OK, I had a boring weekend and rewrote the whole thing in PowerShell. A few things I noticed:
- It was more LOC owing mostly to brackets, etc, but I'm over the whole "less LOC is better" mentality.
- It was faster - but this is probably the result of WSL2, but also, I didn't have to call out to other CLI utilities as much. You can do just about everything in PowerShell and call out to .NET for anything more complex.
- It was far more readable. Having to parse while [ ]; do ... done; is harder than ForEach-Object ( ) { }
- Passing things around as an object is a very pleasant way of scripting (Python could do the same, but it feels more "Programming Language" than "Scripting" at this point)
- Calling an CLI utility is just the same as in Bash (Unless it has spaces, then you just do & "Path to CLI.exe")
I had to make a change recently and was pleasantly surprised at how much I could remember the layout, functions, etc.
I also tried to adhere to the MS-isms like using a Verb-Noun for my functions where it makes sense, and breaking things up in modules.
I don't absolutely love PowerShell, but it is pleasant to use and pulling in .NET stuff is just a line of code away.
Now I use it directly as a daily driver, with Windows Terminal - which is also surprisingly pleasant to use.
That is just a case of people trying to use PowerShell as a bash shell combined with the unfortunate decision by whomever at Microsoft to alias PowerShell cmdlets to well known UNIX tools.
It doesn't "silently mangle" binary data - it assumes you're piping PS objects or text streams through. If it is a text stream, then it will pipe through as Unicode by default.
Invoke-WebRequest (curl) pipes out an object, with the content in (object).Content. Using | uses Unicode by default which "mangles" binary output. So it tries to convert the object into a Unicode text stream then into the file.
The correct way to save an image would be to simply set -OutFile (-o) in Invoke-WebRequest.
If you MUST pipe it through, then you need to access the object's .Content, then use Set-Content -AsByteStream (for PS7+) or use Set-Content -Encoding byte (PS5)
This is useful if you wanted to put the image inside a variable (rather than create a temporary file for the image) and pass it around until you figure out what you want to do with it.
>> That is just a case of people trying to use PowerShell as a bash shell combined with the unfortunate decision by whomever at Microsoft to alias PowerShell cmdlets to well known UNIX tools.
>> It doesn't "silently mangle" binary data - it assumes you're piping PS objects or text streams through. If it is a text stream, then it will pipe through as Unicode by default.
The problem is that PowerShell uses aliases that match UNIX command line utility names and the same pipe syntax, but exhibits subtlely different behavior without any indication of the difference.
If someone is not aware of the difference and they use PowerShell to process production data, their data could be corrupted without them realizing it.
> The problem is that PowerShell uses aliases that match UNIX command line utility names
Agreed.
> If someone is not aware of the difference and they use PowerShell to process production data, their data could be corrupted without them realizing it.
Someone coming from UNIX-land shouldn't be making PowerShell scripts day1. They really should be diving into documentation. Microsoft's documentation on PS is _excellent_ and here is one about pipelining: https://learn.microsoft.com/en-us/powershell/module/microsof...
Notably:
> To support pipelining, the receiving cmdlet must have a parameter that accepts pipeline input. Use the Get-Help command with the Full or Parameter options to determine which parameters of a cmdlet accept pipeline input.
then further down:
---
Using native commands in the pipeline
PowerShell allows you to include native external commands in the pipeline. However, it is important to note that PowerShell's pipeline is object-oriented and does not support raw byte data.
Piping or redirecting output from a native program that outputs raw byte data converts the output to .NET strings. This conversion can cause corruption of the raw data output.
As a workaround, call the native commands using cmd.exe /c or sh -c and use of the | and > operators provided by the native shell.
> Piping or redirecting output from a native program that outputs raw byte data converts the output to .NET strings. This conversion can cause corruption of the raw data output.
kichererbsen in german means chick peas, but if you separate the two parts.. and maybe fudge the spelling to kichern, it would literally translates to giggle peas.
in dutch it's kikkererwten which literally translates to frog peas. I always found that funny... makes me want to kichern.
I get the feeling Powershell is exactly what the author has in mind when they say
>Historically, operating systems that have fanciful structured interfaces between programs have been left in the dust by Unix, because Unix maximizes flexibility by favoring the lowest-common-denominator interface, which is the string.
at the end, there.
I adore what Posh gives me over bash. I've also seen many people over the years try to use it as Windows's "shittier Bash" without noticing that doing complicated things with strings here is so painful because _you're not supposed to do complicated things with strings._
The readability of Posh is a direct consequence of people learning to work with its stricter, but more composable interfaces.
That's how I tried to use it in the first year of using Windows (after switching back). Once I let go of my entrenched habits after years of Linux, I was able to do much more with PowerShell.
I mean, there's some beauty to having all output as string. It's more predictable. But soon you're jumping through so many hoops to get what you want, it's terrible.
I've been learning PowerShell ever since it was launched, but it took me many years to realize it's actually better than bash. Sometimes you have two learn tô stop fighting the tool and embrace it instead.
PowerShell fully disregards the POSIX paradigm (good), and has forged its own path in the world of shells.
- one shell does almost everything, rather than having a million different utilities with their own input/output formats
- everything is not a bag of bytes, or a bunch of text to be parsed; outputs are objects with queryable structure
- comes with a massive standard library, even without .NET
- straightforward, (mostly) sane syntax, thoroughly different from `sh` and descendants
I must say, for all the criticism levelled against it, Microsoft has a penchant for creating really useful and unique programming languages and tools, or at least building on existing tools and improving them. C#, F#, .NET, PowerShell, TypeScript, VS Code, VS. And six out of these seven are now cross-platform and open-source.
Why do you think people are still entrenched into mode of thinking these days? Better tooling has switched over to node (NPM) and Python (pip) for anything more complicated than 50 lines.
> Why do you think people are still entrenched into mode of thinking these days?
I dunno. Familiarity? I mainly use Windows because it's what I've used for 23 years, even though I have a dedicated SSD to Linux. Maybe for many people, it's not worth unlearning decades of muscle memory and re-learning PS just so they can get object-oriented shell scripting.
> Historically, operating systems that have fanciful structured interfaces between programs have been left in the dust by Unix, because Unix maximizes flexibility by favoring the lowest-common-denominator interface, which is the string.
I am curious for examples for such a operating systems and their program interfaces
In reality there isn't much contradiction between the two goals, you "just" need a bit more design thought instead of getting stuck in the ugliness of POSIX.
Author's first example
cat beef.txt | grep "lasagna" | sort -n | uniq
is already possible literally as written in Xonsh (a Python-based shell) with a pipeliner plugin
give me functions that output typed data. none of this string parsing shit. the sole fact that they have syntax to call bash: $() makes me not want to use this ever. this is why every single effort like this is wasted. i used to get excited by these projects and after about 2 of them i realized it's always a bunch of crap duct taped together, and also bends over to be compatible with un*x and so everything is ruined.
they're also often so poorly implemented, like with those early fancy python shells they'd go beserk if one command you execute goes into a time consuming process (or infinite loop) whereas why the hell can i not just run another command below while waiting for the one above to complete? this is the thought i had in 2008. i could have made something better out of my ass. really, there's no other way to put it.
here's another thing: a language console should operate on semantic objects, not text. as in, you move objects around to program in it. editing code by typing one character at a time is too slow. no amount of vim or emacs fixes this. in fact, we can just scrap those un*x garbage (along with the rest of un*x) while we're at it, when implementing an OS that works on actual meaningful data instead of text, and semantic objects instead of a language represented by text.
F# is readable and terse. F#'s REPL is really fun to work in, too. The significant whitespace makes it a bit awkward in shell mode, but why do that when editing a script in VS Code, Emacs, or even screen + nano is far more convenient.
In author's defense, code is apparently meant to exemplify the differences between shell and general programming languages mentioned in bullet points. So working doesn't matter and isn't even mentioned that is in Python.
It's also not performing the numeric sort ('-n'), which is arguably the hardest part because it's not a Python built-in. A bad example all around.
Here's my attempt[1]:
import re
lines = list(set(line for line in open('beef.txt') if 'lasagna' in line))
lines.sort(key=lambda line: int(re.match('\d*', line)[0] or 0))
for line in lines: print(line, end='')
Python is especially bad at this task for a number of reasons:
- Iterating over the lines yields strings ending with newlines. If you naively loop and print the sorted lines, `print` will add a second newline, resulting in every other line being empty. Hence the `end=''`.
- Python's str has many helper functions, but none for numeric sort, so you need an import, a regex, and a fallback value.
- It's very imperative with a big mutation in the middle. Using `sorted(lines, key=...)` and `print(''.join(lines))` would be simpler and safer, but each would double the memory usage for large inputs.
- Filters using list comprehensions ('x for x in y if z in x') are idiomatic and readable but very verbose.
- The concepts are a lot more complicated. Here we have file handles, magic iterators, first-class-functions, sets, generators, lists, keyword arguments, regexes, overloaded indexes, etc. A beginner would blindly copy and run, and have no chance to fix any issues that appear.
- Because Python has almost no static analysis (undefined names are runtime errors!), you'll have to find most errors by running your code. For a quick shell script this usually mean running over the real inputs, giving a horribly slow edit-compile-run loop and be potentially dangerous.
Shells need to be good at composing programs together, languages compose libraries.
Pipes and output redirection, running things in parallel and waiting till they end - for some reasons these things suck in some languages compared to what the most basic shell can provide.
Shells let you do unexpected, unintended integrations.
I think languages could do better without breaking
Though to be honest, eg Bash isn't all that great at composing programs, too. Especially when you want to handle failure.
Mostly shells are good at scaling downward, ie having relatively little syntactic (and brain) overhead for very simple things. Especially when you are ok to handle any failures manually.
"Bash is like the neighbor who seems nice and sweet, but maybe they don’t have a life you’d want to have personally. They have some pretty serious character flaws and obviously make mistakes, but in the end they are a nice person who tries hard to make sure the people that depend on them are doing ok.
"Batch [cmd.exe] is the guy who looks like that neighbor, but then turns out to be a serial killer."
...aaand if you actually think this through you will just come to the conclusion
- piping isn't anything special, its just an operator
- piping has like 10 different things you need to account for, just a bunch of stuff is chosen FOR you (heuristically) in the shell. for example IFS. whether its correct for a given pipeline is an entirely different question
- any form of error handling in a pipeline is a syntactic disaster mostly because shell syntax completely sucks
- every second program in the pipeline will literally just execute code from the input until you figure out the secret option to make it not do that anymore
- data has a structure, whether you think it does or not. whenever you fail to account for all possible cases (doesn't help that these tools mostly don't have any specified format) of that (such as in most shell scripts), you are opening holes for bugs. both sides of the pipe must use the same data structure (and since its shell that means encoding it to text which changes depending on your locale and encoding).
- for most cases you don't even need pipes:
a = f()
g(a)
or just g(f())
piping is just premature optimization like lazy evaluation. composing functions is actually simpler, just shells have no (good) way to do it.
Stderr output does not indicate that a program has failed. It's just a communication channel to the user that's not affected when you pipe or redirect stdout. So useful for programs where to want to make sure that data doesn't get mixed up with messages to the user.
exit codes are the only true way to tell if some process failed and programs should set them properly.
This is well made case - but I'm not sure I buy the central argument. Within some basic limits, I don't think terseness and readability have the contradiction made out here, because in programming we have abstraction, which gives us both.
To take the example command that's given:
beef.txt | grep "lasagna" | sort -n | uniq
Sure, writing the logic out for this in something like python straight out the bat with only the standard library might look messy, but with one basic convenience function it could quickly be:
Obviously you have to write the function in the first place, but I'd say if you're doing something like this often, it's easily worth spending that 2 minutes. And if you're not doing this often, you'll have a faster time writing more code, but keeping less heavy lifting of "how does bash pipe together" in your head.
I shared a project here a few weeks ago experimenting with what my dream shell might look like, what surprised me more than anything else, was how easy writing a repl environment actually is. I put a scrappy one together as one person in a few hours, so I don't understand why as developers we've reached general language models before being able to make a powerful, but new-user friendly shell.
Also, completely unrelated note, but posix only allows passing back strings - but isn't this true of web apis too which we use all the time? How come no json as a standard passback from programs?
Of course, all of those things would need to be generators. Python accepts that kind of code, but most "Python developers" wouldn't think on it. Haskell is a much more natural fit for replacing Bash. (By the way, Haskell has at least one Bash replacement library that I remember, but it's hard to find).
That Python example is broken in all kinds of ways, and is overly verbose. I would go with something like the following, which does spark some joy in me:
matches = {l for l in open("beef.txt") if "lasagna" in l}
print(*sorted(matches, reverse=True), sep="")
On And on the same note, the shell command is weird too, and should probably be:
grep "lasagna" beef.txt | sort -nur
So the shell command is still definitely simpler for this case, but for anything more complex, I'd go with Python.
EDIT: I put back the `n` flag, in case the lines start with numbers.
That's much better, but like the original implementation it's still missing the -n flag for sort, which extracts numbers at the beginning of lines and sorts them numerically instead of lexicographically.
Oh, I always thought it only works on numbers, and am very pleasantly surprised to learn it works on numeric prefixes, thank you!
EDIT: After playing around with the python approach, I arrived at this, which is definitely more verbose, but arguably, more maintainable:
import re
NUMERIC_PREFIX_RE = re.compile(r"^(\d+.?\d*)")
def numeric_descending(line):
if m := NUMERIC_PREFIX_RE.match(line):
return -float(m.group())
return float("inf")
matches = {l for l in open("beef.txt") if "lasagna" in l}
print(*sorted(matches, key=numeric_descending), sep="")
Not sure it is more maintainable than a shell solution. Look at this in two years from now thinking there might be a bug somewhere in there (say you want to include negatives and scientific notation). In the shell, a quick inspection suffices. In this case you have to deal with regular expressions within Python, their own little special language… and it may not spark joy.
Well, it seems you nerd-sniped me a bit, so here's a new version which deals well with scientific notation (negatives were already supported) and is actually simpler, using a split and "it's better to ask for forgiveness..." instead of the regex:
def numeric_descending(line):
possible_number = line.split()[0]
try:
return (-float(possible_number), line)
except ValueError:
return (float("inf"), line)
matches = {l for l in open("beef.txt") if "lasagna" in l}
print(*sorted(matches, key=numeric_descending), sep="")
Thanks, and apologies---I didn't mean to come off as negative. Yes, this code is cleaner and certainly maintainable a couple years down the road. It will work in a slightly different way than the shell script (the lack of space in "10oz" of lasagna vs "2.0e1 oz" of lasagna), however I'd consider this a feature rather than a bug, as it could help identify bad formatting in the input data.
I've learned from experience that it is hard to replace certain simple space and memory efficient programs that have been tuned over the course of 40 or more years. If one wanted to print only a single instance of a line in a file, then python's set construct in your snippet is certainly sparking more joy compared to the awk equivalent of printing only the first instance of a line in a file:
awk '!x[$0] {print; x[$0]=1}'
Tuning python to use ordered dicts to keep the line order looks a little less pleasant (and might appear confusing to an uninitiated), but would still be rather neat. There are many instance of day-to-day data inspection tasks where bash, sort, shuf, sed, grep (or rg), and awk will solve a problem near optimally out of the box and can combine nicely in modest memory machines to tackle large data, when some python native code (or poorly designed python library) might accidentally cause a performance mess. Interactive shells are an awesome superpower that is hopefully going to stay with us for a while. I'd have personally been happy if some type of Lisp REPL had won over the shell fights, but we don't live in that world, so python + posix tools via a shell will have to work in practice for most people.
Your Python version is def neater and more compact. However there's something really elegant about using the | operator to compose commands (or using |> in OCaml/F# or iirc $ in Haskell) that I can't quite articulate :)
My example is a bit stupid but I hope it's clear what I mean. Maybe it's just that as someone who only knows left-to-right languages, the flow of the data matches the order the steps appear? Actually I'm now curious if Arabic or Hebrew speakers feel the same way. Or maybe I'm overthinking this :)
For me, the last example feels messy for two reasons. First, I have to worry about matching parens. That can feel mentally exhausting even for simple cases. Second, it's written all on one line, so it's harder to mentally separate all the steps. I usually break expressions like that apart into separate lines, but that means finding concise yet meaningful names for temporary variables.
In Forth, you have to reason about how many things each command takes off the stack and leaves on the stack, whereas with pipes, it's always exactly one thing.
Yeah I think it’s one of the less-hard things about Forth. For what it’s worth I quite enjoyed fiddling with Forth, and went through a bit of a “stack computing” phase. I never made the connection with the the pipe operator tho
I noticed the contrived Python example too, but yours will explode if the input is too big, so, it's not equivalent to Shell, which will buffer the input.
Still, even with that in mind, Python variant could've been greatly simplified. To give the author the benefit of a doubt: they probably made such a contrived example on purpose to underscore their point, but it's hard to find small-scale examples which illustrate verbosity... On the other hand, I might be overthinking on behalf of the author. Doubt they explicitly set out to make a contrived example. They probably wrote what they wrote and were happy with the result because it helped them to make a compelling argument, and they didn't think to analyze their own illustrations too deeply to see if they might disagree with their conclusions.
>yours will explode if the input is too big, so, it's not equivalent to Shell, which will buffer the input.
I agree that mine would explode on a massive file, but how would the shell version avoid this, given the need to sort? As far as I know, it sorts entirely in memory.
When the input is large enough, GNU `sort` writes the input in chunks to multiple temporary files, sorts the individual files, and then merges the result for output.
Well it might work as a joke, but I have to say that I had more than enough experiences with Powershell that drastically disprove that notion in my opinion. That is not to say that bash and consorts are much better, but e.g. a shell that decides to arbitrarily violate its normal error handling logic based on internal voodoo heuristics about if it's running in a console or not [1] is pretty much the total antithesis of sanely architected in my book.
As in: Architecture must be the reason someone thought this insane scheme has any merit. And arbitrarily starting to throw exceptions whenever a subprocess dares to write to stderr, especially if the shell doesn't do this in normal operation, doesn't get any other label than "insane" from me.
Also the terrible terrible idea of using common unix shell command names as aliases for PowerShell commands that are similar but don't quite work the same way and take entirely different parameters.
...and that time period where Invoke-WebRequest used IE for parsing by default.
But other than those flaws I agree that it is really great.
> using common unix shell command names as aliases for PowerShell commands that are similar but don't quite work the same way
On any Linux/Unix, when `ls` is called, PS runs the native `ls` and not `Get-ChildItem`. These aliases only exist on Windows, where unless one is explicitly running a Unix-ish shell (WSL, Cygwin, MinGW, MSYS2, etc), PS will use these aliases. Plus, `ls`, `mv`, `cp`, and `rm` never really existed on Windows; the equivalent cmd binaries were `dir`, `move`, `copy`/`robocopy`, `del`.
Finally, this is a fairly tired argument against PowerShell...
If you are thinking I am a sarcastic detractor, I am not. I whole heartedly believe PowerShell is superior in every way that matters to more common POSIX shells.
I was unaware of the difference between Windows and Linux in this regard as I don't tend to use PowerShell on the few Linux systems I run. However, I maintain that it is still a mistake to alias them in Windows because anyone coming from a POSIX shell background will be misled.
> the equivalent cmd binaries were `dir`, `move`, `copy`/`robocopy`, `del`.
Nit: only robocopy is a binary in the list. The rest are built in to the command shell.
My sarcasm detector led me into thinking you were a sarcastic detractor (heh).
I get your point though; perhaps PowerShell should've stuck with the initialism aliases only (`gci`, `cpi`, `ri`, etc) and people wouldn't have nitpicked on this as though it was some powerful argument against PS.
Perhaps there could've been a banner saying `hey, this is Windows, use `gci` instead of `ls` with these xyz options`.
It may be a 'tired' argument but a valid one. For me I keep using 'dosisms' in powershell. But they do not work because those commands are actually something different and you need the params for those items. One that gets me every time is is the 'dir /s' command which does not work in powershell because you should use -recurse which is the command for Get-ChildItem which is what it is really calling.
I think most people have this issue when jumping between shells. Each one is has its own way of working. But adding aliases, I can see why they did it. The problem is it was poorly done up front. Now they are kind of stuck with it and we get to deal with it. Also you may be expecting all of the sorting items out of dir/ls. But you do not get those. You get to pipe it and sort it in a secondary step. Which is fine. But can is a bit of a pain when you are used to something like 'dir /os' vs 'Get-ChildItem . | Sort-Object -Property Length'. It just takes some getting used to. But seems longer and more verbose to get the same effect.
It is nicer though when you want to act on those items though (which is where powershell shines) as everything is in an object and you can slice it down. But using it for 'shell' things like where is a file and what is the biggest in a folder it falls a bit flat and feels like the object model is in the way.
> where is a file and what is the biggest in a folder
You don't need to use the full commands and parameters. The commands have aliases, and parameters can be arbitrarily short (and case-insensitive) as long as they are unambiguous:
gci . | sort Length
gci . -r -inc <filename>
Furthermore, the PowerShell interpreter has an extremely powerful autocomplete, so these should be straightforward to look up.
Maybe I'm missing something, but the example at the bottom, you can do just as much type checking with the terse command-line syntax as with what he calls the 'typed interface'.
The conclusion is eerily close to using a programming language, Python say, having a natural library API for that, and then a small `if __name__ == '__main__'` that parses args (with argoarse or docopt or whatever) and calls it.
The problem of course is so now you want your script to 'pipe' to pandoc, say, and pandoc even if implemented like this might not have chosen Python but Ruby, say. So you have to (or ~have to, most reasonable way is to) use the CLI to interface with it anyway.
224 comments
[ 2.0 ms ] story [ 280 ms ] threadIn the Python world, there are various solutions starting from https://github.com/ialbert/plac or https://argh.readthedocs.io/en/latest/ and moving on to https://github.com/pallets/click or https://github.com/Lucretiel/autocommand and probably N others.
Personally, I prefer Nim to Python which has https://github.com/c-blake/cligen. As mentioned in https://github.com/c-blake/cligen/blob/master/MOTIVATION.md, but not in the borretti article, the overhead of dispatch to a program in shell REPLs can also be thousands to millions of times higher than an API call.
[1] - https://github.com/pallets/click [2] - https://click.palletsprojects.com/en/8.1.x/why/#why-not-argp...
The interesting thing is that pipes behave in an rpn like fashion, but standard behavior is prefix based, So a rebol like language should be the normal operation, and then it changes to rpn when you pipe, the only languages I see implementing this is rye, which is highly experimental.
Powershell is also a really good option, at least for what OP wants, I like terseness. I can read bash code fine enough, until someone starts golfing, but that's true of all languages. python also has xonsh.
The source is a straightfoward `configure.ac` of 75 commented lines of code (13 with comments removed)
The whole point of autotools is to make sure that your project can run on the largest possible set of unix-like operating systems.
`sh` is actually the very best compilation target for that, looking back in time, whatever other language that could have been chosen (most likely perl) would have been a huge mistake.
Autoconf was litterally made so that you can write C code that is somewhat portable. Suggesting that it should have been written in C and built locally is non-sensical.
Not in my personal experience anyway.
Basically if you wanted something that 1) is available on all Unix-like flavors and 2) is strictly well defined and standardized, you could count them on a single hand fingers.
I guess the candidates were sh, awk, make, m4 and perl.
sh had the advantage of being ubiquitous, somewhat well understood by everyone, strictly defined (an sh-compatible shell at /bin/sh was mandated by Posix/SUS, which had (and still have) quite good traction even for non-strictly unix/posix Oses)
Note that even by today's standards, "sh" is probably the only thing you can be 100% sure is available whatever the Unix flavor, even on the most minimal build imaginable.
I cannot say I can think of anything as well defined and supported even today.
Python? Ok but which specific version? Most distribs struggled to migrate to Python 3 for the very reason that system-wide python 2 usage was pervasive in base utilities (yum, etc). If Python 2 would have been chosen as autoconf target, imagine the nightmare we would be in today.
Perl? I guess it could have been the strongest candidate after sh. Present me is happy that it was not chosen though, as we seldomly see any Perl nowadays, while sh is still quite well used and understood.
sh is also quite well suited IMHO for the kind of things you do with autoconf/automake/make. Some examples that come to my mind:
- Cleanup some directories / intermediary files
- grep / find / sed some configuration from config files to .h/.pc
- Lookup for ad-hoc libraries on the filesystem
- Archive / build your dists
- etc
Now dont get me wrong, I won't say autotools are a brease to work with. Especially I have a profound hate of m4.
Pragmatically though, once you tame the beast, it does the job it was made to do pretty darn well.
I guess it at least demonstrated the power of modularity. Almost nobody can understand it, but it still works...
https://www.oilshell.org/blog/2021/01/why-a-new-shell.html
If anyone wants to understand Oil a bit better, I found that the best intro is actually the Github wiki: https://github.com/oilshell/oil/wiki
Of course, in the POSIX world, "library API" means "C API", so if you want to write nice-looking Python code like in the article, there is still some more work to do.
[1] https://trac.ffmpeg.org/wiki/Using%20libav*
And libav* is used by a lot more than /usr/bin/ffmpeg
² The fact that the line count is just a hair off 2¹⁶ surely has some sinister, esoteric significance
A hair off‽ That's two base-two orders of magnitude from 2¹⁶, being 10 more than 2¹⁴.
https://github.com/anki-code/xontrib-pipeliner
The trial-and-error command refining is arguably what REPLs are for. It's the filesystem navigation that are the odd part.
(INB4: don't give me the "useless use of cat", it factors out better like this and everyone does it anyway.)
`<quux grep xkcd`
I do think it is possible to find a sane middle ground however it’s not an easy task negotiating the right compromises to make. Plus any shell that does achieve this wouldn’t be POSIX compliant, which then automatically breaks support with sh/Bash etc.
My shell https://murex.rocks
PowerShell does quite well in this case.
Story time: I've been a diehard bash/zsh fan for the better part of the last two decades (I was a regular user of it since 1999 so that's 24 years now). I daily-drove some form of Linux since 1999 up until ~2010 when I switched to MacOS (zsh). I only switched to using Windows as my daily driver for ~2 1/2 years ago.
Around 2 years ago, I had a script in python that I wrote to process my directory of media files based on the directory name and looked for a subtitle file and pulled that down. For fun, it was a simple enough script that I re-wrote it in bash (WSL2). Six months later, I needed to make a change and was surprised at how much I couldn't read it. I just forgot.
That was OK, I had a boring weekend and rewrote the whole thing in PowerShell. A few things I noticed:
- It was more LOC owing mostly to brackets, etc, but I'm over the whole "less LOC is better" mentality.
- It was faster - but this is probably the result of WSL2, but also, I didn't have to call out to other CLI utilities as much. You can do just about everything in PowerShell and call out to .NET for anything more complex.
- It was far more readable. Having to parse while [ ]; do ... done; is harder than ForEach-Object ( ) { }
- Passing things around as an object is a very pleasant way of scripting (Python could do the same, but it feels more "Programming Language" than "Scripting" at this point)
- Calling an CLI utility is just the same as in Bash (Unless it has spaces, then you just do & "Path to CLI.exe")
I had to make a change recently and was pleasantly surprised at how much I could remember the layout, functions, etc.
I also tried to adhere to the MS-isms like using a Verb-Noun for my functions where it makes sense, and breaking things up in modules.
I don't absolutely love PowerShell, but it is pleasant to use and pulling in .NET stuff is just a line of code away.
Now I use it directly as a daily driver, with Windows Terminal - which is also surprisingly pleasant to use.
[1]: https://learn.microsoft.com/en-us/powershell/scripting/learn...
[2]: https://learn.microsoft.com/en-us/powershell/module/microsof...
https://brianreiter.org/2010/01/29/powershells-object-pipeli...
Newer versions of Powershell have flags that must be explicitly passed to fix this, but it can bite you if you are not aware:
https://stackoverflow.com/questions/54086430/how-to-pipe-bin...
It doesn't "silently mangle" binary data - it assumes you're piping PS objects or text streams through. If it is a text stream, then it will pipe through as Unicode by default.
Invoke-WebRequest (curl) pipes out an object, with the content in (object).Content. Using | uses Unicode by default which "mangles" binary output. So it tries to convert the object into a Unicode text stream then into the file.
The correct way to save an image would be to simply set -OutFile (-o) in Invoke-WebRequest.
If you MUST pipe it through, then you need to access the object's .Content, then use Set-Content -AsByteStream (for PS7+) or use Set-Content -Encoding byte (PS5)
PS5> $(Invoke-WebRequest -Uri https://www.w3schools.com/html/w3schools.jpg -ContentType 'image/jpg').Content | Set-Content -Encoding byte test.jpg
PS7> $(Invoke-WebRequest -Uri https://www.w3schools.com/html/w3schools.jpg -ContentType 'image/jpg').Content | Set-Content -AsByteStream test.jpg
This is useful if you wanted to put the image inside a variable (rather than create a temporary file for the image) and pass it around until you figure out what you want to do with it.
>> It doesn't "silently mangle" binary data - it assumes you're piping PS objects or text streams through. If it is a text stream, then it will pipe through as Unicode by default.
The problem is that PowerShell uses aliases that match UNIX command line utility names and the same pipe syntax, but exhibits subtlely different behavior without any indication of the difference.
If someone is not aware of the difference and they use PowerShell to process production data, their data could be corrupted without them realizing it.
Agreed.
> If someone is not aware of the difference and they use PowerShell to process production data, their data could be corrupted without them realizing it.
Someone coming from UNIX-land shouldn't be making PowerShell scripts day1. They really should be diving into documentation. Microsoft's documentation on PS is _excellent_ and here is one about pipelining: https://learn.microsoft.com/en-us/powershell/module/microsof...
Notably:
> To support pipelining, the receiving cmdlet must have a parameter that accepts pipeline input. Use the Get-Help command with the Full or Parameter options to determine which parameters of a cmdlet accept pipeline input.
then further down:
---
Using native commands in the pipeline
PowerShell allows you to include native external commands in the pipeline. However, it is important to note that PowerShell's pipeline is object-oriented and does not support raw byte data.
Piping or redirecting output from a native program that outputs raw byte data converts the output to .NET strings. This conversion can cause corruption of the raw data output.
As a workaround, call the native commands using cmd.exe /c or sh -c and use of the | and > operators provided by the native shell.
---
To work with external commands and pipelining, you can refer to this SO (top answer explains it very well): https://stackoverflow.com/questions/8097354/how-do-i-capture...
And this behavior is terrible and one of many "gotchas" that PowerShell has. Fortunately, there is a chance it will be "fixed": https://github.com/PowerShell/PowerShell/issues/1908#issueco...
kichererbsen in german means chick peas, but if you separate the two parts.. and maybe fudge the spelling to kichern, it would literally translates to giggle peas.
in dutch it's kikkererwten which literally translates to frog peas. I always found that funny... makes me want to kichern.
>Historically, operating systems that have fanciful structured interfaces between programs have been left in the dust by Unix, because Unix maximizes flexibility by favoring the lowest-common-denominator interface, which is the string.
at the end, there.
I adore what Posh gives me over bash. I've also seen many people over the years try to use it as Windows's "shittier Bash" without noticing that doing complicated things with strings here is so painful because _you're not supposed to do complicated things with strings._
The readability of Posh is a direct consequence of people learning to work with its stricter, but more composable interfaces.
That's how I tried to use it in the first year of using Windows (after switching back). Once I let go of my entrenched habits after years of Linux, I was able to do much more with PowerShell.
I've been learning PowerShell ever since it was launched, but it took me many years to realize it's actually better than bash. Sometimes you have two learn tô stop fighting the tool and embrace it instead.
I still hate the naming schema, though.
Yeah, I suppose. :) In a way, it's also antiquated.
- one shell does almost everything, rather than having a million different utilities with their own input/output formats
- everything is not a bag of bytes, or a bunch of text to be parsed; outputs are objects with queryable structure
- comes with a massive standard library, even without .NET
- straightforward, (mostly) sane syntax, thoroughly different from `sh` and descendants
I must say, for all the criticism levelled against it, Microsoft has a penchant for creating really useful and unique programming languages and tools, or at least building on existing tools and improving them. C#, F#, .NET, PowerShell, TypeScript, VS Code, VS. And six out of these seven are now cross-platform and open-source.
> PowerShell fully disregards the POSIX paradigm
I agree that it's good.
Why do you think people are still entrenched into mode of thinking these days? Better tooling has switched over to node (NPM) and Python (pip) for anything more complicated than 50 lines.
I dunno. Familiarity? I mainly use Windows because it's what I've used for 23 years, even though I have a dedicated SSD to Linux. Maybe for many people, it's not worth unlearning decades of muscle memory and re-learning PS just so they can get object-oriented shell scripting.
I've often wondered how far you could get with a table based text structure in POSIX land.
Things like awk can deal with that already. Its backwards compatible if you ignore the first line.
it comes in handy when querying REST APIs
https://learn.microsoft.com/en-us/powershell/module/microsof...
I am curious for examples for such a operating systems and their program interfaces
Author's first example
cat beef.txt | grep "lasagna" | sort -n | uniq
is already possible literally as written in Xonsh (a Python-based shell) with a pipeliner plugin
https://xon.sh/
https://github.com/anki-code/xontrib-pipeliner
give me functions that output typed data. none of this string parsing shit. the sole fact that they have syntax to call bash: $() makes me not want to use this ever. this is why every single effort like this is wasted. i used to get excited by these projects and after about 2 of them i realized it's always a bunch of crap duct taped together, and also bends over to be compatible with un*x and so everything is ruined.
they're also often so poorly implemented, like with those early fancy python shells they'd go beserk if one command you execute goes into a time consuming process (or infinite loop) whereas why the hell can i not just run another command below while waiting for the one above to complete? this is the thought i had in 2008. i could have made something better out of my ass. really, there's no other way to put it.
here's another thing: a language console should operate on semantic objects, not text. as in, you move objects around to program in it. editing code by typing one character at a time is too slow. no amount of vim or emacs fixes this. in fact, we can just scrap those un*x garbage (along with the rest of un*x) while we're at it, when implementing an OS that works on actual meaningful data instead of text, and semantic objects instead of a language represented by text.
: root@llana fred #; dd if=/dev/sda of=/dev/null
^Z
[1]+ Stopped dd
if=/dev/sda of=/dev/null
: root@llana fred #; bg
[1]+ dd if=/dev/sda of=/dev/null &
: root@llana fred #; jobs
[1]+ Running dd
if=/dev/sda of=/dev/null &
https://aljaz.murerzen.eu/posts/2022-11-11-shells-need-typin...
• No need to involve Path objects; open("beef.txt") works.
• The first argument for filter() should be the filter, not the iterable.
• The stream itself is iterable, so no need to call the readlines() method.
• The first argument for re.match() should be the pattern, not the string.
• You probably wanted re.search(), not re.match(). Or, since this is a fixed string, use the ‘in’ operator.
• ‘is not None’ is not necessary for checking if the match was successful.
• It's ‘reversed’, not ‘reverse’.
• All that sorting and reversing is unnecessary when you're going to put it in a set anyway.
Here's my attempt[1]:
Python is especially bad at this task for a number of reasons:- Iterating over the lines yields strings ending with newlines. If you naively loop and print the sorted lines, `print` will add a second newline, resulting in every other line being empty. Hence the `end=''`.
- Python's str has many helper functions, but none for numeric sort, so you need an import, a regex, and a fallback value.
- It's very imperative with a big mutation in the middle. Using `sorted(lines, key=...)` and `print(''.join(lines))` would be simpler and safer, but each would double the memory usage for large inputs.
- Filters using list comprehensions ('x for x in y if z in x') are idiomatic and readable but very verbose.
- The concepts are a lot more complicated. Here we have file handles, magic iterators, first-class-functions, sets, generators, lists, keyword arguments, regexes, overloaded indexes, etc. A beginner would blindly copy and run, and have no chance to fix any issues that appear.
- Because Python has almost no static analysis (undefined names are runtime errors!), you'll have to find most errors by running your code. For a quick shell script this usually mean running over the real inputs, giving a horribly slow edit-compile-run loop and be potentially dangerous.
[1]: https://gist.github.com/boppreh/3cf5489da92d85f11e152739320d...
Pipes and output redirection, running things in parallel and waiting till they end - for some reasons these things suck in some languages compared to what the most basic shell can provide.
Shells let you do unexpected, unintended integrations.
I think languages could do better without breaking
Mostly shells are good at scaling downward, ie having relatively little syntactic (and brain) overhead for very simple things. Especially when you are ok to handle any failures manually.
"Bash is like the neighbor who seems nice and sweet, but maybe they don’t have a life you’d want to have personally. They have some pretty serious character flaws and obviously make mistakes, but in the end they are a nice person who tries hard to make sure the people that depend on them are doing ok.
"Batch [cmd.exe] is the guy who looks like that neighbor, but then turns out to be a serial killer."
https://blog.nullspace.io/batch.html
This is important in a practical rather than theoretical way.
As for structured data......lines of ascii is more that useful enough a great deal of the time.
Exit status are inconsistent. Stderr output: is it a warning (ignorable) or an error?
exit codes are the only true way to tell if some process failed and programs should set them properly.
To take the example command that's given:
beef.txt | grep "lasagna" | sort -n | uniq
Sure, writing the logic out for this in something like python straight out the bat with only the standard library might look messy, but with one basic convenience function it could quickly be:
search(for='lasagna', in='beef.txt', clear_duplicates=False).sorted()
Obviously you have to write the function in the first place, but I'd say if you're doing something like this often, it's easily worth spending that 2 minutes. And if you're not doing this often, you'll have a faster time writing more code, but keeping less heavy lifting of "how does bash pipe together" in your head.
I shared a project here a few weeks ago experimenting with what my dream shell might look like, what surprised me more than anything else, was how easy writing a repl environment actually is. I put a scrappy one together as one person in a few hours, so I don't understand why as developers we've reached general language models before being able to make a powerful, but new-user friendly shell.
Also, completely unrelated note, but posix only allows passing back strings - but isn't this true of web apis too which we use all the time? How come no json as a standard passback from programs?
Shameless plug for the project I mentioned earlier: https://github.com/benrutter/clamshell
You could more faithfully mirror the bash example in Haskell, if you wanted to, if you wrote the necessary library functions:
This would be actual valid Haskell syntax.Yes, the Python equivalent would be:
lines("beef.txt").filter(lambda(x): x.contains("lasagna")).sort(type="natural").remove_duplicates()
Of course, all of those things would need to be generators. Python accepts that kind of code, but most "Python developers" wouldn't think on it. Haskell is a much more natural fit for replacing Bash. (By the way, Haskell has at least one Bash replacement library that I remember, but it's hard to find).
I guess you can do something very close, though.
EDIT: I put back the `n` flag, in case the lines start with numbers.
Imagine if python functions /classes had their own add implementation.
You aren't just looking for foo + bar. You've also got baz(foo, bar, add), biz(add, foo, bar), buz(foo, bar).add().
Some people will use one. Some another, they'll be subtle incompatilities, etc.
EDIT: After playing around with the python approach, I arrived at this, which is definitely more verbose, but arguably, more maintainable:
I've learned from experience that it is hard to replace certain simple space and memory efficient programs that have been tuned over the course of 40 or more years. If one wanted to print only a single instance of a line in a file, then python's set construct in your snippet is certainly sparking more joy compared to the awk equivalent of printing only the first instance of a line in a file:
Tuning python to use ordered dicts to keep the line order looks a little less pleasant (and might appear confusing to an uninitiated), but would still be rather neat. There are many instance of day-to-day data inspection tasks where bash, sort, shuf, sed, grep (or rg), and awk will solve a problem near optimally out of the box and can combine nicely in modest memory machines to tackle large data, when some python native code (or poorly designed python library) might accidentally cause a performance mess. Interactive shells are an awesome superpower that is hopefully going to stay with us for a while. I'd have personally been happy if some type of Lisp REPL had won over the shell fights, but we don't live in that world, so python + posix tools via a shell will have to work in practice for most people.For me, it's the fact that it nicely maps to high level steps I would use to manipulate the option, expressed quite tersely.
But then you could say the same about forth.
get_users filterActiveUsers sortByDateCreated getEmail
Would be valid forth. But that doesn't seem to match people's brain in the same way.
Maybe humans are weird and inconsistent?
Or maybe it's because it would end up as & "file" -f 5 --bar get_users?
Although the shell doesn't really parse the options so you could cheat a bit and do
&Get_users -f "file" --bar 5
Which to me seems more readable than standard shell.
Or perhaps it's that a stack is good for certain things, but not for everything?
If your language just consisted of map() you'd soon run into its limits.
Still, even with that in mind, Python variant could've been greatly simplified. To give the author the benefit of a doubt: they probably made such a contrived example on purpose to underscore their point, but it's hard to find small-scale examples which illustrate verbosity... On the other hand, I might be overthinking on behalf of the author. Doubt they explicitly set out to make a contrived example. They probably wrote what they wrote and were happy with the result because it helped them to make a compelling argument, and they didn't think to analyze their own illustrations too deeply to see if they might disagree with their conclusions.
I agree that mine would explode on a massive file, but how would the shell version avoid this, given the need to sort? As far as I know, it sorts entirely in memory.
https://unix.stackexchange.com/questions/279096/scalability-...
Aside from the terrible, terrible idea of using verbs as prefixes, Power Shell is a great combination between a programming language and a shell.
I still use Bourne-derived shells because they're just so damn human.
As in: Architecture must be the reason someone thought this insane scheme has any merit. And arbitrarily starting to throw exceptions whenever a subprocess dares to write to stderr, especially if the shell doesn't do this in normal operation, doesn't get any other label than "insane" from me.
[1]: https://stackoverflow.com/a/57272097
...and that time period where Invoke-WebRequest used IE for parsing by default.
But other than those flaws I agree that it is really great.
On any Linux/Unix, when `ls` is called, PS runs the native `ls` and not `Get-ChildItem`. These aliases only exist on Windows, where unless one is explicitly running a Unix-ish shell (WSL, Cygwin, MinGW, MSYS2, etc), PS will use these aliases. Plus, `ls`, `mv`, `cp`, and `rm` never really existed on Windows; the equivalent cmd binaries were `dir`, `move`, `copy`/`robocopy`, `del`.
Finally, this is a fairly tired argument against PowerShell...
[1]: https://learn.microsoft.com/en-us/powershell/scripting/learn...
I was unaware of the difference between Windows and Linux in this regard as I don't tend to use PowerShell on the few Linux systems I run. However, I maintain that it is still a mistake to alias them in Windows because anyone coming from a POSIX shell background will be misled.
> the equivalent cmd binaries were `dir`, `move`, `copy`/`robocopy`, `del`.
Nit: only robocopy is a binary in the list. The rest are built in to the command shell.
My sarcasm detector led me into thinking you were a sarcastic detractor (heh).
I get your point though; perhaps PowerShell should've stuck with the initialism aliases only (`gci`, `cpi`, `ri`, etc) and people wouldn't have nitpicked on this as though it was some powerful argument against PS.
Perhaps there could've been a banner saying `hey, this is Windows, use `gci` instead of `ls` with these xyz options`.
I think most people have this issue when jumping between shells. Each one is has its own way of working. But adding aliases, I can see why they did it. The problem is it was poorly done up front. Now they are kind of stuck with it and we get to deal with it. Also you may be expecting all of the sorting items out of dir/ls. But you do not get those. You get to pipe it and sort it in a secondary step. Which is fine. But can is a bit of a pain when you are used to something like 'dir /os' vs 'Get-ChildItem . | Sort-Object -Property Length'. It just takes some getting used to. But seems longer and more verbose to get the same effect.
It is nicer though when you want to act on those items though (which is where powershell shines) as everything is in an object and you can slice it down. But using it for 'shell' things like where is a file and what is the biggest in a folder it falls a bit flat and feels like the object model is in the way.
> where is a file and what is the biggest in a folder
You don't need to use the full commands and parameters. The commands have aliases, and parameters can be arbitrarily short (and case-insensitive) as long as they are unambiguous:
Furthermore, the PowerShell interpreter has an extremely powerful autocomplete, so these should be straightforward to look up.Wow.
The problem of course is so now you want your script to 'pipe' to pandoc, say, and pandoc even if implemented like this might not have chosen Python but Ruby, say. So you have to (or ~have to, most reasonable way is to) use the CLI to interface with it anyway.
Unless it's Perl!.. although some would claim it's unreadable and not robust at all.